@almadar/ui 5.21.11 → 5.22.2

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 (87) hide show
  1. package/dist/avl/index.cjs +939 -638
  2. package/dist/avl/index.js +939 -638
  3. package/dist/components/core/molecules/CalendarGrid.d.ts +3 -10
  4. package/dist/components/core/molecules/ContentRenderer.d.ts +2 -2
  5. package/dist/components/core/molecules/DataGrid.d.ts +11 -20
  6. package/dist/components/core/molecules/DataList.d.ts +9 -15
  7. package/dist/components/core/molecules/FormSection.d.ts +4 -4
  8. package/dist/components/core/molecules/PositionedCanvas.d.ts +4 -17
  9. package/dist/components/core/molecules/ReplyTree.d.ts +2 -13
  10. package/dist/components/core/molecules/RichBlockEditor.d.ts +3 -6
  11. package/dist/components/core/molecules/SortableList.d.ts +7 -5
  12. package/dist/components/core/molecules/TableView.d.ts +7 -7
  13. package/dist/components/core/molecules/index.d.ts +3 -3
  14. package/dist/components/core/molecules/useDataDnd.d.ts +5 -5
  15. package/dist/components/core/organisms/CardGrid.d.ts +5 -2
  16. package/dist/components/core/organisms/CaseStudyOrganism.d.ts +4 -3
  17. package/dist/components/core/organisms/DataTable.d.ts +4 -2
  18. package/dist/components/core/organisms/DetailPanel.d.ts +6 -6
  19. package/dist/components/core/organisms/FeatureGridOrganism.d.ts +4 -3
  20. package/dist/components/core/organisms/HeroOrganism.d.ts +4 -5
  21. package/dist/components/core/organisms/List.d.ts +5 -2
  22. package/dist/components/core/organisms/MasterDetail.d.ts +4 -2
  23. package/dist/components/core/organisms/MediaGallery.d.ts +4 -2
  24. package/dist/components/core/organisms/ShowcaseOrganism.d.ts +4 -3
  25. package/dist/components/core/organisms/StatCard.d.ts +5 -2
  26. package/dist/components/core/organisms/StepFlowOrganism.d.ts +4 -3
  27. package/dist/components/core/organisms/Timeline.d.ts +2 -2
  28. package/dist/components/core/organisms/book/index.d.ts +1 -1
  29. package/dist/components/core/organisms/book/types.d.ts +28 -48
  30. package/dist/components/core/organisms/index.d.ts +1 -2
  31. package/dist/components/core/organisms/layout/DashboardGrid.d.ts +2 -2
  32. package/dist/components/core/organisms/marketing-types.d.ts +5 -94
  33. package/dist/components/core/organisms/types.d.ts +9 -27
  34. package/dist/components/core/templates/index.d.ts +6 -6
  35. package/dist/components/game/organisms/BattleBoard.d.ts +14 -90
  36. package/dist/components/game/organisms/CastleBoard.d.ts +7 -21
  37. package/dist/components/game/organisms/UncontrolledBattleBoard.d.ts +2 -7
  38. package/dist/components/game/organisms/WorldMapBoard.d.ts +13 -59
  39. package/dist/components/game/organisms/boardEntity.d.ts +44 -0
  40. package/dist/components/game/organisms/hooks/useBattleState.d.ts +7 -7
  41. package/dist/components/game/organisms/index.d.ts +3 -3
  42. package/dist/components/game/organisms/puzzles/builder/BuilderBoard.d.ts +7 -20
  43. package/dist/components/game/organisms/puzzles/builder/index.d.ts +1 -1
  44. package/dist/components/game/organisms/puzzles/classifier/ClassifierBoard.d.ts +7 -20
  45. package/dist/components/game/organisms/puzzles/classifier/index.d.ts +1 -1
  46. package/dist/components/game/organisms/puzzles/debugger/DebuggerBoard.d.ts +6 -22
  47. package/dist/components/game/organisms/puzzles/debugger/index.d.ts +1 -1
  48. package/dist/components/game/organisms/puzzles/event-handler/EventHandlerBoard.d.ts +6 -33
  49. package/dist/components/game/organisms/puzzles/event-handler/ObjectRulePanel.d.ts +3 -21
  50. package/dist/components/game/organisms/puzzles/event-handler/index.d.ts +2 -2
  51. package/dist/components/game/organisms/puzzles/event-handler/puzzleObject.d.ts +21 -0
  52. package/dist/components/game/organisms/puzzles/negotiator/NegotiatorBoard.d.ts +8 -24
  53. package/dist/components/game/organisms/puzzles/negotiator/index.d.ts +1 -1
  54. package/dist/components/game/organisms/puzzles/sequencer/ActionTile.d.ts +2 -2
  55. package/dist/components/game/organisms/puzzles/sequencer/SequencerBoard.d.ts +7 -36
  56. package/dist/components/game/organisms/puzzles/sequencer/index.d.ts +1 -1
  57. package/dist/components/game/organisms/puzzles/simulator/SimulatorBoard.d.ts +6 -25
  58. package/dist/components/game/organisms/puzzles/simulator/index.d.ts +1 -1
  59. package/dist/components/game/organisms/puzzles/state-architect/StateArchitectBoard.d.ts +7 -40
  60. package/dist/components/game/organisms/puzzles/state-architect/VariablePanel.d.ts +3 -9
  61. package/dist/components/game/organisms/puzzles/state-architect/index.d.ts +2 -2
  62. package/dist/components/game/organisms/three/index.cjs +35 -21
  63. package/dist/components/game/organisms/three/index.js +35 -21
  64. package/dist/components/game/templates/BattleTemplate.d.ts +2 -3
  65. package/dist/components/game/templates/CastleTemplate.d.ts +2 -3
  66. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +1 -16
  67. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +1 -18
  68. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +1 -14
  69. package/dist/components/game/templates/GameTemplate.d.ts +1 -6
  70. package/dist/components/game/templates/WorldMapTemplate.d.ts +2 -3
  71. package/dist/components/index.cjs +2036 -1675
  72. package/dist/components/index.js +1148 -787
  73. package/dist/components/marketing/organisms/PricingOrganism.d.ts +4 -3
  74. package/dist/components/marketing/organisms/StatsOrganism.d.ts +4 -3
  75. package/dist/components/marketing/organisms/TeamOrganism.d.ts +4 -3
  76. package/dist/components/marketing/organisms/book/BookChapterView.d.ts +5 -2
  77. package/dist/components/marketing/organisms/book/BookTableOfContents.d.ts +3 -2
  78. package/dist/components/marketing/organisms/book/BookViewer.d.ts +4 -4
  79. package/dist/components/marketing/templates/AboutPageTemplate.d.ts +32 -6
  80. package/dist/components/marketing/templates/FeatureDetailPageTemplate.d.ts +14 -4
  81. package/dist/components/marketing/templates/LandingPageTemplate.d.ts +47 -9
  82. package/dist/components/marketing/templates/PricingPageTemplate.d.ts +23 -5
  83. package/dist/providers/index.cjs +932 -631
  84. package/dist/providers/index.js +932 -631
  85. package/dist/runtime/index.cjs +934 -633
  86. package/dist/runtime/index.js +934 -633
  87. package/package.json +2 -2
@@ -2718,7 +2718,7 @@ var init_SvgGrid = __esm({
2718
2718
  x,
2719
2719
  y,
2720
2720
  cols = 4,
2721
- rows = 3,
2721
+ rows: rows2 = 3,
2722
2722
  spacing = 20,
2723
2723
  nodeRadius = 3,
2724
2724
  color = "var(--color-primary)",
@@ -2727,7 +2727,7 @@ var init_SvgGrid = __esm({
2727
2727
  highlights = []
2728
2728
  }) => {
2729
2729
  const highlightSet = new Set(highlights);
2730
- return /* @__PURE__ */ jsx("g", { className, opacity, children: Array.from({ length: rows }).map(
2730
+ return /* @__PURE__ */ jsx("g", { className, opacity, children: Array.from({ length: rows2 }).map(
2731
2731
  (_, row) => Array.from({ length: cols }).map((_2, col) => {
2732
2732
  const index = row * cols + col;
2733
2733
  const isHighlighted = highlightSet.has(index);
@@ -3387,7 +3387,7 @@ var init_Input = __esm({
3387
3387
  onClear,
3388
3388
  value,
3389
3389
  options,
3390
- rows = 3,
3390
+ rows: rows2 = 3,
3391
3391
  onChange,
3392
3392
  ...props
3393
3393
  }, ref) => {
@@ -3437,7 +3437,7 @@ var init_Input = __esm({
3437
3437
  ref,
3438
3438
  value,
3439
3439
  onChange,
3440
- rows,
3440
+ rows: rows2,
3441
3441
  className: baseClassName,
3442
3442
  ...props
3443
3443
  }
@@ -5518,66 +5518,6 @@ var init_RangeSlider = __esm({
5518
5518
  RangeSlider.displayName = "RangeSlider";
5519
5519
  }
5520
5520
  });
5521
- function easeOut(t) {
5522
- return t * (2 - t);
5523
- }
5524
- var AnimatedCounter;
5525
- var init_AnimatedCounter = __esm({
5526
- "components/marketing/atoms/AnimatedCounter.tsx"() {
5527
- "use client";
5528
- init_cn();
5529
- init_Typography();
5530
- AnimatedCounter = ({
5531
- value: rawValue,
5532
- duration = 600,
5533
- prefix,
5534
- suffix,
5535
- className
5536
- }) => {
5537
- const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
5538
- const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
5539
- const [displayValue, setDisplayValue] = useState(value);
5540
- const previousValueRef = useRef(value);
5541
- const animationFrameRef = useRef(null);
5542
- useEffect(() => {
5543
- const from = previousValueRef.current;
5544
- const to = value;
5545
- previousValueRef.current = value;
5546
- if (from === to) {
5547
- setDisplayValue(to);
5548
- return;
5549
- }
5550
- const startTime = performance.now();
5551
- const diff = to - from;
5552
- function animate(currentTime) {
5553
- const elapsed = currentTime - startTime;
5554
- const progress = Math.min(elapsed / duration, 1);
5555
- const easedProgress = easeOut(progress);
5556
- setDisplayValue(from + diff * easedProgress);
5557
- if (progress < 1) {
5558
- animationFrameRef.current = requestAnimationFrame(animate);
5559
- } else {
5560
- setDisplayValue(to);
5561
- }
5562
- }
5563
- animationFrameRef.current = requestAnimationFrame(animate);
5564
- return () => {
5565
- if (animationFrameRef.current !== null) {
5566
- cancelAnimationFrame(animationFrameRef.current);
5567
- }
5568
- };
5569
- }, [value, duration]);
5570
- const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5571
- const formattedValue = displayValue.toFixed(decimalPlaces);
5572
- return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5573
- prefix,
5574
- formattedValue,
5575
- suffix
5576
- ] });
5577
- };
5578
- AnimatedCounter.displayName = "AnimatedCounter";
5579
- }
5580
- });
5581
5521
  function useInfiniteScroll(onLoadMore, options = {}) {
5582
5522
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
5583
5523
  const observerRef = useRef(null);
@@ -8059,15 +7999,15 @@ function HeaderSkeleton({ className }) {
8059
7999
  ] })
8060
8000
  ] });
8061
8001
  }
8062
- function TableSkeleton({ rows = 5, columns = 4, className }) {
8002
+ function TableSkeleton({ rows: rows2 = 5, columns = 4, className }) {
8063
8003
  return /* @__PURE__ */ jsxs(VStack, { gap: "none", className: cn("border border-border rounded-lg overflow-hidden", className), children: [
8064
8004
  /* @__PURE__ */ jsx(HStack, { className: "px-4 py-3 bg-muted/30 border-b border-border", children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ jsx(SkeletonBlock, { className: "h-4 flex-1 mx-2" }, i)) }),
8065
- Array.from({ length: rows }).map((_, rowIdx) => /* @__PURE__ */ jsx(
8005
+ Array.from({ length: rows2 }).map((_, rowIdx) => /* @__PURE__ */ jsx(
8066
8006
  HStack,
8067
8007
  {
8068
8008
  className: cn(
8069
8009
  "px-4 py-3",
8070
- rowIdx < rows - 1 && "border-b border-border"
8010
+ rowIdx < rows2 - 1 && "border-b border-border"
8071
8011
  ),
8072
8012
  children: Array.from({ length: columns }).map((_2, colIdx) => /* @__PURE__ */ jsx(SkeletonLine, { className: "flex-1 mx-2" }, colIdx))
8073
8013
  },
@@ -8115,18 +8055,18 @@ function CardSkeleton({ className }) {
8115
8055
  }
8116
8056
  );
8117
8057
  }
8118
- function TextSkeleton({ rows = 3, className }) {
8119
- return /* @__PURE__ */ jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx(
8058
+ function TextSkeleton({ rows: rows2 = 3, className }) {
8059
+ return /* @__PURE__ */ jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows2 }).map((_, i) => /* @__PURE__ */ jsx(
8120
8060
  SkeletonLine,
8121
8061
  {
8122
- className: i === rows - 1 ? "w-2/3" : "w-full"
8062
+ className: i === rows2 - 1 ? "w-2/3" : "w-full"
8123
8063
  },
8124
8064
  i
8125
8065
  )) });
8126
8066
  }
8127
8067
  function Skeleton({
8128
8068
  variant = "text",
8129
- rows,
8069
+ rows: rows2,
8130
8070
  columns,
8131
8071
  fields,
8132
8072
  className
@@ -8136,15 +8076,15 @@ function Skeleton({
8136
8076
  case "header":
8137
8077
  return /* @__PURE__ */ jsx(HeaderSkeleton, { className });
8138
8078
  case "table":
8139
- return /* @__PURE__ */ jsx(TableSkeleton, { rows, columns, className });
8079
+ return /* @__PURE__ */ jsx(TableSkeleton, { rows: rows2, columns, className });
8140
8080
  case "form":
8141
8081
  return /* @__PURE__ */ jsx(FormSkeleton, { fields, className });
8142
8082
  case "card":
8143
8083
  return /* @__PURE__ */ jsx(CardSkeleton, { className });
8144
8084
  case "text":
8145
- return /* @__PURE__ */ jsx(TextSkeleton, { rows, className });
8085
+ return /* @__PURE__ */ jsx(TextSkeleton, { rows: rows2, className });
8146
8086
  default:
8147
- return /* @__PURE__ */ jsx(TextSkeleton, { rows, className });
8087
+ return /* @__PURE__ */ jsx(TextSkeleton, { rows: rows2, className });
8148
8088
  }
8149
8089
  }
8150
8090
  var pulseClass;
@@ -9654,7 +9594,7 @@ var init_Menu = __esm({
9654
9594
  className
9655
9595
  }) => {
9656
9596
  const eventBus = useEventBus();
9657
- const { t } = useTranslate();
9597
+ const { t, direction } = useTranslate();
9658
9598
  const [isOpen, setIsOpen] = useState(false);
9659
9599
  const [activeSubMenu, setActiveSubMenu] = useState(null);
9660
9600
  const [triggerRect, setTriggerRect] = useState(null);
@@ -9708,6 +9648,18 @@ var init_Menu = __esm({
9708
9648
  "bottom-start": "top-full left-0 mt-2",
9709
9649
  "bottom-end": "top-full right-0 mt-2"
9710
9650
  };
9651
+ const rtlMirror = {
9652
+ "top-left": "top-right",
9653
+ "top-right": "top-left",
9654
+ "bottom-left": "bottom-right",
9655
+ "bottom-right": "bottom-left",
9656
+ "top-start": "top-end",
9657
+ "top-end": "top-start",
9658
+ "bottom-start": "bottom-end",
9659
+ "bottom-end": "bottom-start"
9660
+ };
9661
+ const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
9662
+ const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
9711
9663
  const triggerChild = React85__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
9712
9664
  const triggerElement = React85__default.cloneElement(
9713
9665
  triggerChild,
@@ -9735,7 +9687,7 @@ var init_Menu = __esm({
9735
9687
  onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
9736
9688
  "data-testid": item.event ? `action-${item.event}` : void 0,
9737
9689
  className: cn(
9738
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-left",
9690
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
9739
9691
  "text-sm transition-colors",
9740
9692
  "hover:bg-muted",
9741
9693
  "focus:outline-none focus:bg-muted",
@@ -9754,7 +9706,7 @@ var init_Menu = __esm({
9754
9706
  }
9755
9707
  ),
9756
9708
  item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
9757
- hasSubMenu && /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: "sm", className: "flex-shrink-0" })
9709
+ hasSubMenu && /* @__PURE__ */ jsx(Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
9758
9710
  ] })
9759
9711
  },
9760
9712
  itemId
@@ -9774,7 +9726,8 @@ var init_Menu = __esm({
9774
9726
  Box,
9775
9727
  {
9776
9728
  className: cn(
9777
- "absolute left-full top-0 ml-2 z-50",
9729
+ "absolute top-0 z-50",
9730
+ subMenuSideClass,
9778
9731
  menuContainerStyles
9779
9732
  ),
9780
9733
  children: renderMenuItems(item.subMenu)
@@ -9792,12 +9745,12 @@ var init_Menu = __esm({
9792
9745
  className: cn(
9793
9746
  "absolute z-50",
9794
9747
  menuContainerStyles,
9795
- positionClasses3[position],
9748
+ positionClasses3[effectivePosition],
9796
9749
  className
9797
9750
  ),
9798
9751
  style: {
9799
- left: position.includes("left") ? 0 : "auto",
9800
- right: position.includes("right") ? 0 : "auto"
9752
+ left: effectivePosition.includes("left") ? 0 : "auto",
9753
+ right: effectivePosition.includes("right") ? 0 : "auto"
9801
9754
  },
9802
9755
  role: "menu",
9803
9756
  children: renderMenuItems(items)
@@ -10115,7 +10068,7 @@ var init_MapView = __esm({
10115
10068
  shadowSize: [41, 41]
10116
10069
  });
10117
10070
  L.Marker.prototype.options.icon = defaultIcon;
10118
- const { useEffect: useEffect70, useRef: useRef66, useCallback: useCallback113, useState: useState100 } = React85__default;
10071
+ const { useEffect: useEffect70, useRef: useRef66, useCallback: useCallback114, useState: useState100 } = React85__default;
10119
10072
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10120
10073
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10121
10074
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10161,7 +10114,7 @@ var init_MapView = __esm({
10161
10114
  }) {
10162
10115
  const eventBus = useEventBus2();
10163
10116
  const [clickedPosition, setClickedPosition] = useState100(null);
10164
- const handleMapClick = useCallback113((lat, lng) => {
10117
+ const handleMapClick = useCallback114((lat, lng) => {
10165
10118
  if (showClickedPin) {
10166
10119
  setClickedPosition({ lat, lng });
10167
10120
  }
@@ -10170,7 +10123,7 @@ var init_MapView = __esm({
10170
10123
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10171
10124
  }
10172
10125
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10173
- const handleMarkerClick = useCallback113((marker) => {
10126
+ const handleMarkerClick = useCallback114((marker) => {
10174
10127
  onMarkerClick?.(marker);
10175
10128
  if (markerClickEvent) {
10176
10129
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -10391,7 +10344,7 @@ function InputPattern({
10391
10344
  function TextareaPattern({
10392
10345
  value = "",
10393
10346
  placeholder,
10394
- rows = 4,
10347
+ rows: rows2 = 4,
10395
10348
  disabled = false,
10396
10349
  fieldError,
10397
10350
  onChange,
@@ -10411,7 +10364,7 @@ function TextareaPattern({
10411
10364
  {
10412
10365
  value: localValue,
10413
10366
  placeholder,
10414
- rows,
10367
+ rows: rows2,
10415
10368
  disabled,
10416
10369
  error: fieldError,
10417
10370
  onChange: handleChange,
@@ -10896,6 +10849,91 @@ var init_ActionPalette = __esm({
10896
10849
  ActionPalette.displayName = "ActionPalette";
10897
10850
  }
10898
10851
  });
10852
+ function parseValue(value) {
10853
+ if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
10854
+ const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
10855
+ if (!match) {
10856
+ return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
10857
+ }
10858
+ const numStr = match[2];
10859
+ const decimalIdx = numStr.indexOf(".");
10860
+ const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
10861
+ return {
10862
+ prefix: match[1],
10863
+ num: parseFloat(numStr),
10864
+ suffix: match[3],
10865
+ decimals
10866
+ };
10867
+ }
10868
+ var AnimatedCounter;
10869
+ var init_AnimatedCounter = __esm({
10870
+ "components/core/molecules/AnimatedCounter.tsx"() {
10871
+ "use client";
10872
+ init_cn();
10873
+ init_Box();
10874
+ init_Typography();
10875
+ AnimatedCounter = ({
10876
+ value,
10877
+ label,
10878
+ duration = 1500,
10879
+ className
10880
+ }) => {
10881
+ const ref = useRef(null);
10882
+ const [displayValue, setDisplayValue] = useState("0");
10883
+ const [hasAnimated, setHasAnimated] = useState(false);
10884
+ const animate = useCallback(() => {
10885
+ const { num: num2, prefix, suffix, decimals } = parseValue(value);
10886
+ if (num2 === 0) {
10887
+ setDisplayValue(String(value));
10888
+ return;
10889
+ }
10890
+ const startTime = performance.now();
10891
+ const tick = (now) => {
10892
+ const elapsed = now - startTime;
10893
+ const progress = Math.min(elapsed / duration, 1);
10894
+ const eased = 1 - Math.pow(1 - progress, 3);
10895
+ const current = eased * num2;
10896
+ setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
10897
+ if (progress < 1) {
10898
+ requestAnimationFrame(tick);
10899
+ } else {
10900
+ setDisplayValue(String(value));
10901
+ }
10902
+ };
10903
+ requestAnimationFrame(tick);
10904
+ }, [value, duration]);
10905
+ useEffect(() => {
10906
+ if (hasAnimated) return;
10907
+ const el = ref.current;
10908
+ if (!el) return;
10909
+ const observer2 = new IntersectionObserver(
10910
+ (entries) => {
10911
+ if (entries[0].isIntersecting) {
10912
+ setHasAnimated(true);
10913
+ animate();
10914
+ observer2.disconnect();
10915
+ }
10916
+ },
10917
+ { threshold: 0.3 }
10918
+ );
10919
+ observer2.observe(el);
10920
+ return () => observer2.disconnect();
10921
+ }, [hasAnimated, animate]);
10922
+ return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
10923
+ /* @__PURE__ */ jsx(
10924
+ Typography,
10925
+ {
10926
+ variant: "h2",
10927
+ className: "text-primary font-bold tabular-nums",
10928
+ children: hasAnimated ? displayValue : "0"
10929
+ }
10930
+ ),
10931
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
10932
+ ] });
10933
+ };
10934
+ AnimatedCounter.displayName = "AnimatedCounter";
10935
+ }
10936
+ });
10899
10937
  var AuthLayout;
10900
10938
  var init_AuthLayout = __esm({
10901
10939
  "components/core/templates/AuthLayout.tsx"() {
@@ -12237,6 +12275,39 @@ var init_IsometricCanvas2 = __esm({
12237
12275
  init_IsometricCanvas();
12238
12276
  }
12239
12277
  });
12278
+
12279
+ // components/game/organisms/boardEntity.ts
12280
+ function boardEntity(entity) {
12281
+ if (!entity) return void 0;
12282
+ return Array.isArray(entity) ? entity[0] : entity;
12283
+ }
12284
+ function str(v) {
12285
+ return v == null ? "" : String(v);
12286
+ }
12287
+ function num(v, fallback = 0) {
12288
+ const n = Number(v);
12289
+ return Number.isFinite(n) ? n : fallback;
12290
+ }
12291
+ function rows(v) {
12292
+ return Array.isArray(v) ? v : [];
12293
+ }
12294
+ function vec2(v) {
12295
+ const o = v ?? {};
12296
+ return { x: num(o.x), y: num(o.y) };
12297
+ }
12298
+ function unitPosition(u) {
12299
+ return vec2(u.position);
12300
+ }
12301
+ function unitTeam(u) {
12302
+ return str(u.team);
12303
+ }
12304
+ function unitHealth(u) {
12305
+ return num(u.health);
12306
+ }
12307
+ var init_boardEntity = __esm({
12308
+ "components/game/organisms/boardEntity.ts"() {
12309
+ }
12310
+ });
12240
12311
  function BattleBoard({
12241
12312
  entity,
12242
12313
  scale = 0.45,
@@ -12263,43 +12334,49 @@ function BattleBoard({
12263
12334
  attackEvent,
12264
12335
  className
12265
12336
  }) {
12266
- const tiles = entity.tiles;
12267
- const features = entity.features ?? [];
12268
- const boardWidth = entity.boardWidth ?? 8;
12269
- const boardHeight = entity.boardHeight ?? 6;
12270
- const assetManifest = entity.assetManifest;
12271
- const backgroundImage = entity.backgroundImage;
12272
- const units = entity.units;
12273
- const selectedUnitId = entity.selectedUnitId;
12274
- const currentPhase = entity.phase;
12275
- const currentTurn = entity.turn;
12276
- const gameResult = entity.gameResult;
12337
+ const board = boardEntity(entity) ?? {};
12338
+ const tiles = Array.isArray(board.tiles) ? board.tiles : [];
12339
+ const features = Array.isArray(board.features) ? board.features : [];
12340
+ const boardWidth = num(board.boardWidth, 8);
12341
+ const boardHeight = num(board.boardHeight, 6);
12342
+ const assetManifest = board.assetManifest;
12343
+ const backgroundImage = board.backgroundImage;
12344
+ const units = rows(board.units);
12345
+ const selectedUnitId = board.selectedUnitId ?? null;
12346
+ const currentPhase = str(board.phase) || "observation";
12347
+ const currentTurn = num(board.turn, 1);
12348
+ const gameResult = board.gameResult ?? null;
12277
12349
  const eventBus = useEventBus();
12278
12350
  const { t } = useTranslate();
12279
12351
  const [hoveredTile, setHoveredTile] = useState(null);
12280
12352
  const [isShaking, setIsShaking] = useState(false);
12281
12353
  const selectedUnit = useMemo(
12282
- () => units.find((u) => u.id === selectedUnitId) ?? null,
12354
+ () => units.find((u) => str(u.id) === selectedUnitId) ?? null,
12283
12355
  [units, selectedUnitId]
12284
12356
  );
12285
12357
  const hoveredUnit = useMemo(() => {
12286
12358
  if (!hoveredTile) return null;
12287
- return units.find(
12288
- (u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
12289
- ) ?? null;
12359
+ return units.find((u) => {
12360
+ const p2 = unitPosition(u);
12361
+ return p2.x === hoveredTile.x && p2.y === hoveredTile.y && unitHealth(u) > 0;
12362
+ }) ?? null;
12290
12363
  }, [hoveredTile, units]);
12291
- const playerUnits = useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
12292
- const enemyUnits = useMemo(() => units.filter((u) => u.team === "enemy" && u.health > 0), [units]);
12364
+ const playerUnits = useMemo(() => units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0), [units]);
12365
+ const enemyUnits = useMemo(() => units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0), [units]);
12293
12366
  const validMoves = useMemo(() => {
12294
12367
  if (!selectedUnit || currentPhase !== "movement") return [];
12295
12368
  const moves = [];
12296
- const range = selectedUnit.movement;
12369
+ const range = num(selectedUnit.movement);
12370
+ const origin = unitPosition(selectedUnit);
12297
12371
  for (let dy = -range; dy <= range; dy++) {
12298
12372
  for (let dx = -range; dx <= range; dx++) {
12299
- const nx = selectedUnit.position.x + dx;
12300
- const ny = selectedUnit.position.y + dy;
12373
+ const nx = origin.x + dx;
12374
+ const ny = origin.y + dy;
12301
12375
  const dist = Math.abs(dx) + Math.abs(dy);
12302
- if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => u.position.x === nx && u.position.y === ny && u.health > 0)) {
12376
+ if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => {
12377
+ const p2 = unitPosition(u);
12378
+ return p2.x === nx && p2.y === ny && unitHealth(u) > 0;
12379
+ })) {
12303
12380
  moves.push({ x: nx, y: ny });
12304
12381
  }
12305
12382
  }
@@ -12308,11 +12385,14 @@ function BattleBoard({
12308
12385
  }, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
12309
12386
  const attackTargets = useMemo(() => {
12310
12387
  if (!selectedUnit || currentPhase !== "action") return [];
12311
- return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
12312
- const dx = Math.abs(u.position.x - selectedUnit.position.x);
12313
- const dy = Math.abs(u.position.y - selectedUnit.position.y);
12388
+ const sp = unitPosition(selectedUnit);
12389
+ const sTeam = unitTeam(selectedUnit);
12390
+ return units.filter((u) => unitTeam(u) !== sTeam && unitHealth(u) > 0).filter((u) => {
12391
+ const p2 = unitPosition(u);
12392
+ const dx = Math.abs(p2.x - sp.x);
12393
+ const dy = Math.abs(p2.y - sp.y);
12314
12394
  return dx <= 1 && dy <= 1 && dx + dy > 0;
12315
- }).map((u) => u.position);
12395
+ }).map((u) => unitPosition(u));
12316
12396
  }, [selectedUnit, currentPhase, units]);
12317
12397
  const MOVE_SPEED_MS_PER_TILE = 300;
12318
12398
  const movementAnimRef = useRef(null);
@@ -12352,23 +12432,25 @@ function BattleBoard({
12352
12432
  return () => clearInterval(interval);
12353
12433
  }, []);
12354
12434
  const isoUnits = useMemo(() => {
12355
- return units.filter((u) => u.health > 0).map((unit) => {
12356
- const pos = movingPositions.get(unit.id) ?? unit.position;
12435
+ return units.filter((u) => unitHealth(u) > 0).map((unit) => {
12436
+ const id = str(unit.id);
12437
+ const pos = movingPositions.get(id) ?? unitPosition(unit);
12438
+ const unitTraits = Array.isArray(unit.traits) ? unit.traits : void 0;
12357
12439
  return {
12358
- id: unit.id,
12440
+ id,
12359
12441
  position: pos,
12360
- name: unit.name,
12361
- team: unit.team,
12362
- health: unit.health,
12363
- maxHealth: unit.maxHealth,
12364
- unitType: unit.unitType,
12365
- heroId: unit.heroId,
12366
- sprite: unit.sprite,
12367
- traits: unit.traits?.map((t2) => ({
12368
- name: t2.name,
12369
- currentState: t2.currentState,
12370
- states: t2.states,
12371
- cooldown: t2.cooldown ?? 0
12442
+ name: str(unit.name),
12443
+ team: unitTeam(unit),
12444
+ health: unitHealth(unit),
12445
+ maxHealth: num(unit.maxHealth),
12446
+ unitType: unit.unitType == null ? void 0 : str(unit.unitType),
12447
+ heroId: unit.heroId == null ? void 0 : str(unit.heroId),
12448
+ sprite: unit.sprite == null ? void 0 : str(unit.sprite),
12449
+ traits: unitTraits?.map((tr) => ({
12450
+ name: tr.name,
12451
+ currentState: tr.currentState,
12452
+ states: tr.states,
12453
+ cooldown: tr.cooldown ?? 0
12372
12454
  }))
12373
12455
  };
12374
12456
  });
@@ -12380,8 +12462,8 @@ function BattleBoard({
12380
12462
  [scale, baseOffsetX]
12381
12463
  );
12382
12464
  const checkGameEnd = useCallback(() => {
12383
- const pa = units.filter((u) => u.team === "player" && u.health > 0);
12384
- const ea = units.filter((u) => u.team === "enemy" && u.health > 0);
12465
+ const pa = units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
12466
+ const ea = units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
12385
12467
  if (pa.length === 0) {
12386
12468
  onGameEnd?.("defeat");
12387
12469
  if (gameEndEvent) {
@@ -12395,21 +12477,22 @@ function BattleBoard({
12395
12477
  }
12396
12478
  }, [units, onGameEnd, gameEndEvent, eventBus]);
12397
12479
  const handleUnitClick = useCallback((unitId) => {
12398
- const unit = units.find((u) => u.id === unitId);
12480
+ const unit = units.find((u) => str(u.id) === unitId);
12399
12481
  if (!unit) return;
12400
12482
  if (unitClickEvent) {
12401
12483
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
12402
12484
  }
12403
12485
  if (currentPhase === "action" && selectedUnit) {
12404
- if (unit.team === "enemy" && attackTargets.some((t2) => t2.x === unit.position.x && t2.y === unit.position.y)) {
12405
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
12486
+ const up = unitPosition(unit);
12487
+ if (unitTeam(unit) === "enemy" && attackTargets.some((t2) => t2.x === up.x && t2.y === up.y)) {
12488
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
12406
12489
  setIsShaking(true);
12407
12490
  setTimeout(() => setIsShaking(false), 300);
12408
12491
  onAttack?.(selectedUnit, unit, damage);
12409
12492
  if (attackEvent) {
12410
12493
  eventBus.emit(`UI:${attackEvent}`, {
12411
- attackerId: selectedUnit.id,
12412
- targetId: unit.id,
12494
+ attackerId: str(selectedUnit.id),
12495
+ targetId: str(unit.id),
12413
12496
  damage
12414
12497
  });
12415
12498
  }
@@ -12424,9 +12507,9 @@ function BattleBoard({
12424
12507
  if (currentPhase === "movement" && selectedUnit) {
12425
12508
  if (movementAnimRef.current) return;
12426
12509
  if (validMoves.some((m) => m.x === x && m.y === y)) {
12427
- const from = { ...selectedUnit.position };
12510
+ const from = { ...unitPosition(selectedUnit) };
12428
12511
  const to = { x, y };
12429
- startMoveAnimation(selectedUnit.id, from, to, () => {
12512
+ startMoveAnimation(str(selectedUnit.id), from, to, () => {
12430
12513
  onUnitMove?.(selectedUnit, to);
12431
12514
  });
12432
12515
  }
@@ -12584,6 +12667,7 @@ var init_BattleBoard = __esm({
12584
12667
  init_Typography();
12585
12668
  init_Stack();
12586
12669
  init_IsometricCanvas2();
12670
+ init_boardEntity();
12587
12671
  init_isometric();
12588
12672
  BattleBoard.displayName = "BattleBoard";
12589
12673
  }
@@ -13806,24 +13890,24 @@ var init_CodeBlock = __esm({
13806
13890
  return;
13807
13891
  }
13808
13892
  lineEls.forEach((el) => {
13809
- const num = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13810
- if (hiddenLines.has(num)) {
13893
+ const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13894
+ if (hiddenLines.has(num2)) {
13811
13895
  el.style.display = "none";
13812
13896
  return;
13813
13897
  }
13814
13898
  el.style.display = "";
13815
13899
  el.style.position = "relative";
13816
13900
  el.style.paddingLeft = "1.2em";
13817
- const region = foldStartMap.get(num);
13901
+ const region = foldStartMap.get(num2);
13818
13902
  if (!region) return;
13819
- const isCollapsed = collapsed.has(num);
13903
+ const isCollapsed = collapsed.has(num2);
13820
13904
  const toggle = document.createElement("span");
13821
13905
  toggle.className = "fold-toggle";
13822
13906
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13823
13907
  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%";
13824
13908
  toggle.addEventListener("click", (e) => {
13825
13909
  e.stopPropagation();
13826
- toggleFoldRef.current(num);
13910
+ toggleFoldRef.current(num2);
13827
13911
  });
13828
13912
  el.insertBefore(toggle, el.firstChild);
13829
13913
  if (isCollapsed) {
@@ -16076,10 +16160,13 @@ var init_BookChapterView = __esm({
16076
16160
  init_cn();
16077
16161
  BookChapterView = ({
16078
16162
  chapter,
16163
+ orbitalSchema,
16079
16164
  direction,
16080
16165
  className
16081
16166
  }) => {
16082
16167
  const { t: _t } = useTranslate();
16168
+ const title = String(chapter.title ?? "");
16169
+ const content = String(chapter.content ?? "");
16083
16170
  return /* @__PURE__ */ jsxs(
16084
16171
  VStack,
16085
16172
  {
@@ -16087,16 +16174,16 @@ var init_BookChapterView = __esm({
16087
16174
  className: cn("px-6 py-8 max-w-4xl mx-auto w-full", className),
16088
16175
  style: { direction },
16089
16176
  children: [
16090
- /* @__PURE__ */ jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: chapter.title }),
16177
+ /* @__PURE__ */ jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: title }),
16091
16178
  /* @__PURE__ */ jsx(Divider, {}),
16092
- !!chapter.orbitalSchema && /* @__PURE__ */ jsx(ScaledDiagram, { children: /* @__PURE__ */ jsx(
16179
+ !!orbitalSchema && /* @__PURE__ */ jsx(ScaledDiagram, { children: /* @__PURE__ */ jsx(
16093
16180
  JazariStateMachine,
16094
16181
  {
16095
- schema: chapter.orbitalSchema,
16182
+ schema: orbitalSchema,
16096
16183
  direction
16097
16184
  }
16098
16185
  ) }),
16099
- /* @__PURE__ */ jsx(ContentRenderer, { content: chapter.content, direction })
16186
+ /* @__PURE__ */ jsx(ContentRenderer, { content, direction })
16100
16187
  ]
16101
16188
  }
16102
16189
  );
@@ -16194,7 +16281,7 @@ var init_BookNavBar = __esm({
16194
16281
  BookNavBar = ({
16195
16282
  currentPage,
16196
16283
  totalPages,
16197
- chapterTitle,
16284
+ chapterTitle: chapterTitle2,
16198
16285
  direction,
16199
16286
  className
16200
16287
  }) => {
@@ -16235,12 +16322,12 @@ var init_BookNavBar = __esm({
16235
16322
  )
16236
16323
  ] }),
16237
16324
  /* @__PURE__ */ jsxs(Box, { className: "flex-1 mx-4 max-w-md", children: [
16238
- chapterTitle && /* @__PURE__ */ jsx(
16325
+ chapterTitle2 && /* @__PURE__ */ jsx(
16239
16326
  Typography,
16240
16327
  {
16241
16328
  variant: "caption",
16242
16329
  className: "text-center block truncate text-muted-foreground",
16243
- children: chapterTitle
16330
+ children: chapterTitle2
16244
16331
  }
16245
16332
  ),
16246
16333
  /* @__PURE__ */ jsx(ProgressBar, { value: progress, size: "sm", variant: "primary" })
@@ -16307,31 +16394,35 @@ var init_BookTableOfContents = __esm({
16307
16394
  style: { direction },
16308
16395
  children: [
16309
16396
  /* @__PURE__ */ jsx(Typography, { variant: "h1", className: "text-3xl font-bold text-center mb-4", children: t("book.tableOfContents") }),
16310
- parts.map((part, partIdx) => /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
16311
- /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
16312
- /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16313
- /* @__PURE__ */ jsx(Typography, { variant: "h3", className: "font-semibold", children: part.title })
16314
- ] }),
16315
- /* @__PURE__ */ jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: part.chapters.map((chapter) => {
16316
- const isCurrent = chapter.id === currentChapterId;
16317
- return /* @__PURE__ */ jsx(
16318
- Button,
16319
- {
16320
- variant: "ghost",
16321
- size: "sm",
16322
- action: "BOOK_NAVIGATE",
16323
- actionPayload: { chapterId: chapter.id },
16324
- className: cn(
16325
- "justify-start text-left w-full",
16326
- direction === "rtl" && "text-right",
16327
- isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16328
- ),
16329
- children: /* @__PURE__ */ jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: chapter.title }) })
16330
- },
16331
- chapter.id
16332
- );
16333
- }) })
16334
- ] }, partIdx))
16397
+ parts.map((part, partIdx) => {
16398
+ const chapters = Array.isArray(part.chapters) ? part.chapters : [];
16399
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
16400
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
16401
+ /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16402
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", className: "font-semibold", children: String(part.title ?? "") })
16403
+ ] }),
16404
+ /* @__PURE__ */ jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: chapters.map((chapter) => {
16405
+ const id = chapter.id == null ? "" : String(chapter.id);
16406
+ const isCurrent = id === currentChapterId;
16407
+ return /* @__PURE__ */ jsx(
16408
+ Button,
16409
+ {
16410
+ variant: "ghost",
16411
+ size: "sm",
16412
+ action: "BOOK_NAVIGATE",
16413
+ actionPayload: { chapterId: id },
16414
+ className: cn(
16415
+ "justify-start text-left w-full",
16416
+ direction === "rtl" && "text-right",
16417
+ isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16418
+ ),
16419
+ children: /* @__PURE__ */ jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: String(chapter.title ?? "") }) })
16420
+ },
16421
+ id
16422
+ );
16423
+ }) })
16424
+ ] }, partIdx);
16425
+ })
16335
16426
  ]
16336
16427
  }
16337
16428
  );
@@ -16453,27 +16544,41 @@ function resolveFieldMap(fieldMap) {
16453
16544
  function get(obj, key) {
16454
16545
  return obj[key];
16455
16546
  }
16547
+ function asStr(v) {
16548
+ return v == null ? "" : String(v);
16549
+ }
16456
16550
  function mapBookData(raw, fields = IDENTITY_BOOK_FIELDS) {
16457
16551
  const rawParts = get(raw, fields.parts) ?? [];
16458
- return {
16459
- title: get(raw, fields.title) ?? "",
16460
- subtitle: get(raw, fields.subtitle),
16461
- author: get(raw, fields.author),
16462
- coverImageUrl: get(raw, fields.coverImageUrl),
16463
- direction: get(raw, fields.direction) ?? void 0,
16464
- parts: rawParts.map((part) => {
16465
- const rawChapters = get(part, fields.chapters) ?? [];
16466
- return {
16467
- title: get(part, fields.partTitle) ?? "",
16468
- chapters: rawChapters.map((ch) => ({
16469
- id: get(ch, fields.chapterId) ?? "",
16470
- title: get(ch, fields.chapterTitle) ?? "",
16471
- content: get(ch, fields.chapterContent) ?? "",
16472
- orbitalSchema: get(ch, fields.chapterOrbitalSchema)
16473
- }))
16474
- };
16475
- })
16552
+ const direction = get(raw, fields.direction) ?? "ltr";
16553
+ const cover = {
16554
+ title: asStr(get(raw, fields.title)),
16555
+ subtitle: asStr(get(raw, fields.subtitle)),
16556
+ author: asStr(get(raw, fields.author)),
16557
+ coverImageUrl: asStr(get(raw, fields.coverImageUrl)),
16558
+ direction
16476
16559
  };
16560
+ const schemaByChapterId = {};
16561
+ const chapters = [];
16562
+ const parts = rawParts.map((part) => {
16563
+ const rawChapters = get(part, fields.chapters) ?? [];
16564
+ const chapterRows = rawChapters.map((ch) => {
16565
+ const id = asStr(get(ch, fields.chapterId));
16566
+ const schema = get(ch, fields.chapterOrbitalSchema);
16567
+ if (schema) schemaByChapterId[id] = schema;
16568
+ const row = {
16569
+ id,
16570
+ title: asStr(get(ch, fields.chapterTitle)),
16571
+ content: asStr(get(ch, fields.chapterContent))
16572
+ };
16573
+ chapters.push(row);
16574
+ return row;
16575
+ });
16576
+ return {
16577
+ title: asStr(get(part, fields.partTitle)),
16578
+ chapters: chapterRows
16579
+ };
16580
+ });
16581
+ return { cover, direction, parts, chapters, schemaByChapterId };
16477
16582
  }
16478
16583
  var IDENTITY_BOOK_FIELDS, AR_BOOK_FIELDS, FIELD_MAP_REGISTRY;
16479
16584
  var init_types2 = __esm({
@@ -16511,10 +16616,7 @@ var init_types2 = __esm({
16511
16616
  };
16512
16617
  }
16513
16618
  });
16514
- function flattenChapters(book) {
16515
- return book.parts.flatMap((part) => part.chapters);
16516
- }
16517
- var PRINT_STYLES, BookViewer;
16619
+ var chapterId, chapterTitle, PRINT_STYLES, BookViewer;
16518
16620
  var init_BookViewer = __esm({
16519
16621
  "components/marketing/organisms/book/BookViewer.tsx"() {
16520
16622
  init_Box();
@@ -16527,6 +16629,8 @@ var init_BookViewer = __esm({
16527
16629
  init_BookNavBar();
16528
16630
  init_EmptyState();
16529
16631
  init_types2();
16632
+ chapterId = (ch) => ch?.id == null ? void 0 : String(ch.id);
16633
+ chapterTitle = (ch) => ch?.title == null ? void 0 : String(ch.title);
16530
16634
  PRINT_STYLES = `
16531
16635
  @media print {
16532
16636
  .book-viewer-page {
@@ -16555,14 +16659,14 @@ var init_BookViewer = __esm({
16555
16659
  return mapBookData(raw, resolvedFieldMap);
16556
16660
  }, [entity, resolvedFieldMap]);
16557
16661
  const direction = book?.direction ?? "ltr";
16558
- const chapters = useMemo(() => book ? flattenChapters(book) : [], [book]);
16662
+ const chapters = useMemo(() => book ? book.chapters : [], [book]);
16559
16663
  const totalPages = 2 + chapters.length;
16560
16664
  const navigateTo = useCallback(
16561
16665
  (page) => {
16562
16666
  const clamped = Math.max(0, Math.min(page, totalPages - 1));
16563
16667
  setCurrentPage(clamped);
16564
- const chapterId = clamped >= 2 ? chapters[clamped - 2]?.id : void 0;
16565
- eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId });
16668
+ const id = clamped >= 2 ? chapterId(chapters[clamped - 2]) : void 0;
16669
+ eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId: id });
16566
16670
  },
16567
16671
  [totalPages, chapters, eventBus]
16568
16672
  );
@@ -16574,8 +16678,8 @@ var init_BookViewer = __esm({
16574
16678
  eventBus.on("UI:BOOK_PAGE_NEXT", () => navigateTo(currentPage + 1)),
16575
16679
  eventBus.on("UI:BOOK_PRINT", () => window.print()),
16576
16680
  eventBus.on("UI:BOOK_NAVIGATE", (event) => {
16577
- const chapterId = event.payload?.chapterId;
16578
- const idx = chapters.findIndex((ch) => ch.id === chapterId);
16681
+ const targetId = event.payload?.chapterId;
16682
+ const idx = chapters.findIndex((ch) => chapterId(ch) === targetId);
16579
16683
  if (idx >= 0) navigateTo(idx + 2);
16580
16684
  })
16581
16685
  ];
@@ -16592,9 +16696,11 @@ var init_BookViewer = __esm({
16592
16696
  style.remove();
16593
16697
  };
16594
16698
  }, []);
16595
- const currentChapterId = currentPage >= 2 ? chapters[currentPage - 2]?.id : void 0;
16596
- const currentChapterTitle = currentPage >= 2 ? chapters[currentPage - 2]?.title : void 0;
16699
+ const currentChapterId = currentPage >= 2 ? chapterId(chapters[currentPage - 2]) : void 0;
16700
+ const currentChapterTitle = currentPage >= 2 ? chapterTitle(chapters[currentPage - 2]) : void 0;
16597
16701
  if (!book) return /* @__PURE__ */ jsx(EmptyState, { message: t("book.noData") });
16702
+ const cover = book.cover;
16703
+ const coverTitle = String(cover.title ?? "");
16598
16704
  return /* @__PURE__ */ jsxs(VStack, { className: cn("relative h-full overflow-hidden bg-background", className), children: [
16599
16705
  /* @__PURE__ */ jsxs(
16600
16706
  Box,
@@ -16606,10 +16712,10 @@ var init_BookViewer = __esm({
16606
16712
  /* @__PURE__ */ jsx(
16607
16713
  BookCoverPage,
16608
16714
  {
16609
- title: book.title,
16610
- subtitle: book.subtitle,
16611
- author: book.author,
16612
- coverImageUrl: book.coverImageUrl,
16715
+ title: coverTitle,
16716
+ subtitle: String(cover.subtitle ?? "") || void 0,
16717
+ author: String(cover.author ?? "") || void 0,
16718
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16613
16719
  direction
16614
16720
  }
16615
16721
  ),
@@ -16620,23 +16726,27 @@ var init_BookViewer = __esm({
16620
16726
  direction
16621
16727
  }
16622
16728
  ),
16623
- chapters.map((chapter) => /* @__PURE__ */ jsx(
16624
- BookChapterView,
16625
- {
16626
- chapter,
16627
- direction
16628
- },
16629
- chapter.id
16630
- ))
16729
+ chapters.map((chapter) => {
16730
+ const id = chapterId(chapter);
16731
+ return /* @__PURE__ */ jsx(
16732
+ BookChapterView,
16733
+ {
16734
+ chapter,
16735
+ orbitalSchema: id ? book.schemaByChapterId[id] : void 0,
16736
+ direction
16737
+ },
16738
+ id
16739
+ );
16740
+ })
16631
16741
  ] }),
16632
16742
  /* @__PURE__ */ jsxs(Box, { className: "print:hidden", children: [
16633
16743
  currentPage === 0 && /* @__PURE__ */ jsx(
16634
16744
  BookCoverPage,
16635
16745
  {
16636
- title: book.title,
16637
- subtitle: book.subtitle,
16638
- author: book.author,
16639
- coverImageUrl: book.coverImageUrl,
16746
+ title: coverTitle,
16747
+ subtitle: String(cover.subtitle ?? "") || void 0,
16748
+ author: String(cover.author ?? "") || void 0,
16749
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16640
16750
  direction
16641
16751
  }
16642
16752
  ),
@@ -16652,6 +16762,7 @@ var init_BookViewer = __esm({
16652
16762
  BookChapterView,
16653
16763
  {
16654
16764
  chapter: chapters[currentPage - 2],
16765
+ orbitalSchema: currentChapterId ? book.schemaByChapterId[currentChapterId] : void 0,
16655
16766
  direction
16656
16767
  }
16657
16768
  )
@@ -16664,7 +16775,7 @@ var init_BookViewer = __esm({
16664
16775
  {
16665
16776
  currentPage,
16666
16777
  totalPages,
16667
- chapterTitle: currentPage === 0 ? book.title : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16778
+ chapterTitle: currentPage === 0 ? coverTitle : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16668
16779
  direction
16669
16780
  }
16670
16781
  )
@@ -16762,7 +16873,7 @@ var init_Grid = __esm({
16762
16873
  };
16763
16874
  Grid = ({
16764
16875
  cols = 1,
16765
- rows,
16876
+ rows: rows2,
16766
16877
  gap = "md",
16767
16878
  rowGap,
16768
16879
  colGap,
@@ -16774,7 +16885,7 @@ var init_Grid = __esm({
16774
16885
  children,
16775
16886
  as: Component = "div"
16776
16887
  }) => {
16777
- const mergedStyle = rows ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, ...style } : style;
16888
+ const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
16778
16889
  return React85__default.createElement(
16779
16890
  Component,
16780
16891
  {
@@ -17490,14 +17601,14 @@ function BuilderBoard({
17490
17601
  }) {
17491
17602
  const { emit } = useEventBus();
17492
17603
  const { t } = useTranslate();
17493
- const resolved = Array.isArray(entity) ? entity[0] : entity;
17604
+ const resolved = boardEntity(entity);
17494
17605
  const [placements, setPlacements] = useState({});
17495
17606
  const [headerError, setHeaderError] = useState(false);
17496
17607
  const [submitted, setSubmitted] = useState(false);
17497
17608
  const [attempts, setAttempts] = useState(0);
17498
17609
  const [showHint, setShowHint] = useState(false);
17499
- const components = resolved?.components ?? [];
17500
- const slots = resolved?.slots ?? [];
17610
+ const components = Array.isArray(resolved?.components) ? resolved.components : [];
17611
+ const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
17501
17612
  const usedComponentIds = new Set(Object.values(placements));
17502
17613
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
17503
17614
  const [selectedComponent, setSelectedComponent] = useState(null);
@@ -17531,7 +17642,7 @@ function BuilderBoard({
17531
17642
  }, [slots, placements, attempts, completeEvent, emit]);
17532
17643
  const handleReset = () => {
17533
17644
  setSubmitted(false);
17534
- if (attempts >= 2 && resolved?.hint) {
17645
+ if (attempts >= 2 && str(resolved?.hint)) {
17535
17646
  setShowHint(true);
17536
17647
  }
17537
17648
  };
@@ -17544,20 +17655,24 @@ function BuilderBoard({
17544
17655
  };
17545
17656
  const getComponentById = (id) => components.find((c) => c.id === id);
17546
17657
  if (!resolved) return null;
17658
+ const theme = resolved.theme ?? void 0;
17659
+ const themeBackground = theme?.background;
17660
+ const headerImage = str(resolved.headerImage);
17661
+ const hint = str(resolved.hint);
17547
17662
  return /* @__PURE__ */ jsx(
17548
17663
  Box,
17549
17664
  {
17550
17665
  className,
17551
17666
  style: {
17552
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
17667
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
17553
17668
  backgroundSize: "cover",
17554
17669
  backgroundPosition: "center"
17555
17670
  },
17556
17671
  children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
17557
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
17672
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
17558
17673
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
17559
- /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
17560
- /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.description })
17674
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
17675
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) })
17561
17676
  ] }) }),
17562
17677
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
17563
17678
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.components") }),
@@ -17617,9 +17732,9 @@ function BuilderBoard({
17617
17732
  ] }) }),
17618
17733
  submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
17619
17734
  /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
17620
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("builder.success") : resolved.failMessage ?? t("builder.incorrect") })
17735
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
17621
17736
  ] }) }),
17622
- showHint && resolved.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.hint }) }),
17737
+ showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
17623
17738
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
17624
17739
  !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17625
17740
  /* @__PURE__ */ jsx(Icon, { icon: Wrench, size: "sm" }),
@@ -17638,6 +17753,7 @@ var init_BuilderBoard = __esm({
17638
17753
  "components/game/organisms/puzzles/builder/BuilderBoard.tsx"() {
17639
17754
  init_atoms2();
17640
17755
  init_useEventBus();
17756
+ init_boardEntity();
17641
17757
  BuilderBoard.displayName = "BuilderBoard";
17642
17758
  }
17643
17759
  });
@@ -17975,21 +18091,24 @@ function CalendarGrid({
17975
18091
  eventBus.emit(`UI:${longPressEvent}`, { date: day.toISOString(), time, ...longPressPayload });
17976
18092
  }, 500);
17977
18093
  }, [longPressEvent, longPressPayload, eventBus]);
17978
- const renderEvent = (event) => /* @__PURE__ */ jsx(
17979
- Box,
17980
- {
17981
- rounded: "md",
17982
- padding: "xs",
17983
- border: true,
17984
- className: cn(
17985
- "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
17986
- event.color ? event.color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
17987
- ),
17988
- onClick: (e) => handleEventClick(event, e),
17989
- children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
17990
- },
17991
- event.id
17992
- );
18094
+ const renderEvent = (event) => {
18095
+ const color = event.color;
18096
+ return /* @__PURE__ */ jsx(
18097
+ Box,
18098
+ {
18099
+ rounded: "md",
18100
+ padding: "xs",
18101
+ border: true,
18102
+ className: cn(
18103
+ "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
18104
+ color ? color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
18105
+ ),
18106
+ onClick: (e) => handleEventClick(event, e),
18107
+ children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
18108
+ },
18109
+ event.id
18110
+ );
18111
+ };
17993
18112
  return /* @__PURE__ */ jsxs(
17994
18113
  Box,
17995
18114
  {
@@ -19653,7 +19772,6 @@ var init_CardGrid = __esm({
19653
19772
  alignItems = "stretch",
19654
19773
  className,
19655
19774
  children,
19656
- // EntityDisplayProps
19657
19775
  entity,
19658
19776
  isLoading = false,
19659
19777
  error = null,
@@ -20125,14 +20243,14 @@ var init_CaseStudyOrganism = __esm({
20125
20243
  /* @__PURE__ */ jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((study) => /* @__PURE__ */ jsx(
20126
20244
  CaseStudyCard,
20127
20245
  {
20128
- title: study.title,
20129
- description: study.description,
20130
- category: study.category,
20131
- categoryColor: study.categoryColor,
20132
- href: study.href,
20133
- linkLabel: study.linkLabel
20246
+ title: String(study.title ?? ""),
20247
+ description: String(study.description ?? ""),
20248
+ category: String(study.category ?? ""),
20249
+ categoryColor: study.categoryColor != null ? String(study.categoryColor) : void 0,
20250
+ href: String(study.href ?? ""),
20251
+ linkLabel: study.linkLabel != null ? String(study.linkLabel) : void 0
20134
20252
  },
20135
- study.id
20253
+ String(study.id ?? "")
20136
20254
  )) })
20137
20255
  ] });
20138
20256
  };
@@ -20155,10 +20273,10 @@ function CastleBoard({
20155
20273
  className
20156
20274
  }) {
20157
20275
  const eventBus = useEventBus();
20158
- const resolved = Array.isArray(entity) ? entity[0] : entity;
20159
- const tiles = resolved?.tiles ?? [];
20160
- const features = resolved?.features ?? [];
20161
- const units = resolved?.units ?? [];
20276
+ const resolved = boardEntity(entity);
20277
+ const tiles = Array.isArray(resolved?.tiles) ? resolved.tiles : [];
20278
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
20279
+ const units = Array.isArray(resolved?.units) ? resolved.units : [];
20162
20280
  const assetManifest = resolved?.assetManifest;
20163
20281
  const backgroundImage = resolved?.backgroundImage;
20164
20282
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -20186,7 +20304,7 @@ function CastleBoard({
20186
20304
  onFeatureClick?.(feature);
20187
20305
  if (featureClickEvent) {
20188
20306
  eventBus.emit(`UI:${featureClickEvent}`, {
20189
- featureId: feature.id,
20307
+ featureId: feature.id ?? "",
20190
20308
  featureType: feature.type,
20191
20309
  x: feature.x,
20192
20310
  y: feature.y
@@ -20254,6 +20372,7 @@ var init_CastleBoard = __esm({
20254
20372
  init_cn();
20255
20373
  init_useEventBus();
20256
20374
  init_IsometricCanvas2();
20375
+ init_boardEntity();
20257
20376
  init_isometric();
20258
20377
  CastleBoard.displayName = "CastleBoard";
20259
20378
  }
@@ -21064,14 +21183,14 @@ function ClassifierBoard({
21064
21183
  }) {
21065
21184
  const { emit } = useEventBus();
21066
21185
  const { t } = useTranslate();
21067
- const resolved = Array.isArray(entity) ? entity[0] : entity;
21186
+ const resolved = boardEntity(entity);
21068
21187
  const [assignments, setAssignments] = useState({});
21069
21188
  const [headerError, setHeaderError] = useState(false);
21070
21189
  const [submitted, setSubmitted] = useState(false);
21071
21190
  const [attempts, setAttempts] = useState(0);
21072
21191
  const [showHint, setShowHint] = useState(false);
21073
- const items = resolved?.items ?? [];
21074
- const categories = resolved?.categories ?? [];
21192
+ const items = Array.isArray(resolved?.items) ? resolved.items : [];
21193
+ const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21075
21194
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21076
21195
  const allAssigned = Object.keys(assignments).length === items.length;
21077
21196
  const results = submitted ? items.map((item) => ({
@@ -21103,7 +21222,7 @@ function ClassifierBoard({
21103
21222
  }, [items, assignments, attempts, completeEvent, emit]);
21104
21223
  const handleReset = () => {
21105
21224
  setSubmitted(false);
21106
- if (attempts >= 2 && resolved?.hint) {
21225
+ if (attempts >= 2 && str(resolved?.hint)) {
21107
21226
  setShowHint(true);
21108
21227
  }
21109
21228
  };
@@ -21114,20 +21233,25 @@ function ClassifierBoard({
21114
21233
  setShowHint(false);
21115
21234
  };
21116
21235
  if (!resolved) return null;
21236
+ const theme = resolved.theme ?? void 0;
21237
+ const themeBackground = theme?.background;
21238
+ const headerImage = str(resolved.headerImage);
21239
+ const hint = str(resolved.hint);
21240
+ const failMessage = str(resolved.failMessage);
21117
21241
  return /* @__PURE__ */ jsx(
21118
21242
  Box,
21119
21243
  {
21120
21244
  className,
21121
21245
  style: {
21122
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
21246
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
21123
21247
  backgroundSize: "cover",
21124
21248
  backgroundPosition: "center"
21125
21249
  },
21126
21250
  children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
21127
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
21251
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
21128
21252
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21129
- /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
21130
- /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.description })
21253
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
21254
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) })
21131
21255
  ] }) }),
21132
21256
  unassignedItems.length > 0 && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21133
21257
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("classifier.itemsToSort") }),
@@ -21179,10 +21303,10 @@ function ClassifierBoard({
21179
21303
  }) }),
21180
21304
  submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
21181
21305
  /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
21182
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
21183
- !allCorrect && resolved.failMessage && /* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-muted-foreground", children: resolved.failMessage })
21306
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
21307
+ !allCorrect && failMessage && /* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-muted-foreground", children: failMessage })
21184
21308
  ] }) }),
21185
- showHint && resolved.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.hint }) }),
21309
+ showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
21186
21310
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
21187
21311
  !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21188
21312
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
@@ -21201,6 +21325,7 @@ var init_ClassifierBoard = __esm({
21201
21325
  "components/game/organisms/puzzles/classifier/ClassifierBoard.tsx"() {
21202
21326
  init_atoms2();
21203
21327
  init_useEventBus();
21328
+ init_boardEntity();
21204
21329
  ClassifierBoard.displayName = "ClassifierBoard";
21205
21330
  }
21206
21331
  });
@@ -27571,7 +27696,7 @@ function InventoryPanel({
27571
27696
  const slotArray = Array.from({ length: safeSlots }, (_, index) => {
27572
27697
  return safeItems[index] ?? null;
27573
27698
  });
27574
- const rows = Math.ceil(safeSlots / safeColumns);
27699
+ const rows2 = Math.ceil(safeSlots / safeColumns);
27575
27700
  const handleSlotClick = useCallback((index) => {
27576
27701
  if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
27577
27702
  onSelectSlot?.(index);
@@ -27640,7 +27765,7 @@ function InventoryPanel({
27640
27765
  className: "grid gap-1 bg-[var(--color-card)] p-2 rounded-container border border-border",
27641
27766
  style: {
27642
27767
  gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
27643
- gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
27768
+ gridTemplateRows: `repeat(${rows2}, ${slotSize}px)`
27644
27769
  },
27645
27770
  children: slotArray.map((item, index) => /* @__PURE__ */ jsx(
27646
27771
  "button",
@@ -31604,11 +31729,11 @@ function LatticeSVG({
31604
31729
  }) {
31605
31730
  const paths = [];
31606
31731
  const cols = 5;
31607
- const rows = Math.ceil(h / (w / cols));
31732
+ const rows2 = Math.ceil(h / (w / cols));
31608
31733
  const cellW = w / cols;
31609
31734
  const cellH = cellW;
31610
31735
  const bulge = cellW * 0.3;
31611
- for (let row = 0; row < rows; row++) {
31736
+ for (let row = 0; row < rows2; row++) {
31612
31737
  for (let col = 0; col < cols; col++) {
31613
31738
  const cx = col * cellW + cellW / 2;
31614
31739
  const cy = row * cellH + cellH / 2;
@@ -32020,7 +32145,7 @@ var init_MatrixQuestion = __esm({
32020
32145
  };
32021
32146
  MatrixQuestion = ({
32022
32147
  title,
32023
- rows,
32148
+ rows: rows2,
32024
32149
  columns = DEFAULT_MATRIX_COLUMNS,
32025
32150
  values,
32026
32151
  onChange,
@@ -32030,7 +32155,7 @@ var init_MatrixQuestion = __esm({
32030
32155
  className
32031
32156
  }) => {
32032
32157
  const styles = sizeStyles13[size];
32033
- const safeRows = rows ?? [];
32158
+ const safeRows = rows2 ?? [];
32034
32159
  const safeValues = values ?? {};
32035
32160
  const eventBus = useEventBus();
32036
32161
  const handleChange = useCallback(
@@ -32658,7 +32783,8 @@ var init_PositionedCanvas = __esm({
32658
32783
  dragRef.current = null;
32659
32784
  setDraggingId(null);
32660
32785
  if (!wasDrag) {
32661
- const next = selectedId === item.id ? null : item.id;
32786
+ const itemId = item.id;
32787
+ const next = selectedId === itemId ? null : itemId;
32662
32788
  onSelect?.(next);
32663
32789
  if (selectEvent) {
32664
32790
  eventBus.emit(`UI:${selectEvent}`, { id: next });
@@ -32693,15 +32819,22 @@ var init_PositionedCanvas = __esm({
32693
32819
  style: { width, height },
32694
32820
  onClick: handleContainerClick,
32695
32821
  children: items.map((item) => {
32822
+ const itemId = item.id;
32823
+ const label = item.label;
32824
+ const x = item.x;
32825
+ const y = item.y;
32826
+ const capacity = item.capacity;
32827
+ const partySize = item.partySize;
32828
+ const serverName = item.serverName;
32696
32829
  const status = item.status ?? "empty";
32697
32830
  const shape = item.shape ?? "round";
32698
- const isSelected = selectedId === item.id;
32699
- const isDragging = draggingId === item.id;
32831
+ const isSelected = selectedId === itemId;
32832
+ const isDragging = draggingId === itemId;
32700
32833
  const statusBadge = STATUS_BADGE[status];
32701
32834
  return /* @__PURE__ */ jsxs(
32702
32835
  Box,
32703
32836
  {
32704
- "data-testid": `item-node-${item.id}`,
32837
+ "data-testid": `item-node-${itemId}`,
32705
32838
  "data-status": status,
32706
32839
  className: cn(
32707
32840
  "absolute flex flex-col items-center justify-center gap-1 border-2 select-none",
@@ -32712,7 +32845,7 @@ var init_PositionedCanvas = __esm({
32712
32845
  isSelected && "outline outline-2 outline-offset-2 outline-primary shadow-md",
32713
32846
  isDragging && "shadow-lg z-10"
32714
32847
  ),
32715
- style: { left: item.x, top: item.y, touchAction: "none" },
32848
+ style: { left: x, top: y, touchAction: "none" },
32716
32849
  onPointerDown: (e) => handlePointerDown(e, item),
32717
32850
  onPointerMove: handlePointerMove,
32718
32851
  onPointerUp: (e) => handlePointerUp(e, item),
@@ -32720,10 +32853,10 @@ var init_PositionedCanvas = __esm({
32720
32853
  children: [
32721
32854
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1", children: [
32722
32855
  getStatusIcon(status),
32723
- /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "semibold", children: item.label })
32856
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "semibold", children: label })
32724
32857
  ] }),
32725
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: item.partySize !== void 0 && status === "seated" ? `${item.partySize}/${item.capacity}` : `Cap ${item.capacity}` }),
32726
- status === "seated" && item.serverName && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: item.serverName }),
32858
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: partySize !== void 0 && status === "seated" ? `${partySize}/${capacity}` : `Cap ${capacity}` }),
32859
+ status === "seated" && serverName && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: serverName }),
32727
32860
  isSelected && /* @__PURE__ */ jsx(
32728
32861
  Badge,
32729
32862
  {
@@ -32735,7 +32868,7 @@ var init_PositionedCanvas = __esm({
32735
32868
  )
32736
32869
  ]
32737
32870
  },
32738
- item.id
32871
+ itemId
32739
32872
  );
32740
32873
  })
32741
32874
  }
@@ -33507,9 +33640,10 @@ var init_RichBlockEditor = __esm({
33507
33640
  });
33508
33641
  function collectInitiallyCollapsed(nodes, acc) {
33509
33642
  for (const n of nodes) {
33643
+ const replies = n.replies;
33510
33644
  if (n.collapsed) acc.add(n.id);
33511
- if (n.replies && n.replies.length > 0) {
33512
- collectInitiallyCollapsed(n.replies, acc);
33645
+ if (replies && replies.length > 0) {
33646
+ collectInitiallyCollapsed(replies, acc);
33513
33647
  }
33514
33648
  }
33515
33649
  }
@@ -33539,44 +33673,52 @@ var init_ReplyTree = __esm({
33539
33673
  }) => {
33540
33674
  const eventBus = useEventBus();
33541
33675
  const { t } = useTranslate();
33542
- const hasReplies = !!node.replies && node.replies.length > 0;
33543
- const isCollapsed = collapsedSet.has(node.id);
33676
+ const nodeId = node.id;
33677
+ const authorName = node.authorName;
33678
+ const authorAvatarUrl = node.authorAvatarUrl;
33679
+ const content = node.content;
33680
+ const postedAt = node.postedAt;
33681
+ const voteCount = node.voteCount;
33682
+ const userVote = node.userVote;
33683
+ const replies = node.replies;
33684
+ const hasReplies = !!replies && replies.length > 0;
33685
+ const isCollapsed = collapsedSet.has(nodeId);
33544
33686
  const atMaxDepth = depth >= maxDepth;
33545
33687
  const [replyOpen, setReplyOpen] = useState(false);
33546
33688
  const [draft, setDraft] = useState("");
33547
33689
  const handleVote = useCallback(
33548
33690
  (next) => {
33549
- onVote?.(node.id, next);
33550
- if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId: node.id, vote: next });
33691
+ onVote?.(nodeId, next);
33692
+ if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId, vote: next });
33551
33693
  },
33552
- [node.id, onVote, voteEvent, eventBus]
33694
+ [nodeId, onVote, voteEvent, eventBus]
33553
33695
  );
33554
33696
  const handleReply = useCallback(() => {
33555
- onReply?.(node.id);
33697
+ onReply?.(nodeId);
33556
33698
  setReplyOpen((open) => !open);
33557
- }, [node.id, onReply]);
33699
+ }, [nodeId, onReply]);
33558
33700
  const handleSubmitReply = useCallback(() => {
33559
- const content = draft.trim();
33560
- if (!content) return;
33561
- if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: node.id, content });
33701
+ const text = draft.trim();
33702
+ if (!text) return;
33703
+ if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: nodeId, content: text });
33562
33704
  setDraft("");
33563
33705
  setReplyOpen(false);
33564
- }, [node.id, draft, replyEvent, eventBus]);
33706
+ }, [nodeId, draft, replyEvent, eventBus]);
33565
33707
  const handleCancelReply = useCallback(() => {
33566
33708
  setDraft("");
33567
33709
  setReplyOpen(false);
33568
33710
  }, []);
33569
33711
  const handleFlag = useCallback(() => {
33570
- onFlag?.(node.id);
33571
- if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId: node.id });
33572
- }, [node.id, onFlag, flagEvent, eventBus]);
33712
+ onFlag?.(nodeId);
33713
+ if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId });
33714
+ }, [nodeId, onFlag, flagEvent, eventBus]);
33573
33715
  const handleContinue = useCallback(() => {
33574
- onContinueThread?.(node.id);
33575
- if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId: node.id });
33576
- }, [node.id, onContinueThread, continueThreadEvent, eventBus]);
33716
+ onContinueThread?.(nodeId);
33717
+ if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId });
33718
+ }, [nodeId, onContinueThread, continueThreadEvent, eventBus]);
33577
33719
  const handleToggle = useCallback(() => {
33578
- toggleCollapse(node.id);
33579
- }, [node.id, toggleCollapse]);
33720
+ toggleCollapse(nodeId);
33721
+ }, [nodeId, toggleCollapse]);
33580
33722
  return /* @__PURE__ */ jsxs(Box, { className: "flex flex-row gap-2 items-stretch min-w-0", children: [
33581
33723
  /* @__PURE__ */ jsxs(Box, { className: "flex flex-col items-center flex-shrink-0 w-6", children: [
33582
33724
  hasReplies ? /* @__PURE__ */ jsx(
@@ -33608,25 +33750,25 @@ var init_ReplyTree = __esm({
33608
33750
  /* @__PURE__ */ jsx(
33609
33751
  Avatar,
33610
33752
  {
33611
- src: node.authorAvatarUrl,
33612
- name: node.authorName,
33753
+ src: authorAvatarUrl,
33754
+ name: authorName,
33613
33755
  size: "sm"
33614
33756
  }
33615
33757
  ),
33616
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: node.authorName }),
33617
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: node.postedAt })
33758
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: authorName }),
33759
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: postedAt })
33618
33760
  ] }),
33619
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: node.content }),
33761
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: content }),
33620
33762
  showActions && /* @__PURE__ */ jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
33621
33763
  /* @__PURE__ */ jsx(
33622
33764
  VoteStack,
33623
33765
  {
33624
- count: node.voteCount ?? 0,
33625
- userVote: node.userVote ?? null,
33766
+ count: voteCount ?? 0,
33767
+ userVote: userVote ?? null,
33626
33768
  onVote: handleVote,
33627
33769
  size: "sm",
33628
33770
  variant: "horizontal",
33629
- label: t("replyTree.voteOnReplyBy", { author: node.authorName })
33771
+ label: t("replyTree.voteOnReplyBy", { author: authorName })
33630
33772
  }
33631
33773
  ),
33632
33774
  /* @__PURE__ */ jsx(
@@ -33636,7 +33778,7 @@ var init_ReplyTree = __esm({
33636
33778
  size: "sm",
33637
33779
  leftIcon: "message-square",
33638
33780
  onClick: handleReply,
33639
- "aria-label": t("replyTree.replyTo", { author: node.authorName }),
33781
+ "aria-label": t("replyTree.replyTo", { author: authorName }),
33640
33782
  children: t("replyTree.reply")
33641
33783
  }
33642
33784
  ),
@@ -33647,7 +33789,7 @@ var init_ReplyTree = __esm({
33647
33789
  size: "sm",
33648
33790
  leftIcon: "flag",
33649
33791
  onClick: handleFlag,
33650
- "aria-label": t("replyTree.flagReplyBy", { author: node.authorName }),
33792
+ "aria-label": t("replyTree.flagReplyBy", { author: authorName }),
33651
33793
  children: t("replyTree.flag")
33652
33794
  }
33653
33795
  )
@@ -33659,9 +33801,9 @@ var init_ReplyTree = __esm({
33659
33801
  inputType: "textarea",
33660
33802
  rows: 2,
33661
33803
  value: draft,
33662
- placeholder: t("replyTree.replyToPlaceholder", { author: node.authorName }),
33804
+ placeholder: t("replyTree.replyToPlaceholder", { author: authorName }),
33663
33805
  onChange: (e) => setDraft(e.target.value),
33664
- "aria-label": t("replyTree.replyTo", { author: node.authorName })
33806
+ "aria-label": t("replyTree.replyTo", { author: authorName })
33665
33807
  }
33666
33808
  ),
33667
33809
  /* @__PURE__ */ jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
@@ -33692,7 +33834,7 @@ var init_ReplyTree = __esm({
33692
33834
  ),
33693
33835
  children: t("replyTree.continueThread")
33694
33836
  }
33695
- ) : /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-2 mt-1", children: node.replies.map((child) => /* @__PURE__ */ jsx(
33837
+ ) : /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-2 mt-1", children: replies.map((child) => /* @__PURE__ */ jsx(
33696
33838
  ReplyTreeNode,
33697
33839
  {
33698
33840
  node: child,
@@ -36012,8 +36154,8 @@ var init_WizardContainer = __esm({
36012
36154
  return void 0;
36013
36155
  if (typeof controlledStep === "number") return controlledStep;
36014
36156
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
36015
- const num = Number(controlledStep);
36016
- return isNaN(num) ? void 0 : num;
36157
+ const num2 = Number(controlledStep);
36158
+ return isNaN(num2) ? void 0 : num2;
36017
36159
  })();
36018
36160
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
36019
36161
  const totalSteps = steps.length;
@@ -37718,7 +37860,7 @@ function DebuggerBoard({
37718
37860
  }) {
37719
37861
  const { emit } = useEventBus();
37720
37862
  const { t } = useTranslate();
37721
- const resolved = Array.isArray(entity) ? entity[0] : entity;
37863
+ const resolved = boardEntity(entity);
37722
37864
  const [flaggedLines, setFlaggedLines] = useState(/* @__PURE__ */ new Set());
37723
37865
  const [headerError, setHeaderError] = useState(false);
37724
37866
  const [submitted, setSubmitted] = useState(false);
@@ -37736,7 +37878,7 @@ function DebuggerBoard({
37736
37878
  return next;
37737
37879
  });
37738
37880
  };
37739
- const lines = resolved?.lines ?? [];
37881
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37740
37882
  const bugLines = lines.filter((l) => l.isBug);
37741
37883
  const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
37742
37884
  const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
@@ -37751,7 +37893,7 @@ function DebuggerBoard({
37751
37893
  }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37752
37894
  const handleReset = () => {
37753
37895
  setSubmitted(false);
37754
- if (attempts >= 2 && resolved?.hint) {
37896
+ if (attempts >= 2 && str(resolved?.hint)) {
37755
37897
  setShowHint(true);
37756
37898
  }
37757
37899
  };
@@ -37762,24 +37904,28 @@ function DebuggerBoard({
37762
37904
  setShowHint(false);
37763
37905
  };
37764
37906
  if (!resolved) return null;
37907
+ const theme = resolved.theme ?? void 0;
37908
+ const themeBackground = theme?.background;
37909
+ const headerImage = str(resolved.headerImage);
37910
+ const hint = str(resolved.hint);
37765
37911
  return /* @__PURE__ */ jsx(
37766
37912
  Box,
37767
37913
  {
37768
37914
  className,
37769
37915
  style: {
37770
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
37916
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
37771
37917
  backgroundSize: "cover",
37772
37918
  backgroundPosition: "center"
37773
37919
  },
37774
37920
  children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
37775
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
37921
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
37776
37922
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
37777
37923
  /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
37778
37924
  /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "sm" }),
37779
- /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title })
37925
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
37780
37926
  ] }),
37781
- /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.description }),
37782
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(resolved.bugCount) }) })
37927
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
37928
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
37783
37929
  ] }) }),
37784
37930
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
37785
37931
  const isFlagged = flaggedLines.has(line.id);
@@ -37811,7 +37957,7 @@ function DebuggerBoard({
37811
37957
  );
37812
37958
  }) }) }),
37813
37959
  submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
37814
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
37960
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
37815
37961
  bugLines.map((line) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "start", children: [
37816
37962
  /* @__PURE__ */ jsx(
37817
37963
  Icon,
@@ -37827,7 +37973,7 @@ function DebuggerBoard({
37827
37973
  ] })
37828
37974
  ] }, line.id))
37829
37975
  ] }) }),
37830
- showHint && resolved.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.hint }) }),
37976
+ showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
37831
37977
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
37832
37978
  !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
37833
37979
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
@@ -37846,6 +37992,7 @@ var init_DebuggerBoard = __esm({
37846
37992
  "components/game/organisms/puzzles/debugger/DebuggerBoard.tsx"() {
37847
37993
  init_atoms2();
37848
37994
  init_useEventBus();
37995
+ init_boardEntity();
37849
37996
  DebuggerBoard.displayName = "DebuggerBoard";
37850
37997
  }
37851
37998
  });
@@ -37883,7 +38030,7 @@ function getBadgeVariant(fieldName, value) {
37883
38030
  return "default";
37884
38031
  }
37885
38032
  function formatFieldLabel(fieldName) {
37886
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
38033
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase());
37887
38034
  }
37888
38035
  function formatFieldValue(value, fieldName) {
37889
38036
  if (typeof value === "number") {
@@ -37902,26 +38049,26 @@ function formatFieldValue(value, fieldName) {
37902
38049
  }
37903
38050
  function renderRichFieldValue(value, fieldName, fieldType) {
37904
38051
  if (value === void 0 || value === null) return "\u2014";
37905
- const str = String(value);
38052
+ const str2 = String(value);
37906
38053
  switch (fieldType) {
37907
38054
  case "image":
37908
38055
  case "url": {
37909
- if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
38056
+ if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
37910
38057
  return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
37911
38058
  "img",
37912
38059
  {
37913
- src: str,
38060
+ src: str2,
37914
38061
  alt: formatFieldLabel(fieldName),
37915
38062
  className: "max-w-full max-h-64 rounded-md object-contain",
37916
38063
  loading: "lazy"
37917
38064
  }
37918
38065
  ) });
37919
38066
  }
37920
- return str;
38067
+ return str2;
37921
38068
  }
37922
38069
  case "markdown":
37923
38070
  case "richtext":
37924
- return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Typography, { variant: "body", className: "break-words", children: str }), children: /* @__PURE__ */ jsx(
38071
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Typography, { variant: "body", className: "break-words", children: str2 }), children: /* @__PURE__ */ jsx(
37925
38072
  Box,
37926
38073
  {
37927
38074
  className: "prose prose-sm max-w-none",
@@ -37939,11 +38086,11 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37939
38086
  "--tw-prose-th-borders": "var(--color-border)",
37940
38087
  "--tw-prose-td-borders": "var(--color-border)"
37941
38088
  },
37942
- children: /* @__PURE__ */ jsx(ReactMarkdown2, { children: str })
38089
+ children: /* @__PURE__ */ jsx(ReactMarkdown2, { children: str2 })
37943
38090
  }
37944
38091
  ) });
37945
38092
  case "code":
37946
- return /* @__PURE__ */ jsx(Box, { className: "mt-1 rounded-md bg-muted p-3 overflow-x-auto", children: /* @__PURE__ */ jsx("pre", { className: "text-sm font-mono whitespace-pre-wrap break-words m-0", children: /* @__PURE__ */ jsx("code", { children: str }) }) });
38093
+ return /* @__PURE__ */ jsx(Box, { className: "mt-1 rounded-md bg-muted p-3 overflow-x-auto", children: /* @__PURE__ */ jsx("pre", { className: "text-sm font-mono whitespace-pre-wrap break-words m-0", children: /* @__PURE__ */ jsx("code", { children: str2 }) }) });
37947
38094
  case "html":
37948
38095
  return /* @__PURE__ */ jsx(
37949
38096
  Box,
@@ -37963,12 +38110,12 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37963
38110
  "--tw-prose-th-borders": "var(--color-border)",
37964
38111
  "--tw-prose-td-borders": "var(--color-border)"
37965
38112
  },
37966
- children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: str })
38113
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: str2 })
37967
38114
  }
37968
38115
  );
37969
38116
  case "date":
37970
38117
  case "datetime": {
37971
- const d = new Date(str);
38118
+ const d = new Date(str2);
37972
38119
  if (!isNaN(d.getTime())) {
37973
38120
  return d.toLocaleDateString(void 0, {
37974
38121
  year: "numeric",
@@ -37977,7 +38124,7 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37977
38124
  ...fieldType === "datetime" ? { hour: "2-digit", minute: "2-digit" } : {}
37978
38125
  });
37979
38126
  }
37980
- return str;
38127
+ return str2;
37981
38128
  }
37982
38129
  default:
37983
38130
  return formatFieldValue(value, fieldName);
@@ -38655,6 +38802,40 @@ var init_RuleEditor = __esm({
38655
38802
  RuleEditor.displayName = "RuleEditor";
38656
38803
  }
38657
38804
  });
38805
+
38806
+ // components/game/organisms/puzzles/event-handler/puzzleObject.ts
38807
+ function objId(o) {
38808
+ return o.id == null ? "" : String(o.id);
38809
+ }
38810
+ function objName(o) {
38811
+ return o.name == null ? "" : String(o.name);
38812
+ }
38813
+ function objIcon(o) {
38814
+ return o.icon == null ? "" : String(o.icon);
38815
+ }
38816
+ function objStates(o) {
38817
+ return Array.isArray(o.states) ? o.states : [];
38818
+ }
38819
+ function objCurrentState(o) {
38820
+ return o.currentState == null ? "" : String(o.currentState);
38821
+ }
38822
+ function objAvailableEvents(o) {
38823
+ return Array.isArray(o.availableEvents) ? o.availableEvents : [];
38824
+ }
38825
+ function objAvailableActions(o) {
38826
+ return Array.isArray(o.availableActions) ? o.availableActions : [];
38827
+ }
38828
+ function objRules(o) {
38829
+ return Array.isArray(o.rules) ? o.rules : [];
38830
+ }
38831
+ function objMaxRules(o) {
38832
+ const n = Number(o.maxRules);
38833
+ return Number.isFinite(n) && n > 0 ? n : 3;
38834
+ }
38835
+ var init_puzzleObject = __esm({
38836
+ "components/game/organisms/puzzles/event-handler/puzzleObject.ts"() {
38837
+ }
38838
+ });
38658
38839
  function ObjectRulePanel({
38659
38840
  object,
38660
38841
  onRulesChange,
@@ -38662,55 +38843,63 @@ function ObjectRulePanel({
38662
38843
  className
38663
38844
  }) {
38664
38845
  const { t } = useTranslate();
38665
- const maxRules = object.maxRules || 3;
38666
- const canAdd = object.rules.length < maxRules;
38846
+ const id = objId(object);
38847
+ const name = objName(object);
38848
+ const icon = objIcon(object);
38849
+ const states = objStates(object);
38850
+ const currentState = objCurrentState(object);
38851
+ const availableEvents = objAvailableEvents(object);
38852
+ const availableActions = objAvailableActions(object);
38853
+ const rules = objRules(object);
38854
+ const maxRules = objMaxRules(object);
38855
+ const canAdd = rules.length < maxRules;
38667
38856
  const handleRuleChange = useCallback((index, updatedRule) => {
38668
- const newRules = [...object.rules];
38857
+ const newRules = [...rules];
38669
38858
  newRules[index] = updatedRule;
38670
- onRulesChange(object.id, newRules);
38671
- }, [object.id, object.rules, onRulesChange]);
38859
+ onRulesChange(id, newRules);
38860
+ }, [id, rules, onRulesChange]);
38672
38861
  const handleRuleRemove = useCallback((index) => {
38673
- const newRules = object.rules.filter((_, i) => i !== index);
38674
- onRulesChange(object.id, newRules);
38675
- }, [object.id, object.rules, onRulesChange]);
38862
+ const newRules = rules.filter((_, i) => i !== index);
38863
+ onRulesChange(id, newRules);
38864
+ }, [id, rules, onRulesChange]);
38676
38865
  const handleAddRule = useCallback(() => {
38677
38866
  if (!canAdd || disabled) return;
38678
- const firstEvent = object.availableEvents[0]?.value || "";
38679
- const firstAction = object.availableActions[0]?.value || "";
38867
+ const firstEvent = availableEvents[0]?.value || "";
38868
+ const firstAction = availableActions[0]?.value || "";
38680
38869
  const newRule = {
38681
38870
  id: `rule-${nextRuleId++}`,
38682
38871
  whenEvent: firstEvent,
38683
38872
  thenAction: firstAction
38684
38873
  };
38685
- onRulesChange(object.id, [...object.rules, newRule]);
38686
- }, [canAdd, disabled, object, onRulesChange]);
38874
+ onRulesChange(id, [...rules, newRule]);
38875
+ }, [canAdd, disabled, id, rules, availableEvents, availableActions, onRulesChange]);
38687
38876
  const machine = {
38688
- name: object.name,
38689
- states: object.states,
38690
- currentState: object.currentState,
38691
- transitions: object.rules.map((r) => ({
38692
- from: object.currentState,
38693
- to: object.states.find((s) => s !== object.currentState) || object.currentState,
38877
+ name,
38878
+ states,
38879
+ currentState,
38880
+ transitions: rules.map((r) => ({
38881
+ from: currentState,
38882
+ to: states.find((s) => s !== currentState) || currentState,
38694
38883
  event: r.whenEvent
38695
38884
  }))
38696
38885
  };
38697
38886
  return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
38698
38887
  /* @__PURE__ */ jsxs(HStack, { className: "items-center", gap: "sm", children: [
38699
- /* @__PURE__ */ jsx(Typography, { variant: "h5", children: object.icon }),
38888
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", children: icon }),
38700
38889
  /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
38701
- /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
38702
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + object.currentState })
38890
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: name }),
38891
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + currentState })
38703
38892
  ] })
38704
38893
  ] }),
38705
38894
  /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
38706
38895
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
38707
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
38708
- object.rules.map((rule, i) => /* @__PURE__ */ jsx(
38896
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: rules.length, max: maxRules }) + ":" }),
38897
+ rules.map((rule, i) => /* @__PURE__ */ jsx(
38709
38898
  RuleEditor,
38710
38899
  {
38711
38900
  rule,
38712
- availableEvents: object.availableEvents,
38713
- availableActions: object.availableActions,
38901
+ availableEvents,
38902
+ availableActions,
38714
38903
  onChange: (r) => handleRuleChange(i, r),
38715
38904
  onRemove: () => handleRuleRemove(i),
38716
38905
  disabled
@@ -38728,6 +38917,7 @@ var init_ObjectRulePanel = __esm({
38728
38917
  init_cn();
38729
38918
  init_TraitStateViewer();
38730
38919
  init_RuleEditor();
38920
+ init_puzzleObject();
38731
38921
  nextRuleId = 1;
38732
38922
  ObjectRulePanel.displayName = "ObjectRulePanel";
38733
38923
  }
@@ -38794,11 +38984,11 @@ function EventHandlerBoard({
38794
38984
  }) {
38795
38985
  const { emit } = useEventBus();
38796
38986
  const { t } = useTranslate();
38797
- const resolved = Array.isArray(entity) ? entity[0] : entity;
38798
- const entityObjects = resolved?.objects ?? [];
38799
- const [objects, setObjects] = useState(entityObjects);
38987
+ const resolved = boardEntity(entity);
38988
+ const entityObjects = rows(resolved?.objects);
38989
+ const [objects, setObjects] = useState(() => [...entityObjects]);
38800
38990
  const [selectedObjectId, setSelectedObjectId] = useState(
38801
- entityObjects[0]?.id || null
38991
+ entityObjects[0] ? objId(entityObjects[0]) : null
38802
38992
  );
38803
38993
  const [headerError, setHeaderError] = useState(false);
38804
38994
  const [playState, setPlayState] = useState("editing");
@@ -38809,10 +38999,10 @@ function EventHandlerBoard({
38809
38999
  useEffect(() => () => {
38810
39000
  if (timerRef.current) clearTimeout(timerRef.current);
38811
39001
  }, []);
38812
- const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
39002
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
38813
39003
  const handleRulesChange = useCallback((objectId, rules) => {
38814
39004
  setObjects((prev) => prev.map(
38815
- (o) => o.id === objectId ? { ...o, rules } : o
39005
+ (o) => objId(o) === objectId ? { ...o, rules } : o
38816
39006
  ));
38817
39007
  }, []);
38818
39008
  const addLogEntry = useCallback((icon, message, status = "done") => {
@@ -38826,11 +39016,12 @@ function EventHandlerBoard({
38826
39016
  setEventLog([]);
38827
39017
  const allRules = [];
38828
39018
  objects.forEach((obj) => {
38829
- obj.rules.forEach((rule) => {
39019
+ objRules(obj).forEach((rule) => {
38830
39020
  allRules.push({ object: obj, rule });
38831
39021
  });
38832
39022
  });
38833
- const triggers = resolved?.triggerEvents || [];
39023
+ const triggers = Array.isArray(resolved?.triggerEvents) ? resolved.triggerEvents : [];
39024
+ const goalEvent = str(resolved?.goalEvent);
38834
39025
  const eventQueue = [...triggers];
38835
39026
  const firedEvents = /* @__PURE__ */ new Set();
38836
39027
  let stepIdx = 0;
@@ -38859,14 +39050,14 @@ function EventHandlerBoard({
38859
39050
  addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
38860
39051
  } else {
38861
39052
  matching.forEach(({ object, rule }) => {
38862
- addLogEntry(object.icon, t("eventHandler.heardEvent", { object: object.name, event: currentEvent, action: rule.thenAction }), "done");
39053
+ addLogEntry(objIcon(object), t("eventHandler.heardEvent", { object: objName(object), event: currentEvent, action: rule.thenAction }), "done");
38863
39054
  eventQueue.push(rule.thenAction);
38864
- if (rule.thenAction === resolved?.goalEvent) {
39055
+ if (rule.thenAction === goalEvent) {
38865
39056
  goalReached = true;
38866
39057
  }
38867
39058
  });
38868
39059
  }
38869
- if (currentEvent === resolved?.goalEvent) {
39060
+ if (currentEvent === goalEvent) {
38870
39061
  goalReached = true;
38871
39062
  }
38872
39063
  stepIdx++;
@@ -38884,65 +39075,75 @@ function EventHandlerBoard({
38884
39075
  }, []);
38885
39076
  const handleReset = useCallback(() => {
38886
39077
  if (timerRef.current) clearTimeout(timerRef.current);
38887
- setObjects(resolved?.objects ?? []);
39078
+ const resetObjects = rows(resolved?.objects);
39079
+ setObjects([...resetObjects]);
38888
39080
  setPlayState("editing");
38889
39081
  setEventLog([]);
38890
- setSelectedObjectId((resolved?.objects ?? [])[0]?.id || null);
39082
+ setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
38891
39083
  setAttempts(0);
38892
39084
  }, [resolved?.objects]);
38893
39085
  if (!resolved) return null;
38894
39086
  const objectViewers = objects.map((obj) => {
39087
+ const states = objStates(obj);
39088
+ const currentState = objCurrentState(obj);
38895
39089
  const machine = {
38896
- name: obj.name,
38897
- states: obj.states,
38898
- currentState: obj.currentState,
38899
- transitions: obj.rules.map((r) => ({
38900
- from: obj.currentState,
38901
- to: obj.states.find((s) => s !== obj.currentState) || obj.currentState,
39090
+ name: objName(obj),
39091
+ states,
39092
+ currentState,
39093
+ transitions: objRules(obj).map((r) => ({
39094
+ from: currentState,
39095
+ to: states.find((s) => s !== currentState) || currentState,
38902
39096
  event: r.whenEvent
38903
39097
  }))
38904
39098
  };
38905
39099
  return { obj, machine };
38906
39100
  });
38907
- const showHint = attempts >= 3 && resolved.hint;
39101
+ const hint = str(resolved.hint);
39102
+ const showHint = attempts >= 3 && hint;
39103
+ const theme = resolved.theme ?? void 0;
39104
+ const themeBackground = theme?.background;
39105
+ const headerImage = str(resolved.headerImage);
38908
39106
  const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
38909
39107
  return /* @__PURE__ */ jsxs(
38910
39108
  VStack,
38911
39109
  {
38912
39110
  className: cn("p-4 gap-6", className),
38913
39111
  style: {
38914
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
39112
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
38915
39113
  backgroundSize: "cover",
38916
39114
  backgroundPosition: "center"
38917
39115
  },
38918
39116
  children: [
38919
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
39117
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
38920
39118
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
38921
- /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
38922
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
39119
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
39120
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
38923
39121
  /* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
38924
39122
  /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
38925
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.goalCondition })
39123
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: str(resolved.goalCondition) })
38926
39124
  ] })
38927
39125
  ] }),
38928
39126
  /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
38929
39127
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
38930
- /* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsx(
38931
- Box,
38932
- {
38933
- className: cn(
38934
- "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
38935
- selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
38936
- ),
38937
- onClick: () => setSelectedObjectId(obj.id),
38938
- children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
38939
- /* @__PURE__ */ jsx(Typography, { variant: "h5", children: obj.icon }),
38940
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
38941
- /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
38942
- ] })
38943
- },
38944
- obj.id
38945
- )) })
39128
+ /* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => {
39129
+ const oid = objId(obj);
39130
+ return /* @__PURE__ */ jsx(
39131
+ Box,
39132
+ {
39133
+ className: cn(
39134
+ "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
39135
+ selectedObjectId === oid ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
39136
+ ),
39137
+ onClick: () => setSelectedObjectId(oid),
39138
+ children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
39139
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", children: objIcon(obj) }),
39140
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: objName(obj) }),
39141
+ /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
39142
+ ] })
39143
+ },
39144
+ oid
39145
+ );
39146
+ }) })
38946
39147
  ] }),
38947
39148
  selectedObject && /* @__PURE__ */ jsx(
38948
39149
  ObjectRulePanel,
@@ -38953,12 +39154,12 @@ function EventHandlerBoard({
38953
39154
  }
38954
39155
  ),
38955
39156
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
38956
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("eventHandler.chainComplete") }) }),
39157
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
38957
39158
  playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
38958
39159
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
38959
39160
  showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
38960
39161
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
38961
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
39162
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
38962
39163
  ] }) })
38963
39164
  ] }),
38964
39165
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
@@ -38986,6 +39187,8 @@ var init_EventHandlerBoard = __esm({
38986
39187
  init_TraitStateViewer();
38987
39188
  init_ObjectRulePanel();
38988
39189
  init_EventLog();
39190
+ init_puzzleObject();
39191
+ init_boardEntity();
38989
39192
  ENCOURAGEMENT_KEYS = [
38990
39193
  "puzzle.tryAgain1",
38991
39194
  "puzzle.tryAgain2",
@@ -39074,7 +39277,10 @@ var init_FeatureGridOrganism = __esm({
39074
39277
  );
39075
39278
  useCallback(
39076
39279
  (feature) => {
39077
- eventBus.emit("UI:FEATURE_CLICK", { id: feature.id, href: feature.href ?? "" });
39280
+ eventBus.emit("UI:FEATURE_CLICK", {
39281
+ id: String(feature.id ?? ""),
39282
+ href: String(feature.href ?? "")
39283
+ });
39078
39284
  },
39079
39285
  [eventBus]
39080
39286
  );
@@ -39084,14 +39290,17 @@ var init_FeatureGridOrganism = __esm({
39084
39290
  if (error) {
39085
39291
  return /* @__PURE__ */ jsx(ErrorState, { message: error.message, className });
39086
39292
  }
39087
- const featureCards = items.map((feature) => ({
39088
- icon: feature.icon,
39089
- title: feature.title,
39090
- description: feature.description,
39091
- href: feature.href,
39092
- linkLabel: feature.linkLabel,
39093
- variant: feature.href ? "interactive" : "bordered"
39094
- }));
39293
+ const featureCards = items.map((feature) => {
39294
+ const href = feature.href != null ? String(feature.href) : void 0;
39295
+ return {
39296
+ icon: feature.icon != null ? String(feature.icon) : void 0,
39297
+ title: String(feature.title ?? ""),
39298
+ description: String(feature.description ?? ""),
39299
+ href,
39300
+ linkLabel: feature.linkLabel != null ? String(feature.linkLabel) : void 0,
39301
+ variant: href ? "interactive" : "bordered"
39302
+ };
39303
+ });
39095
39304
  return /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
39096
39305
  (heading || subtitle) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
39097
39306
  heading && /* @__PURE__ */ jsx(Typography, { variant: "h2", align: "center", children: heading }),
@@ -40409,22 +40618,24 @@ var init_HeroOrganism = __esm({
40409
40618
  () => Array.isArray(entity) ? entity[0] : entity && typeof entity === "object" ? entity : void 0,
40410
40619
  [entity]
40411
40620
  );
40621
+ const primaryAction = resolved?.primaryAction;
40622
+ const secondaryAction = resolved?.secondaryAction;
40412
40623
  const handlePrimaryClick = useCallback(() => {
40413
- if (resolved?.primaryAction) {
40624
+ if (primaryAction) {
40414
40625
  eventBus.emit("UI:CTA_PRIMARY", {
40415
- label: resolved.primaryAction.label,
40416
- href: resolved.primaryAction.href
40626
+ label: String(primaryAction.label ?? ""),
40627
+ href: String(primaryAction.href ?? "")
40417
40628
  });
40418
40629
  }
40419
- }, [eventBus, resolved]);
40630
+ }, [eventBus, primaryAction]);
40420
40631
  const handleSecondaryClick = useCallback(() => {
40421
- if (resolved?.secondaryAction) {
40632
+ if (secondaryAction) {
40422
40633
  eventBus.emit("UI:CTA_SECONDARY", {
40423
- label: resolved.secondaryAction.label,
40424
- href: resolved.secondaryAction.href
40634
+ label: String(secondaryAction.label ?? ""),
40635
+ href: String(secondaryAction.href ?? "")
40425
40636
  });
40426
40637
  }
40427
- }, [eventBus, resolved]);
40638
+ }, [eventBus, secondaryAction]);
40428
40639
  if (isLoading) {
40429
40640
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
40430
40641
  }
@@ -40434,17 +40645,19 @@ var init_HeroOrganism = __esm({
40434
40645
  if (!resolved) {
40435
40646
  return null;
40436
40647
  }
40648
+ const imageRaw = resolved.image;
40649
+ const image = imageRaw ? { src: String(imageRaw.src ?? ""), alt: String(imageRaw.alt ?? "") } : void 0;
40437
40650
  return /* @__PURE__ */ jsxs(
40438
40651
  HeroSection,
40439
40652
  {
40440
- tag: resolved.tag,
40441
- title: resolved.title,
40442
- titleAccent: resolved.titleAccent,
40443
- subtitle: resolved.subtitle,
40444
- primaryAction: resolved.primaryAction ? { label: resolved.primaryAction.label, href: resolved.primaryAction.href } : void 0,
40445
- secondaryAction: resolved.secondaryAction ? { label: resolved.secondaryAction.label, href: resolved.secondaryAction.href } : void 0,
40446
- installCommand: resolved.installCommand,
40447
- image: resolved.image,
40653
+ tag: resolved.tag != null ? String(resolved.tag) : void 0,
40654
+ title: String(resolved.title ?? ""),
40655
+ titleAccent: resolved.titleAccent != null ? String(resolved.titleAccent) : void 0,
40656
+ subtitle: String(resolved.subtitle ?? ""),
40657
+ primaryAction: primaryAction ? { label: String(primaryAction.label ?? ""), href: String(primaryAction.href ?? "") } : void 0,
40658
+ secondaryAction: secondaryAction ? { label: String(secondaryAction.label ?? ""), href: String(secondaryAction.href ?? "") } : void 0,
40659
+ installCommand: resolved.installCommand != null ? String(resolved.installCommand) : void 0,
40660
+ image,
40448
40661
  imagePosition: resolved.imagePosition,
40449
40662
  background: resolved.background,
40450
40663
  className: cn(className),
@@ -40453,8 +40666,8 @@ var init_HeroOrganism = __esm({
40453
40666
  /* @__PURE__ */ jsx(
40454
40667
  _HeroClickInterceptor,
40455
40668
  {
40456
- hasPrimary: !!resolved.primaryAction,
40457
- hasSecondary: !!resolved.secondaryAction,
40669
+ hasPrimary: !!primaryAction,
40670
+ hasSecondary: !!secondaryAction,
40458
40671
  onPrimaryClick: handlePrimaryClick,
40459
40672
  onSecondaryClick: handleSecondaryClick
40460
40673
  }
@@ -40683,7 +40896,7 @@ function formatValue3(value, fieldName) {
40683
40896
  return String(value);
40684
40897
  }
40685
40898
  function formatFieldLabel2(fieldName) {
40686
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
40899
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase()).replace(/Id$/, "").trim();
40687
40900
  }
40688
40901
  var STATUS_STYLES2, StatusBadge, ProgressIndicator, List3;
40689
40902
  var init_List = __esm({
@@ -41530,20 +41743,22 @@ function NegotiatorBoard({
41530
41743
  }) {
41531
41744
  const { emit } = useEventBus();
41532
41745
  const { t } = useTranslate();
41533
- const resolved = Array.isArray(entity) ? entity[0] : entity;
41746
+ const resolved = boardEntity(entity);
41534
41747
  const [history, setHistory] = useState([]);
41535
41748
  const [headerError, setHeaderError] = useState(false);
41536
41749
  const [showHint, setShowHint] = useState(false);
41750
+ const totalRounds = num(resolved?.totalRounds);
41751
+ const targetScore = num(resolved?.targetScore);
41537
41752
  const currentRound = history.length;
41538
- const isComplete = currentRound >= (resolved?.totalRounds ?? 0);
41753
+ const isComplete = currentRound >= totalRounds;
41539
41754
  const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
41540
41755
  const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
41541
- const won = isComplete && playerTotal >= (resolved?.targetScore ?? 0);
41542
- const actions = resolved?.actions ?? [];
41543
- const payoffMatrix = resolved?.payoffMatrix ?? [];
41756
+ const won = isComplete && playerTotal >= targetScore;
41757
+ const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
41758
+ const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
41544
41759
  const handleAction = useCallback((actionId) => {
41545
41760
  if (isComplete) return;
41546
- const opponentAction = getOpponentAction(resolved?.opponentStrategy ?? "random", actions, history);
41761
+ const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
41547
41762
  const payoff = payoffMatrix.find(
41548
41763
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
41549
41764
  );
@@ -41556,42 +41771,46 @@ function NegotiatorBoard({
41556
41771
  };
41557
41772
  const newHistory = [...history, result];
41558
41773
  setHistory(newHistory);
41559
- if (newHistory.length >= (resolved?.totalRounds ?? 0)) {
41774
+ if (newHistory.length >= totalRounds) {
41560
41775
  const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
41561
- if (total >= (resolved?.targetScore ?? 0)) {
41776
+ if (total >= targetScore) {
41562
41777
  emit(`UI:${completeEvent}`, { success: true, score: total });
41563
41778
  }
41564
- if (newHistory.length >= 3 && resolved?.hint) {
41779
+ if (newHistory.length >= 3 && str(resolved?.hint)) {
41565
41780
  setShowHint(true);
41566
41781
  }
41567
41782
  }
41568
- }, [isComplete, resolved, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41783
+ }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41569
41784
  const handleReset = () => {
41570
41785
  setHistory([]);
41571
41786
  setShowHint(false);
41572
41787
  };
41573
41788
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
41574
41789
  if (!resolved) return null;
41790
+ const theme = resolved.theme ?? void 0;
41791
+ const themeBackground = theme?.background;
41792
+ const headerImage = str(resolved.headerImage);
41793
+ const hint = str(resolved.hint);
41575
41794
  return /* @__PURE__ */ jsx(
41576
41795
  Box,
41577
41796
  {
41578
41797
  className,
41579
41798
  style: {
41580
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
41799
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
41581
41800
  backgroundSize: "cover",
41582
41801
  backgroundPosition: "center"
41583
41802
  },
41584
41803
  children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
41585
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
41804
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
41586
41805
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
41587
- /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
41588
- /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.description }),
41806
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
41807
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
41589
41808
  /* @__PURE__ */ jsxs(HStack, { gap: "md", children: [
41590
- /* @__PURE__ */ jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(resolved.totalRounds) }) }),
41809
+ /* @__PURE__ */ jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(totalRounds) }) }),
41591
41810
  /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
41592
41811
  t("negotiator.target"),
41593
41812
  ": ",
41594
- resolved.targetScore
41813
+ targetScore
41595
41814
  ] })
41596
41815
  ] })
41597
41816
  ] }) }),
@@ -41640,16 +41859,16 @@ function NegotiatorBoard({
41640
41859
  ] }) }),
41641
41860
  isComplete && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
41642
41861
  /* @__PURE__ */ jsx(Icon, { icon: CheckCircle, size: "lg", className: won ? "text-success" : "text-error" }),
41643
- /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: won ? resolved.successMessage ?? t("negotiator.success") : resolved.failMessage ?? t("negotiator.failed") }),
41862
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: won ? str(resolved.successMessage) || t("negotiator.success") : str(resolved.failMessage) || t("negotiator.failed") }),
41644
41863
  /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
41645
41864
  t("negotiator.finalScore"),
41646
41865
  ": ",
41647
41866
  playerTotal,
41648
41867
  "/",
41649
- resolved.targetScore
41868
+ targetScore
41650
41869
  ] })
41651
41870
  ] }) }),
41652
- showHint && resolved.hint && !won && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.hint }) }),
41871
+ showHint && hint && !won && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
41653
41872
  isComplete && !won && /* @__PURE__ */ jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
41654
41873
  ] })
41655
41874
  }
@@ -41659,6 +41878,7 @@ var init_NegotiatorBoard = __esm({
41659
41878
  "components/game/organisms/puzzles/negotiator/NegotiatorBoard.tsx"() {
41660
41879
  init_atoms2();
41661
41880
  init_useEventBus();
41881
+ init_boardEntity();
41662
41882
  NegotiatorBoard.displayName = "NegotiatorBoard";
41663
41883
  }
41664
41884
  });
@@ -41694,13 +41914,13 @@ var init_PricingOrganism = __esm({
41694
41914
  return /* @__PURE__ */ jsx(ErrorState, { message: error.message, className });
41695
41915
  }
41696
41916
  const plans = items.map((plan) => ({
41697
- name: plan.name,
41698
- price: plan.price,
41699
- description: plan.description,
41700
- features: plan.features,
41701
- action: { label: plan.actionLabel, href: plan.actionHref },
41702
- highlighted: plan.highlighted,
41703
- badge: plan.badge
41917
+ name: String(plan.name ?? ""),
41918
+ price: String(plan.price ?? ""),
41919
+ description: plan.description != null ? String(plan.description) : void 0,
41920
+ features: (plan.features ?? []).map((f3) => String(f3)),
41921
+ action: { label: String(plan.actionLabel ?? ""), href: String(plan.actionHref ?? "") },
41922
+ highlighted: Boolean(plan.highlighted),
41923
+ badge: plan.badge != null ? String(plan.badge) : void 0
41704
41924
  }));
41705
41925
  return /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
41706
41926
  (heading || subtitle) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -43771,16 +43991,20 @@ function SequencerBoard({
43771
43991
  }) {
43772
43992
  const { emit } = useEventBus();
43773
43993
  const { t } = useTranslate();
43774
- const resolved = Array.isArray(entity) ? entity[0] : entity;
43994
+ const resolved = boardEntity(entity);
43995
+ const maxSlots = num(resolved?.maxSlots);
43996
+ const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
43997
+ const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
43998
+ const allowDuplicates = resolved?.allowDuplicates !== false;
43775
43999
  const [headerError, setHeaderError] = useState(false);
43776
44000
  const [slots, setSlots] = useState(
43777
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0)
44001
+ () => Array.from({ length: maxSlots }, () => void 0)
43778
44002
  );
43779
44003
  const [playState, setPlayState] = useState("idle");
43780
44004
  const [currentStep, setCurrentStep] = useState(-1);
43781
44005
  const [attempts, setAttempts] = useState(0);
43782
44006
  const [slotFeedback, setSlotFeedback] = useState(
43783
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => null)
44007
+ () => Array.from({ length: maxSlots }, () => null)
43784
44008
  );
43785
44009
  const timerRef = useRef(null);
43786
44010
  useEffect(() => () => {
@@ -43814,17 +44038,17 @@ function SequencerBoard({
43814
44038
  }, [emit]);
43815
44039
  const handleReset = useCallback(() => {
43816
44040
  if (timerRef.current) clearTimeout(timerRef.current);
43817
- setSlots(Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0));
44041
+ setSlots(Array.from({ length: maxSlots }, () => void 0));
43818
44042
  setPlayState("idle");
43819
44043
  setCurrentStep(-1);
43820
44044
  setAttempts(0);
43821
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
43822
- }, [resolved?.maxSlots]);
44045
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44046
+ }, [maxSlots]);
43823
44047
  const filledSlots = slots.filter((s) => !!s);
43824
44048
  const canPlay = filledSlots.length > 0 && playState === "idle";
43825
44049
  const handlePlay = useCallback(() => {
43826
44050
  if (!canPlay) return;
43827
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
44051
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
43828
44052
  emit("UI:PLAY_SOUND", { key: "confirm" });
43829
44053
  const sequence = slots.map((s) => s?.id || "");
43830
44054
  if (playEvent) {
@@ -43835,10 +44059,10 @@ function SequencerBoard({
43835
44059
  let step = 0;
43836
44060
  const advance = () => {
43837
44061
  step++;
43838
- if (step >= (resolved?.maxSlots ?? 0)) {
44062
+ if (step >= maxSlots) {
43839
44063
  const playerSeq = slots.map((s) => s?.id);
43840
44064
  const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
43841
- const success = (resolved?.solutions ?? []).some(
44065
+ const success = solutions.some(
43842
44066
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
43843
44067
  );
43844
44068
  if (success) {
@@ -43850,7 +44074,7 @@ function SequencerBoard({
43850
44074
  }
43851
44075
  } else {
43852
44076
  setAttempts((prev) => prev + 1);
43853
- const feedback = computeSlotFeedback(playerSeq, resolved?.solutions ?? []);
44077
+ const feedback = computeSlotFeedback(playerSeq, solutions);
43854
44078
  setSlotFeedback(feedback);
43855
44079
  setPlayState("idle");
43856
44080
  setCurrentStep(-1);
@@ -43868,10 +44092,10 @@ function SequencerBoard({
43868
44092
  }
43869
44093
  };
43870
44094
  timerRef.current = setTimeout(advance, stepDurationMs);
43871
- }, [canPlay, slots, resolved?.maxSlots, resolved?.solutions, stepDurationMs, playEvent, completeEvent, emit]);
44095
+ }, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, emit]);
43872
44096
  const machine = {
43873
- name: resolved?.title ?? "",
43874
- description: resolved?.description ?? "",
44097
+ name: str(resolved?.title),
44098
+ description: str(resolved?.description),
43875
44099
  states: slots.map((s, i) => stepLabel(s, i)),
43876
44100
  currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
43877
44101
  transitions: slots.slice(0, -1).map((s, i) => ({
@@ -43880,37 +44104,41 @@ function SequencerBoard({
43880
44104
  event: "NEXT"
43881
44105
  }))
43882
44106
  };
43883
- const usedIds = resolved?.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
43884
- const showHint = attempts >= 3 && !!resolved?.hint;
44107
+ const usedIds = !allowDuplicates ? slots.filter(Boolean).map((s) => s?.id || "") : [];
44108
+ const hint = str(resolved?.hint);
44109
+ const showHint = attempts >= 3 && !!hint;
43885
44110
  const hasFeedback = slotFeedback.some((f3) => f3 !== null);
43886
44111
  const correctCount = slotFeedback.filter((f3) => f3 === "correct").length;
43887
44112
  const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
43888
44113
  if (!resolved) return null;
44114
+ const theme = resolved.theme ?? void 0;
44115
+ const themeBackground = theme?.background;
44116
+ const headerImage = str(resolved.headerImage);
43889
44117
  return /* @__PURE__ */ jsxs(
43890
44118
  VStack,
43891
44119
  {
43892
44120
  className: cn("p-4 gap-6", className),
43893
44121
  style: {
43894
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44122
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
43895
44123
  backgroundSize: "cover",
43896
44124
  backgroundPosition: "center"
43897
44125
  },
43898
44126
  children: [
43899
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44127
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
43900
44128
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
43901
- /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
43902
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description })
44129
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
44130
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) })
43903
44131
  ] }),
43904
44132
  showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
43905
44133
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
43906
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
44134
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
43907
44135
  ] }) }),
43908
44136
  filledSlots.length > 0 && /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
43909
44137
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
43910
44138
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
43911
44139
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
43912
44140
  hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
43913
- `${correctCount}/${resolved.maxSlots} `,
44141
+ `${correctCount}/${maxSlots} `,
43914
44142
  "\u2705"
43915
44143
  ] })
43916
44144
  ] }),
@@ -43918,7 +44146,7 @@ function SequencerBoard({
43918
44146
  SequenceBar,
43919
44147
  {
43920
44148
  slots,
43921
- maxSlots: resolved.maxSlots,
44149
+ maxSlots,
43922
44150
  onSlotDrop: handleSlotDrop,
43923
44151
  onSlotRemove: handleSlotRemove,
43924
44152
  playing: playState === "playing",
@@ -43932,15 +44160,15 @@ function SequencerBoard({
43932
44160
  playState !== "playing" && /* @__PURE__ */ jsx(
43933
44161
  ActionPalette,
43934
44162
  {
43935
- actions: resolved.availableActions,
44163
+ actions: availableActions,
43936
44164
  usedActionIds: usedIds,
43937
- allowDuplicates: resolved.allowDuplicates !== false,
44165
+ allowDuplicates,
43938
44166
  categoryColors,
43939
44167
  label: t("sequencer.dragActions")
43940
44168
  }
43941
44169
  ),
43942
44170
  hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
43943
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("sequencer.levelComplete") }) }),
44171
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
43944
44172
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
43945
44173
  /* @__PURE__ */ jsx(
43946
44174
  Button,
@@ -43964,6 +44192,7 @@ var init_SequencerBoard = __esm({
43964
44192
  init_cn();
43965
44193
  init_useEventBus();
43966
44194
  init_TraitStateViewer();
44195
+ init_boardEntity();
43967
44196
  init_SequenceBar();
43968
44197
  init_ActionPalette();
43969
44198
  ENCOURAGEMENT_KEYS2 = [
@@ -44013,18 +44242,21 @@ var init_ShowcaseOrganism = __esm({
44013
44242
  heading && /* @__PURE__ */ jsx(Typography, { variant: "h2", align: "center", children: heading }),
44014
44243
  subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body1", color: "muted", align: "center", className: "max-w-2xl", children: subtitle })
44015
44244
  ] }),
44016
- /* @__PURE__ */ jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => /* @__PURE__ */ jsx(
44017
- ShowcaseCard,
44018
- {
44019
- title: item.title,
44020
- description: item.description,
44021
- image: item.image,
44022
- href: item.href,
44023
- badge: item.badge,
44024
- accentColor: item.accentColor
44025
- },
44026
- item.id
44027
- )) })
44245
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => {
44246
+ const imageRaw = item.image;
44247
+ return /* @__PURE__ */ jsx(
44248
+ ShowcaseCard,
44249
+ {
44250
+ title: String(item.title ?? ""),
44251
+ description: item.description != null ? String(item.description) : void 0,
44252
+ image: { src: String(imageRaw?.src ?? ""), alt: String(imageRaw?.alt ?? "") },
44253
+ href: item.href != null ? String(item.href) : void 0,
44254
+ badge: item.badge != null ? String(item.badge) : void 0,
44255
+ accentColor: item.accentColor != null ? String(item.accentColor) : void 0
44256
+ },
44257
+ String(item.id ?? "")
44258
+ );
44259
+ }) })
44028
44260
  ] });
44029
44261
  };
44030
44262
  ShowcaseOrganism.displayName = "ShowcaseOrganism";
@@ -44392,8 +44624,8 @@ function SimulatorBoard({
44392
44624
  }) {
44393
44625
  const { emit } = useEventBus();
44394
44626
  const { t } = useTranslate();
44395
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44396
- const parameters = resolved?.parameters ?? [];
44627
+ const resolved = boardEntity(entity);
44628
+ const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
44397
44629
  const [values, setValues] = useState(() => {
44398
44630
  const init = {};
44399
44631
  for (const p2 of parameters) {
@@ -44407,15 +44639,15 @@ function SimulatorBoard({
44407
44639
  const [showHint, setShowHint] = useState(false);
44408
44640
  const computeOutput = useCallback((params) => {
44409
44641
  try {
44410
- const fn = new Function("params", `return (${resolved?.computeExpression})`);
44642
+ const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
44411
44643
  return fn(params);
44412
44644
  } catch {
44413
44645
  return 0;
44414
44646
  }
44415
44647
  }, [resolved?.computeExpression]);
44416
44648
  const output = useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
44417
- const targetValue = resolved?.targetValue ?? 0;
44418
- const targetTolerance = resolved?.targetTolerance ?? 0;
44649
+ const targetValue = num(resolved?.targetValue);
44650
+ const targetTolerance = num(resolved?.targetTolerance);
44419
44651
  const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
44420
44652
  const handleParameterChange = (id, value) => {
44421
44653
  if (submitted) return;
@@ -44430,7 +44662,7 @@ function SimulatorBoard({
44430
44662
  };
44431
44663
  const handleReset = () => {
44432
44664
  setSubmitted(false);
44433
- if (attempts >= 2 && resolved?.hint) {
44665
+ if (attempts >= 2 && str(resolved?.hint)) {
44434
44666
  setShowHint(true);
44435
44667
  }
44436
44668
  };
@@ -44445,20 +44677,26 @@ function SimulatorBoard({
44445
44677
  setShowHint(false);
44446
44678
  };
44447
44679
  if (!resolved) return null;
44680
+ const theme = resolved.theme ?? void 0;
44681
+ const themeBackground = theme?.background;
44682
+ const headerImage = str(resolved.headerImage);
44683
+ const hint = str(resolved.hint);
44684
+ const outputLabel = str(resolved.outputLabel);
44685
+ const outputUnit = str(resolved.outputUnit);
44448
44686
  return /* @__PURE__ */ jsx(
44449
44687
  Box,
44450
44688
  {
44451
44689
  className,
44452
44690
  style: {
44453
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44691
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
44454
44692
  backgroundSize: "cover",
44455
44693
  backgroundPosition: "center"
44456
44694
  },
44457
44695
  children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
44458
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44696
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44459
44697
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
44460
- /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
44461
- /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.description })
44698
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
44699
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) })
44462
44700
  ] }) }),
44463
44701
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", children: [
44464
44702
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
@@ -44499,28 +44737,28 @@ function SimulatorBoard({
44499
44737
  ] }, param.id))
44500
44738
  ] }) }),
44501
44739
  /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
44502
- /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: resolved.outputLabel }),
44740
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
44503
44741
  /* @__PURE__ */ jsxs(Typography, { variant: "h3", weight: "bold", children: [
44504
44742
  output.toFixed(2),
44505
44743
  " ",
44506
- resolved.outputUnit
44744
+ outputUnit
44507
44745
  ] }),
44508
44746
  submitted && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
44509
44747
  /* @__PURE__ */ jsx(Icon, { icon: isCorrect ? CheckCircle : XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
44510
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? resolved.successMessage ?? t("simulator.correct") : resolved.failMessage ?? t("simulator.incorrect") })
44748
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
44511
44749
  ] }),
44512
44750
  /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44513
44751
  t("simulator.target"),
44514
44752
  ": ",
44515
44753
  targetValue,
44516
44754
  " ",
44517
- resolved.outputUnit,
44755
+ outputUnit,
44518
44756
  " (\xB1",
44519
44757
  targetTolerance,
44520
44758
  ")"
44521
44759
  ] })
44522
44760
  ] }) }),
44523
- showHint && resolved.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: resolved.hint }) }),
44761
+ showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
44524
44762
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
44525
44763
  !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
44526
44764
  /* @__PURE__ */ jsx(Icon, { icon: Play, size: "sm" }),
@@ -44539,6 +44777,7 @@ var init_SimulatorBoard = __esm({
44539
44777
  "components/game/organisms/puzzles/simulator/SimulatorBoard.tsx"() {
44540
44778
  init_atoms2();
44541
44779
  init_useEventBus();
44780
+ init_boardEntity();
44542
44781
  SimulatorBoard.displayName = "SimulatorBoard";
44543
44782
  }
44544
44783
  });
@@ -44964,22 +45203,25 @@ function VariablePanel({
44964
45203
  return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
44965
45204
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
44966
45205
  variables.map((v) => {
44967
- const max = v.max ?? 100;
44968
- const min = v.min ?? 0;
44969
- const pct = Math.round((v.value - min) / (max - min) * 100);
45206
+ const name = v.name == null ? "" : String(v.name);
45207
+ const value = numField(v.value);
45208
+ const max = numField(v.max, 100);
45209
+ const min = numField(v.min, 0);
45210
+ const unit = v.unit == null ? "" : String(v.unit);
45211
+ const pct = Math.round((value - min) / (max - min) * 100);
44970
45212
  const isHigh = pct > 80;
44971
45213
  const isLow = pct < 20;
44972
45214
  return /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
44973
45215
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
44974
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: v.name }),
45216
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: name }),
44975
45217
  /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: cn(
44976
45218
  isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
44977
45219
  ), children: [
44978
- v.value,
44979
- v.unit || "",
45220
+ value,
45221
+ unit,
44980
45222
  " / ",
44981
45223
  max,
44982
- v.unit || ""
45224
+ unit
44983
45225
  ] })
44984
45226
  ] }),
44985
45227
  /* @__PURE__ */ jsx(
@@ -44990,14 +45232,19 @@ function VariablePanel({
44990
45232
  size: "sm"
44991
45233
  }
44992
45234
  )
44993
- ] }, v.name);
45235
+ ] }, name);
44994
45236
  })
44995
45237
  ] });
44996
45238
  }
45239
+ var numField;
44997
45240
  var init_VariablePanel = __esm({
44998
45241
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
44999
45242
  init_atoms2();
45000
45243
  init_cn();
45244
+ numField = (v, fallback = 0) => {
45245
+ const n = Number(v);
45246
+ return Number.isFinite(n) ? n : fallback;
45247
+ };
45001
45248
  VariablePanel.displayName = "VariablePanel";
45002
45249
  }
45003
45250
  });
@@ -45024,14 +45271,21 @@ function StateArchitectBoard({
45024
45271
  }) {
45025
45272
  const { emit } = useEventBus();
45026
45273
  const { t } = useTranslate();
45027
- const resolved = Array.isArray(entity) ? entity[0] : entity;
45028
- const [transitions, setTransitions] = useState(resolved?.transitions ?? []);
45274
+ const resolved = boardEntity(entity);
45275
+ const entityStates = Array.isArray(resolved?.states) ? resolved.states : [];
45276
+ const initialState = str(resolved?.initialState);
45277
+ const entityName = str(resolved?.entityName);
45278
+ const availableEvents = Array.isArray(resolved?.availableEvents) ? resolved.availableEvents : [];
45279
+ const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
45280
+ const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
45281
+ const entityVariables = rows(resolved?.variables);
45282
+ const [transitions, setTransitions] = useState(entityTransitions);
45029
45283
  const [headerError, setHeaderError] = useState(false);
45030
45284
  const [playState, setPlayState] = useState("editing");
45031
- const [currentState, setCurrentState] = useState(resolved?.initialState ?? "");
45285
+ const [currentState, setCurrentState] = useState(initialState);
45032
45286
  const [selectedState, setSelectedState] = useState(null);
45033
45287
  const [testResults, setTestResults] = useState([]);
45034
- const [variables, setVariables] = useState(resolved?.variables ?? []);
45288
+ const [variables, setVariables] = useState(() => [...entityVariables]);
45035
45289
  const [attempts, setAttempts] = useState(0);
45036
45290
  const timerRef = useRef(null);
45037
45291
  const [addingFrom, setAddingFrom] = useState(null);
@@ -45040,12 +45294,12 @@ function StateArchitectBoard({
45040
45294
  }, []);
45041
45295
  const GRAPH_W = 500;
45042
45296
  const GRAPH_H = 400;
45043
- const positions = useMemo(() => layoutStates(resolved?.states ?? [], GRAPH_W, GRAPH_H), [resolved?.states]);
45297
+ const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
45044
45298
  const handleStateClick = useCallback((state) => {
45045
45299
  if (playState !== "editing") return;
45046
45300
  if (addingFrom) {
45047
45301
  if (addingFrom !== state) {
45048
- const event = resolved?.availableEvents[0] || "EVENT";
45302
+ const event = availableEvents[0] || "EVENT";
45049
45303
  const newTrans = {
45050
45304
  id: `t-${nextTransId++}`,
45051
45305
  from: addingFrom,
@@ -45058,7 +45312,7 @@ function StateArchitectBoard({
45058
45312
  } else {
45059
45313
  setSelectedState(state);
45060
45314
  }
45061
- }, [playState, addingFrom, resolved?.availableEvents]);
45315
+ }, [playState, addingFrom, availableEvents]);
45062
45316
  const handleStartAddTransition = useCallback(() => {
45063
45317
  if (!selectedState) return;
45064
45318
  setAddingFrom(selectedState);
@@ -45067,9 +45321,9 @@ function StateArchitectBoard({
45067
45321
  setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45068
45322
  }, []);
45069
45323
  const machine = useMemo(() => ({
45070
- name: resolved?.entityName ?? "",
45071
- description: resolved?.description ?? "",
45072
- states: resolved?.states ?? [],
45324
+ name: entityName,
45325
+ description: str(resolved?.description),
45326
+ states: entityStates,
45073
45327
  currentState,
45074
45328
  transitions: transitions.map((t2) => ({
45075
45329
  from: t2.from,
@@ -45077,7 +45331,7 @@ function StateArchitectBoard({
45077
45331
  event: t2.event,
45078
45332
  guardHint: t2.guardHint
45079
45333
  }))
45080
- }), [resolved, currentState, transitions]);
45334
+ }), [entityName, resolved, entityStates, currentState, transitions]);
45081
45335
  const handleTest = useCallback(() => {
45082
45336
  if (playState !== "editing") return;
45083
45337
  if (testEvent) emit(`UI:${testEvent}`, {});
@@ -45086,7 +45340,7 @@ function StateArchitectBoard({
45086
45340
  const results = [];
45087
45341
  let testIdx = 0;
45088
45342
  const runNextTest = () => {
45089
- if (testIdx >= (resolved?.testCases.length ?? 0)) {
45343
+ if (testIdx >= testCases.length) {
45090
45344
  const allPassed = results.every((r) => r.passed);
45091
45345
  setPlayState(allPassed ? "success" : "fail");
45092
45346
  setTestResults(results);
@@ -45101,9 +45355,9 @@ function StateArchitectBoard({
45101
45355
  }
45102
45356
  return;
45103
45357
  }
45104
- const testCase = resolved?.testCases[testIdx];
45358
+ const testCase = testCases[testIdx];
45105
45359
  if (!testCase) return;
45106
- let state = resolved.initialState;
45360
+ let state = initialState;
45107
45361
  for (const event of testCase.events) {
45108
45362
  const trans = transitions.find((t2) => t2.from === state && t2.event === event);
45109
45363
  if (trans) {
@@ -45121,53 +45375,57 @@ function StateArchitectBoard({
45121
45375
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45122
45376
  };
45123
45377
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45124
- }, [playState, transitions, resolved, stepDurationMs, testEvent, completeEvent, emit]);
45378
+ }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45125
45379
  const handleTryAgain = useCallback(() => {
45126
45380
  if (timerRef.current) clearTimeout(timerRef.current);
45127
45381
  setPlayState("editing");
45128
- setCurrentState(resolved?.initialState ?? "");
45382
+ setCurrentState(initialState);
45129
45383
  setTestResults([]);
45130
- }, [resolved?.initialState]);
45384
+ }, [initialState]);
45131
45385
  const handleReset = useCallback(() => {
45132
45386
  if (timerRef.current) clearTimeout(timerRef.current);
45133
- setTransitions(resolved?.transitions ?? []);
45387
+ setTransitions(entityTransitions);
45134
45388
  setPlayState("editing");
45135
- setCurrentState(resolved?.initialState ?? "");
45389
+ setCurrentState(initialState);
45136
45390
  setTestResults([]);
45137
- setVariables(resolved?.variables ?? []);
45391
+ setVariables([...entityVariables]);
45138
45392
  setSelectedState(null);
45139
45393
  setAddingFrom(null);
45140
45394
  setAttempts(0);
45141
- }, [resolved]);
45395
+ }, [entityTransitions, initialState, entityVariables]);
45142
45396
  const codeData = useMemo(() => ({
45143
- name: resolved?.entityName ?? "",
45144
- states: resolved?.states ?? [],
45145
- initialState: resolved?.initialState ?? "",
45397
+ name: entityName,
45398
+ states: entityStates,
45399
+ initialState,
45146
45400
  transitions: transitions.map((t2) => ({
45147
45401
  from: t2.from,
45148
45402
  to: t2.to,
45149
45403
  event: t2.event,
45150
45404
  ...t2.guardHint ? { guard: t2.guardHint } : {}
45151
45405
  }))
45152
- }), [resolved, transitions]);
45406
+ }), [entityName, entityStates, initialState, transitions]);
45153
45407
  if (!resolved) return null;
45408
+ const theme = resolved.theme ?? void 0;
45409
+ const themeBackground = theme?.background;
45410
+ const headerImage = str(resolved.headerImage);
45411
+ const hint = str(resolved.hint);
45154
45412
  return /* @__PURE__ */ jsxs(
45155
45413
  VStack,
45156
45414
  {
45157
45415
  className: cn("p-4 gap-6", className),
45158
45416
  style: {
45159
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
45417
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
45160
45418
  backgroundSize: "cover",
45161
45419
  backgroundPosition: "center"
45162
45420
  },
45163
45421
  children: [
45164
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
45422
+ headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
45165
45423
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
45166
- /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
45167
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
45424
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
45425
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
45168
45426
  /* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
45169
45427
  /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
45170
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.hint })
45428
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: hint })
45171
45429
  ] })
45172
45430
  ] }),
45173
45431
  /* @__PURE__ */ jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
@@ -45215,14 +45473,14 @@ function StateArchitectBoard({
45215
45473
  ]
45216
45474
  }
45217
45475
  ),
45218
- resolved.states.map((state) => /* @__PURE__ */ jsx(
45476
+ entityStates.map((state) => /* @__PURE__ */ jsx(
45219
45477
  StateNode2,
45220
45478
  {
45221
45479
  name: state,
45222
45480
  position: positions[state],
45223
45481
  isCurrent: state === currentState,
45224
45482
  isSelected: state === selectedState,
45225
- isInitial: state === resolved.initialState,
45483
+ isInitial: state === initialState,
45226
45484
  onClick: () => handleStateClick(state)
45227
45485
  },
45228
45486
  state
@@ -45269,7 +45527,7 @@ function StateArchitectBoard({
45269
45527
  /* @__PURE__ */ jsx(
45270
45528
  VariablePanel,
45271
45529
  {
45272
- entityName: resolved.entityName,
45530
+ entityName,
45273
45531
  variables
45274
45532
  }
45275
45533
  ),
@@ -45284,12 +45542,12 @@ function StateArchitectBoard({
45284
45542
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(CodeView, { data: codeData, label: "View Code" })
45285
45543
  ] })
45286
45544
  ] }),
45287
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("stateArchitect.allPassed") }) }),
45545
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
45288
45546
  playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45289
45547
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
45290
- attempts >= 3 && resolved.hint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
45548
+ attempts >= 3 && hint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
45291
45549
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
45292
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
45550
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
45293
45551
  ] }) })
45294
45552
  ] }),
45295
45553
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
@@ -45319,6 +45577,7 @@ var init_StateArchitectBoard = __esm({
45319
45577
  init_TransitionArrow();
45320
45578
  init_VariablePanel();
45321
45579
  init_CodeView();
45580
+ init_boardEntity();
45322
45581
  ENCOURAGEMENT_KEYS3 = [
45323
45582
  "puzzle.tryAgain1",
45324
45583
  "puzzle.tryAgain2",
@@ -45355,8 +45614,8 @@ var init_StatsOrganism = __esm({
45355
45614
  return /* @__PURE__ */ jsx(ErrorState, { message: error.message, className });
45356
45615
  }
45357
45616
  const stats = items.map((item) => ({
45358
- value: item.value,
45359
- label: item.label
45617
+ value: String(item.value ?? ""),
45618
+ label: String(item.label ?? "")
45360
45619
  }));
45361
45620
  return /* @__PURE__ */ jsx(
45362
45621
  StatsGrid,
@@ -45402,10 +45661,10 @@ var init_StepFlowOrganism = __esm({
45402
45661
  return /* @__PURE__ */ jsx(ErrorState, { message: error.message, className });
45403
45662
  }
45404
45663
  const steps = items.map((item) => ({
45405
- number: item.number,
45406
- title: item.title,
45407
- description: item.description,
45408
- icon: item.icon
45664
+ number: item.number != null ? Number(item.number) : void 0,
45665
+ title: String(item.title ?? ""),
45666
+ description: String(item.description ?? ""),
45667
+ icon: item.icon != null ? String(item.icon) : void 0
45409
45668
  }));
45410
45669
  return /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
45411
45670
  (heading || subtitle) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -45578,13 +45837,13 @@ var init_TeamOrganism = __esm({
45578
45837
  /* @__PURE__ */ jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((member) => /* @__PURE__ */ jsx(
45579
45838
  TeamCard,
45580
45839
  {
45581
- name: member.name,
45582
- nameAr: member.nameAr,
45583
- role: member.role,
45584
- bio: member.bio,
45585
- avatar: member.avatar
45840
+ name: String(member.name ?? ""),
45841
+ nameAr: member.nameAr != null ? String(member.nameAr) : void 0,
45842
+ role: String(member.role ?? ""),
45843
+ bio: String(member.bio ?? ""),
45844
+ avatar: member.avatar != null ? String(member.avatar) : void 0
45586
45845
  },
45587
- member.id
45846
+ String(member.id ?? "")
45588
45847
  )) })
45589
45848
  ] });
45590
45849
  };
@@ -45821,8 +46080,8 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45821
46080
  const [turn, setTurn] = useState(1);
45822
46081
  const [gameResult, setGameResult] = useState(null);
45823
46082
  const checkGameEnd = useCallback((currentUnits) => {
45824
- const pa = currentUnits.filter((u) => u.team === "player" && u.health > 0);
45825
- const ea = currentUnits.filter((u) => u.team === "enemy" && u.health > 0);
46083
+ const pa = currentUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
46084
+ const ea = currentUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
45826
46085
  if (pa.length === 0) {
45827
46086
  setGameResult("defeat");
45828
46087
  setPhase("game_over");
@@ -45840,34 +46099,36 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45840
46099
  }
45841
46100
  }, [onGameEnd, gameEndEvent, eventBus]);
45842
46101
  const handleUnitClick = useCallback((unitId) => {
45843
- const unit = units.find((u) => u.id === unitId);
46102
+ const unit = units.find((u) => str(u.id) === unitId);
45844
46103
  if (!unit) return;
45845
46104
  if (unitClickEvent) {
45846
46105
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
45847
46106
  }
45848
46107
  if (phase === "observation" || phase === "selection") {
45849
- if (unit.team === "player") {
46108
+ if (unitTeam(unit) === "player") {
45850
46109
  setSelectedUnitId(unitId);
45851
46110
  setPhase("movement");
45852
46111
  }
45853
46112
  } else if (phase === "action") {
45854
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46113
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45855
46114
  if (!selectedUnit) return;
45856
- if (unit.team === "enemy") {
45857
- const dx = Math.abs(unit.position.x - selectedUnit.position.x);
45858
- const dy = Math.abs(unit.position.y - selectedUnit.position.y);
46115
+ if (unitTeam(unit) === "enemy") {
46116
+ const up = unitPosition(unit);
46117
+ const sp = unitPosition(selectedUnit);
46118
+ const dx = Math.abs(up.x - sp.x);
46119
+ const dy = Math.abs(up.y - sp.y);
45859
46120
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
45860
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
45861
- const newHealth = Math.max(0, unit.health - damage);
46121
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
46122
+ const newHealth = Math.max(0, unitHealth(unit) - damage);
45862
46123
  const updatedUnits = units.map(
45863
- (u) => u.id === unit.id ? { ...u, health: newHealth } : u
46124
+ (u) => str(u.id) === str(unit.id) ? { ...u, health: newHealth } : u
45864
46125
  );
45865
46126
  setUnits(updatedUnits);
45866
46127
  onAttack?.(selectedUnit, unit, damage);
45867
46128
  if (attackEvent) {
45868
46129
  eventBus.emit(`UI:${attackEvent}`, {
45869
- attackerId: selectedUnit.id,
45870
- targetId: unit.id,
46130
+ attackerId: str(selectedUnit.id),
46131
+ targetId: str(unit.id),
45871
46132
  damage
45872
46133
  });
45873
46134
  }
@@ -45884,16 +46145,20 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45884
46145
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
45885
46146
  }
45886
46147
  if (phase === "movement" && selectedUnitId) {
45887
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46148
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45888
46149
  if (!selectedUnit) return;
45889
- const dx = Math.abs(x - selectedUnit.position.x);
45890
- const dy = Math.abs(y - selectedUnit.position.y);
46150
+ const sp = unitPosition(selectedUnit);
46151
+ const dx = Math.abs(x - sp.x);
46152
+ const dy = Math.abs(y - sp.y);
45891
46153
  const dist = dx + dy;
45892
- if (dist > 0 && dist <= selectedUnit.movement) {
45893
- if (!units.some((u) => u.position.x === x && u.position.y === y && u.health > 0)) {
46154
+ if (dist > 0 && dist <= num(selectedUnit.movement)) {
46155
+ if (!units.some((u) => {
46156
+ const p2 = unitPosition(u);
46157
+ return p2.x === x && p2.y === y && unitHealth(u) > 0;
46158
+ })) {
45894
46159
  setUnits(
45895
46160
  (prev) => prev.map(
45896
- (u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u
46161
+ (u) => str(u.id) === selectedUnitId ? { ...u, position: { x, y } } : u
45897
46162
  )
45898
46163
  );
45899
46164
  setPhase("action");
@@ -45936,12 +46201,13 @@ var init_useBattleState = __esm({
45936
46201
  "components/game/organisms/hooks/useBattleState.ts"() {
45937
46202
  "use client";
45938
46203
  init_useEventBus();
46204
+ init_boardEntity();
45939
46205
  }
45940
46206
  });
45941
46207
  function UncontrolledBattleBoard({ entity, ...rest }) {
45942
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46208
+ const resolved = boardEntity(entity);
45943
46209
  const battleState = useBattleState(
45944
- resolved?.initialUnits ?? [],
46210
+ rows(resolved?.initialUnits),
45945
46211
  {
45946
46212
  tileClickEvent: rest.tileClickEvent,
45947
46213
  unitClickEvent: rest.unitClickEvent,
@@ -45977,10 +46243,23 @@ function UncontrolledBattleBoard({ entity, ...rest }) {
45977
46243
  var init_UncontrolledBattleBoard = __esm({
45978
46244
  "components/game/organisms/UncontrolledBattleBoard.tsx"() {
45979
46245
  init_BattleBoard();
46246
+ init_boardEntity();
45980
46247
  init_useBattleState();
45981
46248
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
45982
46249
  }
45983
46250
  });
46251
+ function heroPosition(h) {
46252
+ return vec2(h.position);
46253
+ }
46254
+ function heroOwner(h) {
46255
+ return str(h.owner);
46256
+ }
46257
+ function heroMovement(h) {
46258
+ return num(h.movement);
46259
+ }
46260
+ function hexPassable(h) {
46261
+ return h.passable !== false;
46262
+ }
45984
46263
  function defaultIsInRange(from, to, range) {
45985
46264
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
45986
46265
  }
@@ -46011,36 +46290,36 @@ function WorldMapBoard({
46011
46290
  className
46012
46291
  }) {
46013
46292
  const eventBus = useEventBus();
46014
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46015
- const hexes = resolved?.hexes ?? [];
46016
- const heroes = resolved?.heroes ?? [];
46017
- const features = resolved?.features ?? [];
46018
- const selectedHeroId = resolved?.selectedHeroId;
46293
+ const resolved = boardEntity(entity);
46294
+ const hexes = rows(resolved?.hexes);
46295
+ const heroes = rows(resolved?.heroes);
46296
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
46297
+ const selectedHeroId = resolved?.selectedHeroId ?? null;
46019
46298
  const assetManifest = resolved?.assetManifest;
46020
46299
  const backgroundImage = resolved?.backgroundImage;
46021
46300
  const [hoveredTile, setHoveredTile] = useState(null);
46022
46301
  const selectedHero = useMemo(
46023
- () => heroes.find((h) => h.id === selectedHeroId) ?? null,
46302
+ () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46024
46303
  [heroes, selectedHeroId]
46025
46304
  );
46026
46305
  const tiles = useMemo(
46027
46306
  () => hexes.map((hex) => ({
46028
- x: hex.x,
46029
- y: hex.y,
46030
- terrain: hex.terrain,
46031
- terrainSprite: hex.terrainSprite
46307
+ x: num(hex.x),
46308
+ y: num(hex.y),
46309
+ terrain: str(hex.terrain),
46310
+ terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
46032
46311
  })),
46033
46312
  [hexes]
46034
46313
  );
46035
46314
  const baseUnits = useMemo(
46036
46315
  () => heroes.map((hero) => ({
46037
- id: hero.id,
46038
- position: hero.position,
46039
- name: hero.name,
46040
- team: hero.owner === "enemy" ? "enemy" : "player",
46316
+ id: str(hero.id),
46317
+ position: heroPosition(hero),
46318
+ name: str(hero.name),
46319
+ team: heroOwner(hero) === "enemy" ? "enemy" : "player",
46041
46320
  health: 100,
46042
46321
  maxHealth: 100,
46043
- sprite: hero.sprite
46322
+ sprite: hero.sprite == null ? void 0 : str(hero.sprite)
46044
46323
  })),
46045
46324
  [heroes]
46046
46325
  );
@@ -46081,73 +46360,94 @@ function WorldMapBoard({
46081
46360
  const isoUnits = useMemo(() => {
46082
46361
  if (movingPositions.size === 0) return baseUnits;
46083
46362
  return baseUnits.map((u) => {
46084
- const pos = movingPositions.get(u.id);
46363
+ const pos = u.id == null ? void 0 : movingPositions.get(u.id);
46085
46364
  return pos ? { ...u, position: pos } : u;
46086
46365
  });
46087
46366
  }, [baseUnits, movingPositions]);
46088
46367
  const validMoves = useMemo(() => {
46089
- if (!selectedHero || selectedHero.movement <= 0) return [];
46368
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46369
+ const sp = heroPosition(selectedHero);
46370
+ const sOwner = heroOwner(selectedHero);
46371
+ const range = heroMovement(selectedHero);
46090
46372
  const moves = [];
46091
46373
  hexes.forEach((hex) => {
46092
- if (hex.passable === false) return;
46093
- if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
46094
- if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
46095
- if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
46096
- moves.push({ x: hex.x, y: hex.y });
46374
+ const hx = num(hex.x);
46375
+ const hy = num(hex.y);
46376
+ if (!hexPassable(hex)) return;
46377
+ if (hx === sp.x && hy === sp.y) return;
46378
+ if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46379
+ if (heroes.some((h) => {
46380
+ const hp = heroPosition(h);
46381
+ return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
46382
+ })) return;
46383
+ moves.push({ x: hx, y: hy });
46097
46384
  });
46098
46385
  return moves;
46099
46386
  }, [selectedHero, hexes, heroes, isInRange]);
46100
46387
  const attackTargets = useMemo(() => {
46101
- if (!selectedHero || selectedHero.movement <= 0) return [];
46102
- return heroes.filter((h) => h.owner !== selectedHero.owner).filter((h) => isInRange(selectedHero.position, h.position, selectedHero.movement)).map((h) => h.position);
46388
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46389
+ const sp = heroPosition(selectedHero);
46390
+ const sOwner = heroOwner(selectedHero);
46391
+ const range = heroMovement(selectedHero);
46392
+ return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
46103
46393
  }, [selectedHero, heroes, isInRange]);
46104
- const maxY = Math.max(...hexes.map((h) => h.y), 0);
46394
+ const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
46105
46395
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46106
46396
  const tileToScreen = useCallback(
46107
46397
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46108
46398
  [scale, baseOffsetX]
46109
46399
  );
46110
46400
  const hoveredHex = useMemo(
46111
- () => hoveredTile ? hexes.find((h) => h.x === hoveredTile.x && h.y === hoveredTile.y) ?? null : null,
46401
+ () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
46112
46402
  [hoveredTile, hexes]
46113
46403
  );
46114
46404
  const hoveredHero = useMemo(
46115
- () => hoveredTile ? heroes.find((h) => h.position.x === hoveredTile.x && h.position.y === hoveredTile.y) ?? null : null,
46405
+ () => hoveredTile ? heroes.find((h) => {
46406
+ const hp = heroPosition(h);
46407
+ return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
46408
+ }) ?? null : null,
46116
46409
  [hoveredTile, heroes]
46117
46410
  );
46118
46411
  const handleTileClick = useCallback((x, y) => {
46119
46412
  if (movementAnimRef.current) return;
46120
- const hex = hexes.find((h) => h.x === x && h.y === y);
46413
+ const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
46121
46414
  if (!hex) return;
46122
46415
  if (tileClickEvent) {
46123
46416
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46124
46417
  }
46125
46418
  if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
46126
- startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
46127
- onHeroMove?.(selectedHero.id, x, y);
46419
+ const heroId = str(selectedHero.id);
46420
+ startMoveAnimation(heroId, { ...heroPosition(selectedHero) }, { x, y }, () => {
46421
+ onHeroMove?.(heroId, x, y);
46128
46422
  if (heroMoveEvent) {
46129
- eventBus.emit(`UI:${heroMoveEvent}`, { heroId: selectedHero.id, toX: x, toY: y });
46423
+ eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46130
46424
  }
46131
- if (hex.feature && hex.feature !== "none") {
46132
- onFeatureEnter?.(selectedHero.id, hex);
46425
+ const feature = str(hex.feature);
46426
+ if (feature && feature !== "none") {
46427
+ onFeatureEnter?.(heroId, hex);
46133
46428
  if (featureEnterEvent) {
46134
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId: selectedHero.id, feature: hex.feature, hex });
46429
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
46135
46430
  }
46136
46431
  }
46137
46432
  });
46138
46433
  return;
46139
46434
  }
46140
- const enemy = heroes.find((h) => h.position.x === x && h.position.y === y && h.owner === "enemy");
46435
+ const enemy = heroes.find((h) => {
46436
+ const hp = heroPosition(h);
46437
+ return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
46438
+ });
46141
46439
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46142
- onBattleEncounter?.(selectedHero.id, enemy.id);
46440
+ const attackerId = str(selectedHero.id);
46441
+ const defenderId = str(enemy.id);
46442
+ onBattleEncounter?.(attackerId, defenderId);
46143
46443
  if (battleEncounterEvent) {
46144
- eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId: selectedHero.id, defenderId: enemy.id });
46444
+ eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46145
46445
  }
46146
46446
  }
46147
46447
  }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46148
46448
  const handleUnitClick = useCallback((unitId) => {
46149
- const hero = heroes.find((h) => h.id === unitId);
46150
- if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
46449
+ const hero = heroes.find((h) => str(h.id) === unitId);
46450
+ if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
46151
46451
  onHeroSelect?.(unitId);
46152
46452
  if (heroSelectEvent) {
46153
46453
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
@@ -46220,6 +46520,7 @@ var init_WorldMapBoard = __esm({
46220
46520
  init_Stack();
46221
46521
  init_LoadingState();
46222
46522
  init_IsometricCanvas2();
46523
+ init_boardEntity();
46223
46524
  init_isometric();
46224
46525
  WorldMapBoard.displayName = "WorldMapBoard";
46225
46526
  }