@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
@@ -2767,7 +2767,7 @@ var init_SvgGrid = __esm({
2767
2767
  x,
2768
2768
  y,
2769
2769
  cols = 4,
2770
- rows = 3,
2770
+ rows: rows2 = 3,
2771
2771
  spacing = 20,
2772
2772
  nodeRadius = 3,
2773
2773
  color = "var(--color-primary)",
@@ -2776,7 +2776,7 @@ var init_SvgGrid = __esm({
2776
2776
  highlights = []
2777
2777
  }) => {
2778
2778
  const highlightSet = new Set(highlights);
2779
- return /* @__PURE__ */ jsxRuntime.jsx("g", { className, opacity, children: Array.from({ length: rows }).map(
2779
+ return /* @__PURE__ */ jsxRuntime.jsx("g", { className, opacity, children: Array.from({ length: rows2 }).map(
2780
2780
  (_, row) => Array.from({ length: cols }).map((_2, col) => {
2781
2781
  const index = row * cols + col;
2782
2782
  const isHighlighted = highlightSet.has(index);
@@ -3436,7 +3436,7 @@ var init_Input = __esm({
3436
3436
  onClear,
3437
3437
  value,
3438
3438
  options,
3439
- rows = 3,
3439
+ rows: rows2 = 3,
3440
3440
  onChange,
3441
3441
  ...props
3442
3442
  }, ref) => {
@@ -3486,7 +3486,7 @@ var init_Input = __esm({
3486
3486
  ref,
3487
3487
  value,
3488
3488
  onChange,
3489
- rows,
3489
+ rows: rows2,
3490
3490
  className: baseClassName,
3491
3491
  ...props
3492
3492
  }
@@ -5567,66 +5567,6 @@ var init_RangeSlider = __esm({
5567
5567
  RangeSlider.displayName = "RangeSlider";
5568
5568
  }
5569
5569
  });
5570
- function easeOut(t) {
5571
- return t * (2 - t);
5572
- }
5573
- var AnimatedCounter;
5574
- var init_AnimatedCounter = __esm({
5575
- "components/marketing/atoms/AnimatedCounter.tsx"() {
5576
- "use client";
5577
- init_cn();
5578
- init_Typography();
5579
- AnimatedCounter = ({
5580
- value: rawValue,
5581
- duration = 600,
5582
- prefix,
5583
- suffix,
5584
- className
5585
- }) => {
5586
- const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
5587
- const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
5588
- const [displayValue, setDisplayValue] = React85.useState(value);
5589
- const previousValueRef = React85.useRef(value);
5590
- const animationFrameRef = React85.useRef(null);
5591
- React85.useEffect(() => {
5592
- const from = previousValueRef.current;
5593
- const to = value;
5594
- previousValueRef.current = value;
5595
- if (from === to) {
5596
- setDisplayValue(to);
5597
- return;
5598
- }
5599
- const startTime = performance.now();
5600
- const diff = to - from;
5601
- function animate(currentTime) {
5602
- const elapsed = currentTime - startTime;
5603
- const progress = Math.min(elapsed / duration, 1);
5604
- const easedProgress = easeOut(progress);
5605
- setDisplayValue(from + diff * easedProgress);
5606
- if (progress < 1) {
5607
- animationFrameRef.current = requestAnimationFrame(animate);
5608
- } else {
5609
- setDisplayValue(to);
5610
- }
5611
- }
5612
- animationFrameRef.current = requestAnimationFrame(animate);
5613
- return () => {
5614
- if (animationFrameRef.current !== null) {
5615
- cancelAnimationFrame(animationFrameRef.current);
5616
- }
5617
- };
5618
- }, [value, duration]);
5619
- const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5620
- const formattedValue = displayValue.toFixed(decimalPlaces);
5621
- return /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5622
- prefix,
5623
- formattedValue,
5624
- suffix
5625
- ] });
5626
- };
5627
- AnimatedCounter.displayName = "AnimatedCounter";
5628
- }
5629
- });
5630
5570
  function useInfiniteScroll(onLoadMore, options = {}) {
5631
5571
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
5632
5572
  const observerRef = React85.useRef(null);
@@ -8108,15 +8048,15 @@ function HeaderSkeleton({ className }) {
8108
8048
  ] })
8109
8049
  ] });
8110
8050
  }
8111
- function TableSkeleton({ rows = 5, columns = 4, className }) {
8051
+ function TableSkeleton({ rows: rows2 = 5, columns = 4, className }) {
8112
8052
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", className: cn("border border-border rounded-lg overflow-hidden", className), children: [
8113
8053
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "px-4 py-3 bg-muted/30 border-b border-border", children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(SkeletonBlock, { className: "h-4 flex-1 mx-2" }, i)) }),
8114
- Array.from({ length: rows }).map((_, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
8054
+ Array.from({ length: rows2 }).map((_, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
8115
8055
  HStack,
8116
8056
  {
8117
8057
  className: cn(
8118
8058
  "px-4 py-3",
8119
- rowIdx < rows - 1 && "border-b border-border"
8059
+ rowIdx < rows2 - 1 && "border-b border-border"
8120
8060
  ),
8121
8061
  children: Array.from({ length: columns }).map((_2, colIdx) => /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { className: "flex-1 mx-2" }, colIdx))
8122
8062
  },
@@ -8164,18 +8104,18 @@ function CardSkeleton({ className }) {
8164
8104
  }
8165
8105
  );
8166
8106
  }
8167
- function TextSkeleton({ rows = 3, className }) {
8168
- return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
8107
+ function TextSkeleton({ rows: rows2 = 3, className }) {
8108
+ return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows2 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
8169
8109
  SkeletonLine,
8170
8110
  {
8171
- className: i === rows - 1 ? "w-2/3" : "w-full"
8111
+ className: i === rows2 - 1 ? "w-2/3" : "w-full"
8172
8112
  },
8173
8113
  i
8174
8114
  )) });
8175
8115
  }
8176
8116
  function Skeleton({
8177
8117
  variant = "text",
8178
- rows,
8118
+ rows: rows2,
8179
8119
  columns,
8180
8120
  fields,
8181
8121
  className
@@ -8185,15 +8125,15 @@ function Skeleton({
8185
8125
  case "header":
8186
8126
  return /* @__PURE__ */ jsxRuntime.jsx(HeaderSkeleton, { className });
8187
8127
  case "table":
8188
- return /* @__PURE__ */ jsxRuntime.jsx(TableSkeleton, { rows, columns, className });
8128
+ return /* @__PURE__ */ jsxRuntime.jsx(TableSkeleton, { rows: rows2, columns, className });
8189
8129
  case "form":
8190
8130
  return /* @__PURE__ */ jsxRuntime.jsx(FormSkeleton, { fields, className });
8191
8131
  case "card":
8192
8132
  return /* @__PURE__ */ jsxRuntime.jsx(CardSkeleton, { className });
8193
8133
  case "text":
8194
- return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows, className });
8134
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows: rows2, className });
8195
8135
  default:
8196
- return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows, className });
8136
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows: rows2, className });
8197
8137
  }
8198
8138
  }
8199
8139
  var pulseClass;
@@ -9703,7 +9643,7 @@ var init_Menu = __esm({
9703
9643
  className
9704
9644
  }) => {
9705
9645
  const eventBus = useEventBus();
9706
- const { t } = hooks.useTranslate();
9646
+ const { t, direction } = hooks.useTranslate();
9707
9647
  const [isOpen, setIsOpen] = React85.useState(false);
9708
9648
  const [activeSubMenu, setActiveSubMenu] = React85.useState(null);
9709
9649
  const [triggerRect, setTriggerRect] = React85.useState(null);
@@ -9757,6 +9697,18 @@ var init_Menu = __esm({
9757
9697
  "bottom-start": "top-full left-0 mt-2",
9758
9698
  "bottom-end": "top-full right-0 mt-2"
9759
9699
  };
9700
+ const rtlMirror = {
9701
+ "top-left": "top-right",
9702
+ "top-right": "top-left",
9703
+ "bottom-left": "bottom-right",
9704
+ "bottom-right": "bottom-left",
9705
+ "top-start": "top-end",
9706
+ "top-end": "top-start",
9707
+ "bottom-start": "bottom-end",
9708
+ "bottom-end": "bottom-start"
9709
+ };
9710
+ const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
9711
+ const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
9760
9712
  const triggerChild = React85__namespace.default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", as: "span", children: trigger });
9761
9713
  const triggerElement = React85__namespace.default.cloneElement(
9762
9714
  triggerChild,
@@ -9784,7 +9736,7 @@ var init_Menu = __esm({
9784
9736
  onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
9785
9737
  "data-testid": item.event ? `action-${item.event}` : void 0,
9786
9738
  className: cn(
9787
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-left",
9739
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
9788
9740
  "text-sm transition-colors",
9789
9741
  "hover:bg-muted",
9790
9742
  "focus:outline-none focus:bg-muted",
@@ -9803,7 +9755,7 @@ var init_Menu = __esm({
9803
9755
  }
9804
9756
  ),
9805
9757
  item.badge !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
9806
- hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-right", size: "sm", className: "flex-shrink-0" })
9758
+ hasSubMenu && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
9807
9759
  ] })
9808
9760
  },
9809
9761
  itemId
@@ -9823,7 +9775,8 @@ var init_Menu = __esm({
9823
9775
  Box,
9824
9776
  {
9825
9777
  className: cn(
9826
- "absolute left-full top-0 ml-2 z-50",
9778
+ "absolute top-0 z-50",
9779
+ subMenuSideClass,
9827
9780
  menuContainerStyles
9828
9781
  ),
9829
9782
  children: renderMenuItems(item.subMenu)
@@ -9841,12 +9794,12 @@ var init_Menu = __esm({
9841
9794
  className: cn(
9842
9795
  "absolute z-50",
9843
9796
  menuContainerStyles,
9844
- positionClasses3[position],
9797
+ positionClasses3[effectivePosition],
9845
9798
  className
9846
9799
  ),
9847
9800
  style: {
9848
- left: position.includes("left") ? 0 : "auto",
9849
- right: position.includes("right") ? 0 : "auto"
9801
+ left: effectivePosition.includes("left") ? 0 : "auto",
9802
+ right: effectivePosition.includes("right") ? 0 : "auto"
9850
9803
  },
9851
9804
  role: "menu",
9852
9805
  children: renderMenuItems(items)
@@ -10164,7 +10117,7 @@ var init_MapView = __esm({
10164
10117
  shadowSize: [41, 41]
10165
10118
  });
10166
10119
  L.Marker.prototype.options.icon = defaultIcon;
10167
- const { useEffect: useEffect70, useRef: useRef66, useCallback: useCallback113, useState: useState100 } = React85__namespace.default;
10120
+ const { useEffect: useEffect70, useRef: useRef66, useCallback: useCallback114, useState: useState100 } = React85__namespace.default;
10168
10121
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10169
10122
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10170
10123
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10210,7 +10163,7 @@ var init_MapView = __esm({
10210
10163
  }) {
10211
10164
  const eventBus = useEventBus2();
10212
10165
  const [clickedPosition, setClickedPosition] = useState100(null);
10213
- const handleMapClick = useCallback113((lat, lng) => {
10166
+ const handleMapClick = useCallback114((lat, lng) => {
10214
10167
  if (showClickedPin) {
10215
10168
  setClickedPosition({ lat, lng });
10216
10169
  }
@@ -10219,7 +10172,7 @@ var init_MapView = __esm({
10219
10172
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10220
10173
  }
10221
10174
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10222
- const handleMarkerClick = useCallback113((marker) => {
10175
+ const handleMarkerClick = useCallback114((marker) => {
10223
10176
  onMarkerClick?.(marker);
10224
10177
  if (markerClickEvent) {
10225
10178
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -10440,7 +10393,7 @@ function InputPattern({
10440
10393
  function TextareaPattern({
10441
10394
  value = "",
10442
10395
  placeholder,
10443
- rows = 4,
10396
+ rows: rows2 = 4,
10444
10397
  disabled = false,
10445
10398
  fieldError,
10446
10399
  onChange,
@@ -10460,7 +10413,7 @@ function TextareaPattern({
10460
10413
  {
10461
10414
  value: localValue,
10462
10415
  placeholder,
10463
- rows,
10416
+ rows: rows2,
10464
10417
  disabled,
10465
10418
  error: fieldError,
10466
10419
  onChange: handleChange,
@@ -10945,6 +10898,91 @@ var init_ActionPalette = __esm({
10945
10898
  ActionPalette.displayName = "ActionPalette";
10946
10899
  }
10947
10900
  });
10901
+ function parseValue(value) {
10902
+ if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
10903
+ const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
10904
+ if (!match) {
10905
+ return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
10906
+ }
10907
+ const numStr = match[2];
10908
+ const decimalIdx = numStr.indexOf(".");
10909
+ const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
10910
+ return {
10911
+ prefix: match[1],
10912
+ num: parseFloat(numStr),
10913
+ suffix: match[3],
10914
+ decimals
10915
+ };
10916
+ }
10917
+ var AnimatedCounter;
10918
+ var init_AnimatedCounter = __esm({
10919
+ "components/core/molecules/AnimatedCounter.tsx"() {
10920
+ "use client";
10921
+ init_cn();
10922
+ init_Box();
10923
+ init_Typography();
10924
+ AnimatedCounter = ({
10925
+ value,
10926
+ label,
10927
+ duration = 1500,
10928
+ className
10929
+ }) => {
10930
+ const ref = React85.useRef(null);
10931
+ const [displayValue, setDisplayValue] = React85.useState("0");
10932
+ const [hasAnimated, setHasAnimated] = React85.useState(false);
10933
+ const animate = React85.useCallback(() => {
10934
+ const { num: num2, prefix, suffix, decimals } = parseValue(value);
10935
+ if (num2 === 0) {
10936
+ setDisplayValue(String(value));
10937
+ return;
10938
+ }
10939
+ const startTime = performance.now();
10940
+ const tick = (now) => {
10941
+ const elapsed = now - startTime;
10942
+ const progress = Math.min(elapsed / duration, 1);
10943
+ const eased = 1 - Math.pow(1 - progress, 3);
10944
+ const current = eased * num2;
10945
+ setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
10946
+ if (progress < 1) {
10947
+ requestAnimationFrame(tick);
10948
+ } else {
10949
+ setDisplayValue(String(value));
10950
+ }
10951
+ };
10952
+ requestAnimationFrame(tick);
10953
+ }, [value, duration]);
10954
+ React85.useEffect(() => {
10955
+ if (hasAnimated) return;
10956
+ const el = ref.current;
10957
+ if (!el) return;
10958
+ const observer2 = new IntersectionObserver(
10959
+ (entries) => {
10960
+ if (entries[0].isIntersecting) {
10961
+ setHasAnimated(true);
10962
+ animate();
10963
+ observer2.disconnect();
10964
+ }
10965
+ },
10966
+ { threshold: 0.3 }
10967
+ );
10968
+ observer2.observe(el);
10969
+ return () => observer2.disconnect();
10970
+ }, [hasAnimated, animate]);
10971
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
10972
+ /* @__PURE__ */ jsxRuntime.jsx(
10973
+ Typography,
10974
+ {
10975
+ variant: "h2",
10976
+ className: "text-primary font-bold tabular-nums",
10977
+ children: hasAnimated ? displayValue : "0"
10978
+ }
10979
+ ),
10980
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
10981
+ ] });
10982
+ };
10983
+ AnimatedCounter.displayName = "AnimatedCounter";
10984
+ }
10985
+ });
10948
10986
  var AuthLayout;
10949
10987
  var init_AuthLayout = __esm({
10950
10988
  "components/core/templates/AuthLayout.tsx"() {
@@ -12286,6 +12324,39 @@ var init_IsometricCanvas2 = __esm({
12286
12324
  init_IsometricCanvas();
12287
12325
  }
12288
12326
  });
12327
+
12328
+ // components/game/organisms/boardEntity.ts
12329
+ function boardEntity(entity) {
12330
+ if (!entity) return void 0;
12331
+ return Array.isArray(entity) ? entity[0] : entity;
12332
+ }
12333
+ function str(v) {
12334
+ return v == null ? "" : String(v);
12335
+ }
12336
+ function num(v, fallback = 0) {
12337
+ const n = Number(v);
12338
+ return Number.isFinite(n) ? n : fallback;
12339
+ }
12340
+ function rows(v) {
12341
+ return Array.isArray(v) ? v : [];
12342
+ }
12343
+ function vec2(v) {
12344
+ const o = v ?? {};
12345
+ return { x: num(o.x), y: num(o.y) };
12346
+ }
12347
+ function unitPosition(u) {
12348
+ return vec2(u.position);
12349
+ }
12350
+ function unitTeam(u) {
12351
+ return str(u.team);
12352
+ }
12353
+ function unitHealth(u) {
12354
+ return num(u.health);
12355
+ }
12356
+ var init_boardEntity = __esm({
12357
+ "components/game/organisms/boardEntity.ts"() {
12358
+ }
12359
+ });
12289
12360
  function BattleBoard({
12290
12361
  entity,
12291
12362
  scale = 0.45,
@@ -12312,43 +12383,49 @@ function BattleBoard({
12312
12383
  attackEvent,
12313
12384
  className
12314
12385
  }) {
12315
- const tiles = entity.tiles;
12316
- const features = entity.features ?? [];
12317
- const boardWidth = entity.boardWidth ?? 8;
12318
- const boardHeight = entity.boardHeight ?? 6;
12319
- const assetManifest = entity.assetManifest;
12320
- const backgroundImage = entity.backgroundImage;
12321
- const units = entity.units;
12322
- const selectedUnitId = entity.selectedUnitId;
12323
- const currentPhase = entity.phase;
12324
- const currentTurn = entity.turn;
12325
- const gameResult = entity.gameResult;
12386
+ const board = boardEntity(entity) ?? {};
12387
+ const tiles = Array.isArray(board.tiles) ? board.tiles : [];
12388
+ const features = Array.isArray(board.features) ? board.features : [];
12389
+ const boardWidth = num(board.boardWidth, 8);
12390
+ const boardHeight = num(board.boardHeight, 6);
12391
+ const assetManifest = board.assetManifest;
12392
+ const backgroundImage = board.backgroundImage;
12393
+ const units = rows(board.units);
12394
+ const selectedUnitId = board.selectedUnitId ?? null;
12395
+ const currentPhase = str(board.phase) || "observation";
12396
+ const currentTurn = num(board.turn, 1);
12397
+ const gameResult = board.gameResult ?? null;
12326
12398
  const eventBus = useEventBus();
12327
12399
  const { t } = hooks.useTranslate();
12328
12400
  const [hoveredTile, setHoveredTile] = React85.useState(null);
12329
12401
  const [isShaking, setIsShaking] = React85.useState(false);
12330
12402
  const selectedUnit = React85.useMemo(
12331
- () => units.find((u) => u.id === selectedUnitId) ?? null,
12403
+ () => units.find((u) => str(u.id) === selectedUnitId) ?? null,
12332
12404
  [units, selectedUnitId]
12333
12405
  );
12334
12406
  const hoveredUnit = React85.useMemo(() => {
12335
12407
  if (!hoveredTile) return null;
12336
- return units.find(
12337
- (u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
12338
- ) ?? null;
12408
+ return units.find((u) => {
12409
+ const p2 = unitPosition(u);
12410
+ return p2.x === hoveredTile.x && p2.y === hoveredTile.y && unitHealth(u) > 0;
12411
+ }) ?? null;
12339
12412
  }, [hoveredTile, units]);
12340
- const playerUnits = React85.useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
12341
- const enemyUnits = React85.useMemo(() => units.filter((u) => u.team === "enemy" && u.health > 0), [units]);
12413
+ const playerUnits = React85.useMemo(() => units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0), [units]);
12414
+ const enemyUnits = React85.useMemo(() => units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0), [units]);
12342
12415
  const validMoves = React85.useMemo(() => {
12343
12416
  if (!selectedUnit || currentPhase !== "movement") return [];
12344
12417
  const moves = [];
12345
- const range = selectedUnit.movement;
12418
+ const range = num(selectedUnit.movement);
12419
+ const origin = unitPosition(selectedUnit);
12346
12420
  for (let dy = -range; dy <= range; dy++) {
12347
12421
  for (let dx = -range; dx <= range; dx++) {
12348
- const nx = selectedUnit.position.x + dx;
12349
- const ny = selectedUnit.position.y + dy;
12422
+ const nx = origin.x + dx;
12423
+ const ny = origin.y + dy;
12350
12424
  const dist = Math.abs(dx) + Math.abs(dy);
12351
- 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)) {
12425
+ if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => {
12426
+ const p2 = unitPosition(u);
12427
+ return p2.x === nx && p2.y === ny && unitHealth(u) > 0;
12428
+ })) {
12352
12429
  moves.push({ x: nx, y: ny });
12353
12430
  }
12354
12431
  }
@@ -12357,11 +12434,14 @@ function BattleBoard({
12357
12434
  }, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
12358
12435
  const attackTargets = React85.useMemo(() => {
12359
12436
  if (!selectedUnit || currentPhase !== "action") return [];
12360
- return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
12361
- const dx = Math.abs(u.position.x - selectedUnit.position.x);
12362
- const dy = Math.abs(u.position.y - selectedUnit.position.y);
12437
+ const sp = unitPosition(selectedUnit);
12438
+ const sTeam = unitTeam(selectedUnit);
12439
+ return units.filter((u) => unitTeam(u) !== sTeam && unitHealth(u) > 0).filter((u) => {
12440
+ const p2 = unitPosition(u);
12441
+ const dx = Math.abs(p2.x - sp.x);
12442
+ const dy = Math.abs(p2.y - sp.y);
12363
12443
  return dx <= 1 && dy <= 1 && dx + dy > 0;
12364
- }).map((u) => u.position);
12444
+ }).map((u) => unitPosition(u));
12365
12445
  }, [selectedUnit, currentPhase, units]);
12366
12446
  const MOVE_SPEED_MS_PER_TILE = 300;
12367
12447
  const movementAnimRef = React85.useRef(null);
@@ -12401,23 +12481,25 @@ function BattleBoard({
12401
12481
  return () => clearInterval(interval);
12402
12482
  }, []);
12403
12483
  const isoUnits = React85.useMemo(() => {
12404
- return units.filter((u) => u.health > 0).map((unit) => {
12405
- const pos = movingPositions.get(unit.id) ?? unit.position;
12484
+ return units.filter((u) => unitHealth(u) > 0).map((unit) => {
12485
+ const id = str(unit.id);
12486
+ const pos = movingPositions.get(id) ?? unitPosition(unit);
12487
+ const unitTraits = Array.isArray(unit.traits) ? unit.traits : void 0;
12406
12488
  return {
12407
- id: unit.id,
12489
+ id,
12408
12490
  position: pos,
12409
- name: unit.name,
12410
- team: unit.team,
12411
- health: unit.health,
12412
- maxHealth: unit.maxHealth,
12413
- unitType: unit.unitType,
12414
- heroId: unit.heroId,
12415
- sprite: unit.sprite,
12416
- traits: unit.traits?.map((t2) => ({
12417
- name: t2.name,
12418
- currentState: t2.currentState,
12419
- states: t2.states,
12420
- cooldown: t2.cooldown ?? 0
12491
+ name: str(unit.name),
12492
+ team: unitTeam(unit),
12493
+ health: unitHealth(unit),
12494
+ maxHealth: num(unit.maxHealth),
12495
+ unitType: unit.unitType == null ? void 0 : str(unit.unitType),
12496
+ heroId: unit.heroId == null ? void 0 : str(unit.heroId),
12497
+ sprite: unit.sprite == null ? void 0 : str(unit.sprite),
12498
+ traits: unitTraits?.map((tr) => ({
12499
+ name: tr.name,
12500
+ currentState: tr.currentState,
12501
+ states: tr.states,
12502
+ cooldown: tr.cooldown ?? 0
12421
12503
  }))
12422
12504
  };
12423
12505
  });
@@ -12429,8 +12511,8 @@ function BattleBoard({
12429
12511
  [scale, baseOffsetX]
12430
12512
  );
12431
12513
  const checkGameEnd = React85.useCallback(() => {
12432
- const pa = units.filter((u) => u.team === "player" && u.health > 0);
12433
- const ea = units.filter((u) => u.team === "enemy" && u.health > 0);
12514
+ const pa = units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
12515
+ const ea = units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
12434
12516
  if (pa.length === 0) {
12435
12517
  onGameEnd?.("defeat");
12436
12518
  if (gameEndEvent) {
@@ -12444,21 +12526,22 @@ function BattleBoard({
12444
12526
  }
12445
12527
  }, [units, onGameEnd, gameEndEvent, eventBus]);
12446
12528
  const handleUnitClick = React85.useCallback((unitId) => {
12447
- const unit = units.find((u) => u.id === unitId);
12529
+ const unit = units.find((u) => str(u.id) === unitId);
12448
12530
  if (!unit) return;
12449
12531
  if (unitClickEvent) {
12450
12532
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
12451
12533
  }
12452
12534
  if (currentPhase === "action" && selectedUnit) {
12453
- if (unit.team === "enemy" && attackTargets.some((t2) => t2.x === unit.position.x && t2.y === unit.position.y)) {
12454
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
12535
+ const up = unitPosition(unit);
12536
+ if (unitTeam(unit) === "enemy" && attackTargets.some((t2) => t2.x === up.x && t2.y === up.y)) {
12537
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
12455
12538
  setIsShaking(true);
12456
12539
  setTimeout(() => setIsShaking(false), 300);
12457
12540
  onAttack?.(selectedUnit, unit, damage);
12458
12541
  if (attackEvent) {
12459
12542
  eventBus.emit(`UI:${attackEvent}`, {
12460
- attackerId: selectedUnit.id,
12461
- targetId: unit.id,
12543
+ attackerId: str(selectedUnit.id),
12544
+ targetId: str(unit.id),
12462
12545
  damage
12463
12546
  });
12464
12547
  }
@@ -12473,9 +12556,9 @@ function BattleBoard({
12473
12556
  if (currentPhase === "movement" && selectedUnit) {
12474
12557
  if (movementAnimRef.current) return;
12475
12558
  if (validMoves.some((m) => m.x === x && m.y === y)) {
12476
- const from = { ...selectedUnit.position };
12559
+ const from = { ...unitPosition(selectedUnit) };
12477
12560
  const to = { x, y };
12478
- startMoveAnimation(selectedUnit.id, from, to, () => {
12561
+ startMoveAnimation(str(selectedUnit.id), from, to, () => {
12479
12562
  onUnitMove?.(selectedUnit, to);
12480
12563
  });
12481
12564
  }
@@ -12633,6 +12716,7 @@ var init_BattleBoard = __esm({
12633
12716
  init_Typography();
12634
12717
  init_Stack();
12635
12718
  init_IsometricCanvas2();
12719
+ init_boardEntity();
12636
12720
  init_isometric();
12637
12721
  BattleBoard.displayName = "BattleBoard";
12638
12722
  }
@@ -13855,24 +13939,24 @@ var init_CodeBlock = __esm({
13855
13939
  return;
13856
13940
  }
13857
13941
  lineEls.forEach((el) => {
13858
- const num = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13859
- if (hiddenLines.has(num)) {
13942
+ const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13943
+ if (hiddenLines.has(num2)) {
13860
13944
  el.style.display = "none";
13861
13945
  return;
13862
13946
  }
13863
13947
  el.style.display = "";
13864
13948
  el.style.position = "relative";
13865
13949
  el.style.paddingLeft = "1.2em";
13866
- const region = foldStartMap.get(num);
13950
+ const region = foldStartMap.get(num2);
13867
13951
  if (!region) return;
13868
- const isCollapsed = collapsed.has(num);
13952
+ const isCollapsed = collapsed.has(num2);
13869
13953
  const toggle = document.createElement("span");
13870
13954
  toggle.className = "fold-toggle";
13871
13955
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13872
13956
  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%";
13873
13957
  toggle.addEventListener("click", (e) => {
13874
13958
  e.stopPropagation();
13875
- toggleFoldRef.current(num);
13959
+ toggleFoldRef.current(num2);
13876
13960
  });
13877
13961
  el.insertBefore(toggle, el.firstChild);
13878
13962
  if (isCollapsed) {
@@ -16125,10 +16209,13 @@ var init_BookChapterView = __esm({
16125
16209
  init_cn();
16126
16210
  BookChapterView = ({
16127
16211
  chapter,
16212
+ orbitalSchema,
16128
16213
  direction,
16129
16214
  className
16130
16215
  }) => {
16131
16216
  const { t: _t } = hooks.useTranslate();
16217
+ const title = String(chapter.title ?? "");
16218
+ const content = String(chapter.content ?? "");
16132
16219
  return /* @__PURE__ */ jsxRuntime.jsxs(
16133
16220
  VStack,
16134
16221
  {
@@ -16136,16 +16223,16 @@ var init_BookChapterView = __esm({
16136
16223
  className: cn("px-6 py-8 max-w-4xl mx-auto w-full", className),
16137
16224
  style: { direction },
16138
16225
  children: [
16139
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: chapter.title }),
16226
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: title }),
16140
16227
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
16141
- !!chapter.orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
16228
+ !!orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
16142
16229
  JazariStateMachine,
16143
16230
  {
16144
- schema: chapter.orbitalSchema,
16231
+ schema: orbitalSchema,
16145
16232
  direction
16146
16233
  }
16147
16234
  ) }),
16148
- /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content: chapter.content, direction })
16235
+ /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content, direction })
16149
16236
  ]
16150
16237
  }
16151
16238
  );
@@ -16243,7 +16330,7 @@ var init_BookNavBar = __esm({
16243
16330
  BookNavBar = ({
16244
16331
  currentPage,
16245
16332
  totalPages,
16246
- chapterTitle,
16333
+ chapterTitle: chapterTitle2,
16247
16334
  direction,
16248
16335
  className
16249
16336
  }) => {
@@ -16284,12 +16371,12 @@ var init_BookNavBar = __esm({
16284
16371
  )
16285
16372
  ] }),
16286
16373
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex-1 mx-4 max-w-md", children: [
16287
- chapterTitle && /* @__PURE__ */ jsxRuntime.jsx(
16374
+ chapterTitle2 && /* @__PURE__ */ jsxRuntime.jsx(
16288
16375
  Typography,
16289
16376
  {
16290
16377
  variant: "caption",
16291
16378
  className: "text-center block truncate text-muted-foreground",
16292
- children: chapterTitle
16379
+ children: chapterTitle2
16293
16380
  }
16294
16381
  ),
16295
16382
  /* @__PURE__ */ jsxRuntime.jsx(ProgressBar, { value: progress, size: "sm", variant: "primary" })
@@ -16356,31 +16443,35 @@ var init_BookTableOfContents = __esm({
16356
16443
  style: { direction },
16357
16444
  children: [
16358
16445
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold text-center mb-4", children: t("book.tableOfContents") }),
16359
- parts.map((part, partIdx) => /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
16360
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", children: [
16361
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16362
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", className: "font-semibold", children: part.title })
16363
- ] }),
16364
- /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: part.chapters.map((chapter) => {
16365
- const isCurrent = chapter.id === currentChapterId;
16366
- return /* @__PURE__ */ jsxRuntime.jsx(
16367
- Button,
16368
- {
16369
- variant: "ghost",
16370
- size: "sm",
16371
- action: "BOOK_NAVIGATE",
16372
- actionPayload: { chapterId: chapter.id },
16373
- className: cn(
16374
- "justify-start text-left w-full",
16375
- direction === "rtl" && "text-right",
16376
- isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16377
- ),
16378
- children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: chapter.title }) })
16379
- },
16380
- chapter.id
16381
- );
16382
- }) })
16383
- ] }, partIdx))
16446
+ parts.map((part, partIdx) => {
16447
+ const chapters = Array.isArray(part.chapters) ? part.chapters : [];
16448
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
16449
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", children: [
16450
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16451
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", className: "font-semibold", children: String(part.title ?? "") })
16452
+ ] }),
16453
+ /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: chapters.map((chapter) => {
16454
+ const id = chapter.id == null ? "" : String(chapter.id);
16455
+ const isCurrent = id === currentChapterId;
16456
+ return /* @__PURE__ */ jsxRuntime.jsx(
16457
+ Button,
16458
+ {
16459
+ variant: "ghost",
16460
+ size: "sm",
16461
+ action: "BOOK_NAVIGATE",
16462
+ actionPayload: { chapterId: id },
16463
+ className: cn(
16464
+ "justify-start text-left w-full",
16465
+ direction === "rtl" && "text-right",
16466
+ isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16467
+ ),
16468
+ children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: String(chapter.title ?? "") }) })
16469
+ },
16470
+ id
16471
+ );
16472
+ }) })
16473
+ ] }, partIdx);
16474
+ })
16384
16475
  ]
16385
16476
  }
16386
16477
  );
@@ -16502,27 +16593,41 @@ function resolveFieldMap(fieldMap) {
16502
16593
  function get(obj, key) {
16503
16594
  return obj[key];
16504
16595
  }
16596
+ function asStr(v) {
16597
+ return v == null ? "" : String(v);
16598
+ }
16505
16599
  function mapBookData(raw, fields = IDENTITY_BOOK_FIELDS) {
16506
16600
  const rawParts = get(raw, fields.parts) ?? [];
16507
- return {
16508
- title: get(raw, fields.title) ?? "",
16509
- subtitle: get(raw, fields.subtitle),
16510
- author: get(raw, fields.author),
16511
- coverImageUrl: get(raw, fields.coverImageUrl),
16512
- direction: get(raw, fields.direction) ?? void 0,
16513
- parts: rawParts.map((part) => {
16514
- const rawChapters = get(part, fields.chapters) ?? [];
16515
- return {
16516
- title: get(part, fields.partTitle) ?? "",
16517
- chapters: rawChapters.map((ch) => ({
16518
- id: get(ch, fields.chapterId) ?? "",
16519
- title: get(ch, fields.chapterTitle) ?? "",
16520
- content: get(ch, fields.chapterContent) ?? "",
16521
- orbitalSchema: get(ch, fields.chapterOrbitalSchema)
16522
- }))
16523
- };
16524
- })
16601
+ const direction = get(raw, fields.direction) ?? "ltr";
16602
+ const cover = {
16603
+ title: asStr(get(raw, fields.title)),
16604
+ subtitle: asStr(get(raw, fields.subtitle)),
16605
+ author: asStr(get(raw, fields.author)),
16606
+ coverImageUrl: asStr(get(raw, fields.coverImageUrl)),
16607
+ direction
16525
16608
  };
16609
+ const schemaByChapterId = {};
16610
+ const chapters = [];
16611
+ const parts = rawParts.map((part) => {
16612
+ const rawChapters = get(part, fields.chapters) ?? [];
16613
+ const chapterRows = rawChapters.map((ch) => {
16614
+ const id = asStr(get(ch, fields.chapterId));
16615
+ const schema = get(ch, fields.chapterOrbitalSchema);
16616
+ if (schema) schemaByChapterId[id] = schema;
16617
+ const row = {
16618
+ id,
16619
+ title: asStr(get(ch, fields.chapterTitle)),
16620
+ content: asStr(get(ch, fields.chapterContent))
16621
+ };
16622
+ chapters.push(row);
16623
+ return row;
16624
+ });
16625
+ return {
16626
+ title: asStr(get(part, fields.partTitle)),
16627
+ chapters: chapterRows
16628
+ };
16629
+ });
16630
+ return { cover, direction, parts, chapters, schemaByChapterId };
16526
16631
  }
16527
16632
  var IDENTITY_BOOK_FIELDS, AR_BOOK_FIELDS, FIELD_MAP_REGISTRY;
16528
16633
  var init_types2 = __esm({
@@ -16560,10 +16665,7 @@ var init_types2 = __esm({
16560
16665
  };
16561
16666
  }
16562
16667
  });
16563
- function flattenChapters(book) {
16564
- return book.parts.flatMap((part) => part.chapters);
16565
- }
16566
- var PRINT_STYLES, BookViewer;
16668
+ var chapterId, chapterTitle, PRINT_STYLES, BookViewer;
16567
16669
  var init_BookViewer = __esm({
16568
16670
  "components/marketing/organisms/book/BookViewer.tsx"() {
16569
16671
  init_Box();
@@ -16576,6 +16678,8 @@ var init_BookViewer = __esm({
16576
16678
  init_BookNavBar();
16577
16679
  init_EmptyState();
16578
16680
  init_types2();
16681
+ chapterId = (ch) => ch?.id == null ? void 0 : String(ch.id);
16682
+ chapterTitle = (ch) => ch?.title == null ? void 0 : String(ch.title);
16579
16683
  PRINT_STYLES = `
16580
16684
  @media print {
16581
16685
  .book-viewer-page {
@@ -16604,14 +16708,14 @@ var init_BookViewer = __esm({
16604
16708
  return mapBookData(raw, resolvedFieldMap);
16605
16709
  }, [entity, resolvedFieldMap]);
16606
16710
  const direction = book?.direction ?? "ltr";
16607
- const chapters = React85.useMemo(() => book ? flattenChapters(book) : [], [book]);
16711
+ const chapters = React85.useMemo(() => book ? book.chapters : [], [book]);
16608
16712
  const totalPages = 2 + chapters.length;
16609
16713
  const navigateTo = React85.useCallback(
16610
16714
  (page) => {
16611
16715
  const clamped = Math.max(0, Math.min(page, totalPages - 1));
16612
16716
  setCurrentPage(clamped);
16613
- const chapterId = clamped >= 2 ? chapters[clamped - 2]?.id : void 0;
16614
- eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId });
16717
+ const id = clamped >= 2 ? chapterId(chapters[clamped - 2]) : void 0;
16718
+ eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId: id });
16615
16719
  },
16616
16720
  [totalPages, chapters, eventBus]
16617
16721
  );
@@ -16623,8 +16727,8 @@ var init_BookViewer = __esm({
16623
16727
  eventBus.on("UI:BOOK_PAGE_NEXT", () => navigateTo(currentPage + 1)),
16624
16728
  eventBus.on("UI:BOOK_PRINT", () => window.print()),
16625
16729
  eventBus.on("UI:BOOK_NAVIGATE", (event) => {
16626
- const chapterId = event.payload?.chapterId;
16627
- const idx = chapters.findIndex((ch) => ch.id === chapterId);
16730
+ const targetId = event.payload?.chapterId;
16731
+ const idx = chapters.findIndex((ch) => chapterId(ch) === targetId);
16628
16732
  if (idx >= 0) navigateTo(idx + 2);
16629
16733
  })
16630
16734
  ];
@@ -16641,9 +16745,11 @@ var init_BookViewer = __esm({
16641
16745
  style.remove();
16642
16746
  };
16643
16747
  }, []);
16644
- const currentChapterId = currentPage >= 2 ? chapters[currentPage - 2]?.id : void 0;
16645
- const currentChapterTitle = currentPage >= 2 ? chapters[currentPage - 2]?.title : void 0;
16748
+ const currentChapterId = currentPage >= 2 ? chapterId(chapters[currentPage - 2]) : void 0;
16749
+ const currentChapterTitle = currentPage >= 2 ? chapterTitle(chapters[currentPage - 2]) : void 0;
16646
16750
  if (!book) return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { message: t("book.noData") });
16751
+ const cover = book.cover;
16752
+ const coverTitle = String(cover.title ?? "");
16647
16753
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("relative h-full overflow-hidden bg-background", className), children: [
16648
16754
  /* @__PURE__ */ jsxRuntime.jsxs(
16649
16755
  Box,
@@ -16655,10 +16761,10 @@ var init_BookViewer = __esm({
16655
16761
  /* @__PURE__ */ jsxRuntime.jsx(
16656
16762
  BookCoverPage,
16657
16763
  {
16658
- title: book.title,
16659
- subtitle: book.subtitle,
16660
- author: book.author,
16661
- coverImageUrl: book.coverImageUrl,
16764
+ title: coverTitle,
16765
+ subtitle: String(cover.subtitle ?? "") || void 0,
16766
+ author: String(cover.author ?? "") || void 0,
16767
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16662
16768
  direction
16663
16769
  }
16664
16770
  ),
@@ -16669,23 +16775,27 @@ var init_BookViewer = __esm({
16669
16775
  direction
16670
16776
  }
16671
16777
  ),
16672
- chapters.map((chapter) => /* @__PURE__ */ jsxRuntime.jsx(
16673
- BookChapterView,
16674
- {
16675
- chapter,
16676
- direction
16677
- },
16678
- chapter.id
16679
- ))
16778
+ chapters.map((chapter) => {
16779
+ const id = chapterId(chapter);
16780
+ return /* @__PURE__ */ jsxRuntime.jsx(
16781
+ BookChapterView,
16782
+ {
16783
+ chapter,
16784
+ orbitalSchema: id ? book.schemaByChapterId[id] : void 0,
16785
+ direction
16786
+ },
16787
+ id
16788
+ );
16789
+ })
16680
16790
  ] }),
16681
16791
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "print:hidden", children: [
16682
16792
  currentPage === 0 && /* @__PURE__ */ jsxRuntime.jsx(
16683
16793
  BookCoverPage,
16684
16794
  {
16685
- title: book.title,
16686
- subtitle: book.subtitle,
16687
- author: book.author,
16688
- coverImageUrl: book.coverImageUrl,
16795
+ title: coverTitle,
16796
+ subtitle: String(cover.subtitle ?? "") || void 0,
16797
+ author: String(cover.author ?? "") || void 0,
16798
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16689
16799
  direction
16690
16800
  }
16691
16801
  ),
@@ -16701,6 +16811,7 @@ var init_BookViewer = __esm({
16701
16811
  BookChapterView,
16702
16812
  {
16703
16813
  chapter: chapters[currentPage - 2],
16814
+ orbitalSchema: currentChapterId ? book.schemaByChapterId[currentChapterId] : void 0,
16704
16815
  direction
16705
16816
  }
16706
16817
  )
@@ -16713,7 +16824,7 @@ var init_BookViewer = __esm({
16713
16824
  {
16714
16825
  currentPage,
16715
16826
  totalPages,
16716
- chapterTitle: currentPage === 0 ? book.title : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16827
+ chapterTitle: currentPage === 0 ? coverTitle : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16717
16828
  direction
16718
16829
  }
16719
16830
  )
@@ -16811,7 +16922,7 @@ var init_Grid = __esm({
16811
16922
  };
16812
16923
  Grid = ({
16813
16924
  cols = 1,
16814
- rows,
16925
+ rows: rows2,
16815
16926
  gap = "md",
16816
16927
  rowGap,
16817
16928
  colGap,
@@ -16823,7 +16934,7 @@ var init_Grid = __esm({
16823
16934
  children,
16824
16935
  as: Component = "div"
16825
16936
  }) => {
16826
- const mergedStyle = rows ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, ...style } : style;
16937
+ const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
16827
16938
  return React85__namespace.default.createElement(
16828
16939
  Component,
16829
16940
  {
@@ -17539,14 +17650,14 @@ function BuilderBoard({
17539
17650
  }) {
17540
17651
  const { emit } = useEventBus();
17541
17652
  const { t } = hooks.useTranslate();
17542
- const resolved = Array.isArray(entity) ? entity[0] : entity;
17653
+ const resolved = boardEntity(entity);
17543
17654
  const [placements, setPlacements] = React85.useState({});
17544
17655
  const [headerError, setHeaderError] = React85.useState(false);
17545
17656
  const [submitted, setSubmitted] = React85.useState(false);
17546
17657
  const [attempts, setAttempts] = React85.useState(0);
17547
17658
  const [showHint, setShowHint] = React85.useState(false);
17548
- const components = resolved?.components ?? [];
17549
- const slots = resolved?.slots ?? [];
17659
+ const components = Array.isArray(resolved?.components) ? resolved.components : [];
17660
+ const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
17550
17661
  const usedComponentIds = new Set(Object.values(placements));
17551
17662
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
17552
17663
  const [selectedComponent, setSelectedComponent] = React85.useState(null);
@@ -17580,7 +17691,7 @@ function BuilderBoard({
17580
17691
  }, [slots, placements, attempts, completeEvent, emit]);
17581
17692
  const handleReset = () => {
17582
17693
  setSubmitted(false);
17583
- if (attempts >= 2 && resolved?.hint) {
17694
+ if (attempts >= 2 && str(resolved?.hint)) {
17584
17695
  setShowHint(true);
17585
17696
  }
17586
17697
  };
@@ -17593,20 +17704,24 @@ function BuilderBoard({
17593
17704
  };
17594
17705
  const getComponentById = (id) => components.find((c) => c.id === id);
17595
17706
  if (!resolved) return null;
17707
+ const theme = resolved.theme ?? void 0;
17708
+ const themeBackground = theme?.background;
17709
+ const headerImage = str(resolved.headerImage);
17710
+ const hint = str(resolved.hint);
17596
17711
  return /* @__PURE__ */ jsxRuntime.jsx(
17597
17712
  Box,
17598
17713
  {
17599
17714
  className,
17600
17715
  style: {
17601
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
17716
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
17602
17717
  backgroundSize: "cover",
17603
17718
  backgroundPosition: "center"
17604
17719
  },
17605
17720
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
17606
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
17721
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
17607
17722
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17608
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
17609
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
17723
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
17724
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
17610
17725
  ] }) }),
17611
17726
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17612
17727
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.components") }),
@@ -17666,9 +17781,9 @@ function BuilderBoard({
17666
17781
  ] }) }),
17667
17782
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
17668
17783
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
17669
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("builder.success") : resolved.failMessage ?? t("builder.incorrect") })
17784
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
17670
17785
  ] }) }),
17671
- showHint && resolved.hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.hint }) }),
17786
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
17672
17787
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
17673
17788
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17674
17789
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Wrench, size: "sm" }),
@@ -17687,6 +17802,7 @@ var init_BuilderBoard = __esm({
17687
17802
  "components/game/organisms/puzzles/builder/BuilderBoard.tsx"() {
17688
17803
  init_atoms2();
17689
17804
  init_useEventBus();
17805
+ init_boardEntity();
17690
17806
  BuilderBoard.displayName = "BuilderBoard";
17691
17807
  }
17692
17808
  });
@@ -18024,21 +18140,24 @@ function CalendarGrid({
18024
18140
  eventBus.emit(`UI:${longPressEvent}`, { date: day.toISOString(), time, ...longPressPayload });
18025
18141
  }, 500);
18026
18142
  }, [longPressEvent, longPressPayload, eventBus]);
18027
- const renderEvent = (event) => /* @__PURE__ */ jsxRuntime.jsx(
18028
- Box,
18029
- {
18030
- rounded: "md",
18031
- padding: "xs",
18032
- border: true,
18033
- className: cn(
18034
- "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
18035
- event.color ? event.color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
18036
- ),
18037
- onClick: (e) => handleEventClick(event, e),
18038
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
18039
- },
18040
- event.id
18041
- );
18143
+ const renderEvent = (event) => {
18144
+ const color = event.color;
18145
+ return /* @__PURE__ */ jsxRuntime.jsx(
18146
+ Box,
18147
+ {
18148
+ rounded: "md",
18149
+ padding: "xs",
18150
+ border: true,
18151
+ className: cn(
18152
+ "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
18153
+ color ? color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
18154
+ ),
18155
+ onClick: (e) => handleEventClick(event, e),
18156
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
18157
+ },
18158
+ event.id
18159
+ );
18160
+ };
18042
18161
  return /* @__PURE__ */ jsxRuntime.jsxs(
18043
18162
  Box,
18044
18163
  {
@@ -19702,7 +19821,6 @@ var init_CardGrid = __esm({
19702
19821
  alignItems = "stretch",
19703
19822
  className,
19704
19823
  children,
19705
- // EntityDisplayProps
19706
19824
  entity,
19707
19825
  isLoading = false,
19708
19826
  error = null,
@@ -20174,14 +20292,14 @@ var init_CaseStudyOrganism = __esm({
20174
20292
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((study) => /* @__PURE__ */ jsxRuntime.jsx(
20175
20293
  CaseStudyCard,
20176
20294
  {
20177
- title: study.title,
20178
- description: study.description,
20179
- category: study.category,
20180
- categoryColor: study.categoryColor,
20181
- href: study.href,
20182
- linkLabel: study.linkLabel
20295
+ title: String(study.title ?? ""),
20296
+ description: String(study.description ?? ""),
20297
+ category: String(study.category ?? ""),
20298
+ categoryColor: study.categoryColor != null ? String(study.categoryColor) : void 0,
20299
+ href: String(study.href ?? ""),
20300
+ linkLabel: study.linkLabel != null ? String(study.linkLabel) : void 0
20183
20301
  },
20184
- study.id
20302
+ String(study.id ?? "")
20185
20303
  )) })
20186
20304
  ] });
20187
20305
  };
@@ -20204,10 +20322,10 @@ function CastleBoard({
20204
20322
  className
20205
20323
  }) {
20206
20324
  const eventBus = useEventBus();
20207
- const resolved = Array.isArray(entity) ? entity[0] : entity;
20208
- const tiles = resolved?.tiles ?? [];
20209
- const features = resolved?.features ?? [];
20210
- const units = resolved?.units ?? [];
20325
+ const resolved = boardEntity(entity);
20326
+ const tiles = Array.isArray(resolved?.tiles) ? resolved.tiles : [];
20327
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
20328
+ const units = Array.isArray(resolved?.units) ? resolved.units : [];
20211
20329
  const assetManifest = resolved?.assetManifest;
20212
20330
  const backgroundImage = resolved?.backgroundImage;
20213
20331
  const [hoveredTile, setHoveredTile] = React85.useState(null);
@@ -20235,7 +20353,7 @@ function CastleBoard({
20235
20353
  onFeatureClick?.(feature);
20236
20354
  if (featureClickEvent) {
20237
20355
  eventBus.emit(`UI:${featureClickEvent}`, {
20238
- featureId: feature.id,
20356
+ featureId: feature.id ?? "",
20239
20357
  featureType: feature.type,
20240
20358
  x: feature.x,
20241
20359
  y: feature.y
@@ -20303,6 +20421,7 @@ var init_CastleBoard = __esm({
20303
20421
  init_cn();
20304
20422
  init_useEventBus();
20305
20423
  init_IsometricCanvas2();
20424
+ init_boardEntity();
20306
20425
  init_isometric();
20307
20426
  CastleBoard.displayName = "CastleBoard";
20308
20427
  }
@@ -21113,14 +21232,14 @@ function ClassifierBoard({
21113
21232
  }) {
21114
21233
  const { emit } = useEventBus();
21115
21234
  const { t } = hooks.useTranslate();
21116
- const resolved = Array.isArray(entity) ? entity[0] : entity;
21235
+ const resolved = boardEntity(entity);
21117
21236
  const [assignments, setAssignments] = React85.useState({});
21118
21237
  const [headerError, setHeaderError] = React85.useState(false);
21119
21238
  const [submitted, setSubmitted] = React85.useState(false);
21120
21239
  const [attempts, setAttempts] = React85.useState(0);
21121
21240
  const [showHint, setShowHint] = React85.useState(false);
21122
- const items = resolved?.items ?? [];
21123
- const categories = resolved?.categories ?? [];
21241
+ const items = Array.isArray(resolved?.items) ? resolved.items : [];
21242
+ const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21124
21243
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21125
21244
  const allAssigned = Object.keys(assignments).length === items.length;
21126
21245
  const results = submitted ? items.map((item) => ({
@@ -21152,7 +21271,7 @@ function ClassifierBoard({
21152
21271
  }, [items, assignments, attempts, completeEvent, emit]);
21153
21272
  const handleReset = () => {
21154
21273
  setSubmitted(false);
21155
- if (attempts >= 2 && resolved?.hint) {
21274
+ if (attempts >= 2 && str(resolved?.hint)) {
21156
21275
  setShowHint(true);
21157
21276
  }
21158
21277
  };
@@ -21163,20 +21282,25 @@ function ClassifierBoard({
21163
21282
  setShowHint(false);
21164
21283
  };
21165
21284
  if (!resolved) return null;
21285
+ const theme = resolved.theme ?? void 0;
21286
+ const themeBackground = theme?.background;
21287
+ const headerImage = str(resolved.headerImage);
21288
+ const hint = str(resolved.hint);
21289
+ const failMessage = str(resolved.failMessage);
21166
21290
  return /* @__PURE__ */ jsxRuntime.jsx(
21167
21291
  Box,
21168
21292
  {
21169
21293
  className,
21170
21294
  style: {
21171
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
21295
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
21172
21296
  backgroundSize: "cover",
21173
21297
  backgroundPosition: "center"
21174
21298
  },
21175
21299
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
21176
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
21300
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
21177
21301
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21178
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
21179
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
21302
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
21303
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
21180
21304
  ] }) }),
21181
21305
  unassignedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21182
21306
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("classifier.itemsToSort") }),
@@ -21228,10 +21352,10 @@ function ClassifierBoard({
21228
21352
  }) }),
21229
21353
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
21230
21354
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
21231
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
21232
- !allCorrect && resolved.failMessage && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "text-muted-foreground", children: resolved.failMessage })
21355
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
21356
+ !allCorrect && failMessage && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "text-muted-foreground", children: failMessage })
21233
21357
  ] }) }),
21234
- showHint && resolved.hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.hint }) }),
21358
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
21235
21359
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
21236
21360
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21237
21361
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -21250,6 +21374,7 @@ var init_ClassifierBoard = __esm({
21250
21374
  "components/game/organisms/puzzles/classifier/ClassifierBoard.tsx"() {
21251
21375
  init_atoms2();
21252
21376
  init_useEventBus();
21377
+ init_boardEntity();
21253
21378
  ClassifierBoard.displayName = "ClassifierBoard";
21254
21379
  }
21255
21380
  });
@@ -27620,7 +27745,7 @@ function InventoryPanel({
27620
27745
  const slotArray = Array.from({ length: safeSlots }, (_, index) => {
27621
27746
  return safeItems[index] ?? null;
27622
27747
  });
27623
- const rows = Math.ceil(safeSlots / safeColumns);
27748
+ const rows2 = Math.ceil(safeSlots / safeColumns);
27624
27749
  const handleSlotClick = React85.useCallback((index) => {
27625
27750
  if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
27626
27751
  onSelectSlot?.(index);
@@ -27689,7 +27814,7 @@ function InventoryPanel({
27689
27814
  className: "grid gap-1 bg-[var(--color-card)] p-2 rounded-container border border-border",
27690
27815
  style: {
27691
27816
  gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
27692
- gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
27817
+ gridTemplateRows: `repeat(${rows2}, ${slotSize}px)`
27693
27818
  },
27694
27819
  children: slotArray.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
27695
27820
  "button",
@@ -31653,11 +31778,11 @@ function LatticeSVG({
31653
31778
  }) {
31654
31779
  const paths = [];
31655
31780
  const cols = 5;
31656
- const rows = Math.ceil(h / (w / cols));
31781
+ const rows2 = Math.ceil(h / (w / cols));
31657
31782
  const cellW = w / cols;
31658
31783
  const cellH = cellW;
31659
31784
  const bulge = cellW * 0.3;
31660
- for (let row = 0; row < rows; row++) {
31785
+ for (let row = 0; row < rows2; row++) {
31661
31786
  for (let col = 0; col < cols; col++) {
31662
31787
  const cx = col * cellW + cellW / 2;
31663
31788
  const cy = row * cellH + cellH / 2;
@@ -32069,7 +32194,7 @@ var init_MatrixQuestion = __esm({
32069
32194
  };
32070
32195
  MatrixQuestion = ({
32071
32196
  title,
32072
- rows,
32197
+ rows: rows2,
32073
32198
  columns = DEFAULT_MATRIX_COLUMNS,
32074
32199
  values,
32075
32200
  onChange,
@@ -32079,7 +32204,7 @@ var init_MatrixQuestion = __esm({
32079
32204
  className
32080
32205
  }) => {
32081
32206
  const styles = sizeStyles13[size];
32082
- const safeRows = rows ?? [];
32207
+ const safeRows = rows2 ?? [];
32083
32208
  const safeValues = values ?? {};
32084
32209
  const eventBus = useEventBus();
32085
32210
  const handleChange = React85.useCallback(
@@ -32707,7 +32832,8 @@ var init_PositionedCanvas = __esm({
32707
32832
  dragRef.current = null;
32708
32833
  setDraggingId(null);
32709
32834
  if (!wasDrag) {
32710
- const next = selectedId === item.id ? null : item.id;
32835
+ const itemId = item.id;
32836
+ const next = selectedId === itemId ? null : itemId;
32711
32837
  onSelect?.(next);
32712
32838
  if (selectEvent) {
32713
32839
  eventBus.emit(`UI:${selectEvent}`, { id: next });
@@ -32742,15 +32868,22 @@ var init_PositionedCanvas = __esm({
32742
32868
  style: { width, height },
32743
32869
  onClick: handleContainerClick,
32744
32870
  children: items.map((item) => {
32871
+ const itemId = item.id;
32872
+ const label = item.label;
32873
+ const x = item.x;
32874
+ const y = item.y;
32875
+ const capacity = item.capacity;
32876
+ const partySize = item.partySize;
32877
+ const serverName = item.serverName;
32745
32878
  const status = item.status ?? "empty";
32746
32879
  const shape = item.shape ?? "round";
32747
- const isSelected = selectedId === item.id;
32748
- const isDragging = draggingId === item.id;
32880
+ const isSelected = selectedId === itemId;
32881
+ const isDragging = draggingId === itemId;
32749
32882
  const statusBadge = STATUS_BADGE[status];
32750
32883
  return /* @__PURE__ */ jsxRuntime.jsxs(
32751
32884
  Box,
32752
32885
  {
32753
- "data-testid": `item-node-${item.id}`,
32886
+ "data-testid": `item-node-${itemId}`,
32754
32887
  "data-status": status,
32755
32888
  className: cn(
32756
32889
  "absolute flex flex-col items-center justify-center gap-1 border-2 select-none",
@@ -32761,7 +32894,7 @@ var init_PositionedCanvas = __esm({
32761
32894
  isSelected && "outline outline-2 outline-offset-2 outline-primary shadow-md",
32762
32895
  isDragging && "shadow-lg z-10"
32763
32896
  ),
32764
- style: { left: item.x, top: item.y, touchAction: "none" },
32897
+ style: { left: x, top: y, touchAction: "none" },
32765
32898
  onPointerDown: (e) => handlePointerDown(e, item),
32766
32899
  onPointerMove: handlePointerMove,
32767
32900
  onPointerUp: (e) => handlePointerUp(e, item),
@@ -32769,10 +32902,10 @@ var init_PositionedCanvas = __esm({
32769
32902
  children: [
32770
32903
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1", children: [
32771
32904
  getStatusIcon(status),
32772
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: item.label })
32905
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: label })
32773
32906
  ] }),
32774
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: item.partySize !== void 0 && status === "seated" ? `${item.partySize}/${item.capacity}` : `Cap ${item.capacity}` }),
32775
- status === "seated" && item.serverName && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: item.serverName }),
32907
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: partySize !== void 0 && status === "seated" ? `${partySize}/${capacity}` : `Cap ${capacity}` }),
32908
+ status === "seated" && serverName && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: serverName }),
32776
32909
  isSelected && /* @__PURE__ */ jsxRuntime.jsx(
32777
32910
  Badge,
32778
32911
  {
@@ -32784,7 +32917,7 @@ var init_PositionedCanvas = __esm({
32784
32917
  )
32785
32918
  ]
32786
32919
  },
32787
- item.id
32920
+ itemId
32788
32921
  );
32789
32922
  })
32790
32923
  }
@@ -33556,9 +33689,10 @@ var init_RichBlockEditor = __esm({
33556
33689
  });
33557
33690
  function collectInitiallyCollapsed(nodes, acc) {
33558
33691
  for (const n of nodes) {
33692
+ const replies = n.replies;
33559
33693
  if (n.collapsed) acc.add(n.id);
33560
- if (n.replies && n.replies.length > 0) {
33561
- collectInitiallyCollapsed(n.replies, acc);
33694
+ if (replies && replies.length > 0) {
33695
+ collectInitiallyCollapsed(replies, acc);
33562
33696
  }
33563
33697
  }
33564
33698
  }
@@ -33588,44 +33722,52 @@ var init_ReplyTree = __esm({
33588
33722
  }) => {
33589
33723
  const eventBus = useEventBus();
33590
33724
  const { t } = hooks.useTranslate();
33591
- const hasReplies = !!node.replies && node.replies.length > 0;
33592
- const isCollapsed = collapsedSet.has(node.id);
33725
+ const nodeId = node.id;
33726
+ const authorName = node.authorName;
33727
+ const authorAvatarUrl = node.authorAvatarUrl;
33728
+ const content = node.content;
33729
+ const postedAt = node.postedAt;
33730
+ const voteCount = node.voteCount;
33731
+ const userVote = node.userVote;
33732
+ const replies = node.replies;
33733
+ const hasReplies = !!replies && replies.length > 0;
33734
+ const isCollapsed = collapsedSet.has(nodeId);
33593
33735
  const atMaxDepth = depth >= maxDepth;
33594
33736
  const [replyOpen, setReplyOpen] = React85.useState(false);
33595
33737
  const [draft, setDraft] = React85.useState("");
33596
33738
  const handleVote = React85.useCallback(
33597
33739
  (next) => {
33598
- onVote?.(node.id, next);
33599
- if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId: node.id, vote: next });
33740
+ onVote?.(nodeId, next);
33741
+ if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId, vote: next });
33600
33742
  },
33601
- [node.id, onVote, voteEvent, eventBus]
33743
+ [nodeId, onVote, voteEvent, eventBus]
33602
33744
  );
33603
33745
  const handleReply = React85.useCallback(() => {
33604
- onReply?.(node.id);
33746
+ onReply?.(nodeId);
33605
33747
  setReplyOpen((open) => !open);
33606
- }, [node.id, onReply]);
33748
+ }, [nodeId, onReply]);
33607
33749
  const handleSubmitReply = React85.useCallback(() => {
33608
- const content = draft.trim();
33609
- if (!content) return;
33610
- if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: node.id, content });
33750
+ const text = draft.trim();
33751
+ if (!text) return;
33752
+ if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: nodeId, content: text });
33611
33753
  setDraft("");
33612
33754
  setReplyOpen(false);
33613
- }, [node.id, draft, replyEvent, eventBus]);
33755
+ }, [nodeId, draft, replyEvent, eventBus]);
33614
33756
  const handleCancelReply = React85.useCallback(() => {
33615
33757
  setDraft("");
33616
33758
  setReplyOpen(false);
33617
33759
  }, []);
33618
33760
  const handleFlag = React85.useCallback(() => {
33619
- onFlag?.(node.id);
33620
- if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId: node.id });
33621
- }, [node.id, onFlag, flagEvent, eventBus]);
33761
+ onFlag?.(nodeId);
33762
+ if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId });
33763
+ }, [nodeId, onFlag, flagEvent, eventBus]);
33622
33764
  const handleContinue = React85.useCallback(() => {
33623
- onContinueThread?.(node.id);
33624
- if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId: node.id });
33625
- }, [node.id, onContinueThread, continueThreadEvent, eventBus]);
33765
+ onContinueThread?.(nodeId);
33766
+ if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId });
33767
+ }, [nodeId, onContinueThread, continueThreadEvent, eventBus]);
33626
33768
  const handleToggle = React85.useCallback(() => {
33627
- toggleCollapse(node.id);
33628
- }, [node.id, toggleCollapse]);
33769
+ toggleCollapse(nodeId);
33770
+ }, [nodeId, toggleCollapse]);
33629
33771
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-stretch min-w-0", children: [
33630
33772
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-col items-center flex-shrink-0 w-6", children: [
33631
33773
  hasReplies ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -33657,25 +33799,25 @@ var init_ReplyTree = __esm({
33657
33799
  /* @__PURE__ */ jsxRuntime.jsx(
33658
33800
  Avatar,
33659
33801
  {
33660
- src: node.authorAvatarUrl,
33661
- name: node.authorName,
33802
+ src: authorAvatarUrl,
33803
+ name: authorName,
33662
33804
  size: "sm"
33663
33805
  }
33664
33806
  ),
33665
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: node.authorName }),
33666
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: node.postedAt })
33807
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: authorName }),
33808
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: postedAt })
33667
33809
  ] }),
33668
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: node.content }),
33810
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: content }),
33669
33811
  showActions && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
33670
33812
  /* @__PURE__ */ jsxRuntime.jsx(
33671
33813
  VoteStack,
33672
33814
  {
33673
- count: node.voteCount ?? 0,
33674
- userVote: node.userVote ?? null,
33815
+ count: voteCount ?? 0,
33816
+ userVote: userVote ?? null,
33675
33817
  onVote: handleVote,
33676
33818
  size: "sm",
33677
33819
  variant: "horizontal",
33678
- label: t("replyTree.voteOnReplyBy", { author: node.authorName })
33820
+ label: t("replyTree.voteOnReplyBy", { author: authorName })
33679
33821
  }
33680
33822
  ),
33681
33823
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -33685,7 +33827,7 @@ var init_ReplyTree = __esm({
33685
33827
  size: "sm",
33686
33828
  leftIcon: "message-square",
33687
33829
  onClick: handleReply,
33688
- "aria-label": t("replyTree.replyTo", { author: node.authorName }),
33830
+ "aria-label": t("replyTree.replyTo", { author: authorName }),
33689
33831
  children: t("replyTree.reply")
33690
33832
  }
33691
33833
  ),
@@ -33696,7 +33838,7 @@ var init_ReplyTree = __esm({
33696
33838
  size: "sm",
33697
33839
  leftIcon: "flag",
33698
33840
  onClick: handleFlag,
33699
- "aria-label": t("replyTree.flagReplyBy", { author: node.authorName }),
33841
+ "aria-label": t("replyTree.flagReplyBy", { author: authorName }),
33700
33842
  children: t("replyTree.flag")
33701
33843
  }
33702
33844
  )
@@ -33708,9 +33850,9 @@ var init_ReplyTree = __esm({
33708
33850
  inputType: "textarea",
33709
33851
  rows: 2,
33710
33852
  value: draft,
33711
- placeholder: t("replyTree.replyToPlaceholder", { author: node.authorName }),
33853
+ placeholder: t("replyTree.replyToPlaceholder", { author: authorName }),
33712
33854
  onChange: (e) => setDraft(e.target.value),
33713
- "aria-label": t("replyTree.replyTo", { author: node.authorName })
33855
+ "aria-label": t("replyTree.replyTo", { author: authorName })
33714
33856
  }
33715
33857
  ),
33716
33858
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
@@ -33741,7 +33883,7 @@ var init_ReplyTree = __esm({
33741
33883
  ),
33742
33884
  children: t("replyTree.continueThread")
33743
33885
  }
33744
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-2 mt-1", children: node.replies.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
33886
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-2 mt-1", children: replies.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
33745
33887
  ReplyTreeNode,
33746
33888
  {
33747
33889
  node: child,
@@ -36061,8 +36203,8 @@ var init_WizardContainer = __esm({
36061
36203
  return void 0;
36062
36204
  if (typeof controlledStep === "number") return controlledStep;
36063
36205
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
36064
- const num = Number(controlledStep);
36065
- return isNaN(num) ? void 0 : num;
36206
+ const num2 = Number(controlledStep);
36207
+ return isNaN(num2) ? void 0 : num2;
36066
36208
  })();
36067
36209
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
36068
36210
  const totalSteps = steps.length;
@@ -37767,7 +37909,7 @@ function DebuggerBoard({
37767
37909
  }) {
37768
37910
  const { emit } = useEventBus();
37769
37911
  const { t } = hooks.useTranslate();
37770
- const resolved = Array.isArray(entity) ? entity[0] : entity;
37912
+ const resolved = boardEntity(entity);
37771
37913
  const [flaggedLines, setFlaggedLines] = React85.useState(/* @__PURE__ */ new Set());
37772
37914
  const [headerError, setHeaderError] = React85.useState(false);
37773
37915
  const [submitted, setSubmitted] = React85.useState(false);
@@ -37785,7 +37927,7 @@ function DebuggerBoard({
37785
37927
  return next;
37786
37928
  });
37787
37929
  };
37788
- const lines = resolved?.lines ?? [];
37930
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37789
37931
  const bugLines = lines.filter((l) => l.isBug);
37790
37932
  const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
37791
37933
  const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
@@ -37800,7 +37942,7 @@ function DebuggerBoard({
37800
37942
  }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37801
37943
  const handleReset = () => {
37802
37944
  setSubmitted(false);
37803
- if (attempts >= 2 && resolved?.hint) {
37945
+ if (attempts >= 2 && str(resolved?.hint)) {
37804
37946
  setShowHint(true);
37805
37947
  }
37806
37948
  };
@@ -37811,24 +37953,28 @@ function DebuggerBoard({
37811
37953
  setShowHint(false);
37812
37954
  };
37813
37955
  if (!resolved) return null;
37956
+ const theme = resolved.theme ?? void 0;
37957
+ const themeBackground = theme?.background;
37958
+ const headerImage = str(resolved.headerImage);
37959
+ const hint = str(resolved.hint);
37814
37960
  return /* @__PURE__ */ jsxRuntime.jsx(
37815
37961
  Box,
37816
37962
  {
37817
37963
  className,
37818
37964
  style: {
37819
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
37965
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
37820
37966
  backgroundSize: "cover",
37821
37967
  backgroundPosition: "center"
37822
37968
  },
37823
37969
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
37824
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
37970
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
37825
37971
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37826
37972
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
37827
37973
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Bug, size: "sm" }),
37828
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title })
37974
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
37829
37975
  ] }),
37830
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description }),
37831
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(resolved.bugCount) }) })
37976
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
37977
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
37832
37978
  ] }) }),
37833
37979
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
37834
37980
  const isFlagged = flaggedLines.has(line.id);
@@ -37860,7 +38006,7 @@ function DebuggerBoard({
37860
38006
  );
37861
38007
  }) }) }),
37862
38008
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37863
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
38009
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
37864
38010
  bugLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "start", children: [
37865
38011
  /* @__PURE__ */ jsxRuntime.jsx(
37866
38012
  Icon,
@@ -37876,7 +38022,7 @@ function DebuggerBoard({
37876
38022
  ] })
37877
38023
  ] }, line.id))
37878
38024
  ] }) }),
37879
- showHint && resolved.hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.hint }) }),
38025
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
37880
38026
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
37881
38027
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
37882
38028
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -37895,6 +38041,7 @@ var init_DebuggerBoard = __esm({
37895
38041
  "components/game/organisms/puzzles/debugger/DebuggerBoard.tsx"() {
37896
38042
  init_atoms2();
37897
38043
  init_useEventBus();
38044
+ init_boardEntity();
37898
38045
  DebuggerBoard.displayName = "DebuggerBoard";
37899
38046
  }
37900
38047
  });
@@ -37932,7 +38079,7 @@ function getBadgeVariant(fieldName, value) {
37932
38079
  return "default";
37933
38080
  }
37934
38081
  function formatFieldLabel(fieldName) {
37935
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
38082
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase());
37936
38083
  }
37937
38084
  function formatFieldValue(value, fieldName) {
37938
38085
  if (typeof value === "number") {
@@ -37951,26 +38098,26 @@ function formatFieldValue(value, fieldName) {
37951
38098
  }
37952
38099
  function renderRichFieldValue(value, fieldName, fieldType) {
37953
38100
  if (value === void 0 || value === null) return "\u2014";
37954
- const str = String(value);
38101
+ const str2 = String(value);
37955
38102
  switch (fieldType) {
37956
38103
  case "image":
37957
38104
  case "url": {
37958
- if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
38105
+ if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
37959
38106
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
37960
38107
  "img",
37961
38108
  {
37962
- src: str,
38109
+ src: str2,
37963
38110
  alt: formatFieldLabel(fieldName),
37964
38111
  className: "max-w-full max-h-64 rounded-md object-contain",
37965
38112
  loading: "lazy"
37966
38113
  }
37967
38114
  ) });
37968
38115
  }
37969
- return str;
38116
+ return str2;
37970
38117
  }
37971
38118
  case "markdown":
37972
38119
  case "richtext":
37973
- return /* @__PURE__ */ jsxRuntime.jsx(React85.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "break-words", children: str }), children: /* @__PURE__ */ jsxRuntime.jsx(
38120
+ return /* @__PURE__ */ jsxRuntime.jsx(React85.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "break-words", children: str2 }), children: /* @__PURE__ */ jsxRuntime.jsx(
37974
38121
  Box,
37975
38122
  {
37976
38123
  className: "prose prose-sm max-w-none",
@@ -37988,11 +38135,11 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37988
38135
  "--tw-prose-th-borders": "var(--color-border)",
37989
38136
  "--tw-prose-td-borders": "var(--color-border)"
37990
38137
  },
37991
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str })
38138
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str2 })
37992
38139
  }
37993
38140
  ) });
37994
38141
  case "code":
37995
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 rounded-md bg-muted p-3 overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-sm font-mono whitespace-pre-wrap break-words m-0", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: str }) }) });
38142
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 rounded-md bg-muted p-3 overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-sm font-mono whitespace-pre-wrap break-words m-0", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: str2 }) }) });
37996
38143
  case "html":
37997
38144
  return /* @__PURE__ */ jsxRuntime.jsx(
37998
38145
  Box,
@@ -38012,12 +38159,12 @@ function renderRichFieldValue(value, fieldName, fieldType) {
38012
38159
  "--tw-prose-th-borders": "var(--color-border)",
38013
38160
  "--tw-prose-td-borders": "var(--color-border)"
38014
38161
  },
38015
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str })
38162
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str2 })
38016
38163
  }
38017
38164
  );
38018
38165
  case "date":
38019
38166
  case "datetime": {
38020
- const d = new Date(str);
38167
+ const d = new Date(str2);
38021
38168
  if (!isNaN(d.getTime())) {
38022
38169
  return d.toLocaleDateString(void 0, {
38023
38170
  year: "numeric",
@@ -38026,7 +38173,7 @@ function renderRichFieldValue(value, fieldName, fieldType) {
38026
38173
  ...fieldType === "datetime" ? { hour: "2-digit", minute: "2-digit" } : {}
38027
38174
  });
38028
38175
  }
38029
- return str;
38176
+ return str2;
38030
38177
  }
38031
38178
  default:
38032
38179
  return formatFieldValue(value, fieldName);
@@ -38704,6 +38851,40 @@ var init_RuleEditor = __esm({
38704
38851
  RuleEditor.displayName = "RuleEditor";
38705
38852
  }
38706
38853
  });
38854
+
38855
+ // components/game/organisms/puzzles/event-handler/puzzleObject.ts
38856
+ function objId(o) {
38857
+ return o.id == null ? "" : String(o.id);
38858
+ }
38859
+ function objName(o) {
38860
+ return o.name == null ? "" : String(o.name);
38861
+ }
38862
+ function objIcon(o) {
38863
+ return o.icon == null ? "" : String(o.icon);
38864
+ }
38865
+ function objStates(o) {
38866
+ return Array.isArray(o.states) ? o.states : [];
38867
+ }
38868
+ function objCurrentState(o) {
38869
+ return o.currentState == null ? "" : String(o.currentState);
38870
+ }
38871
+ function objAvailableEvents(o) {
38872
+ return Array.isArray(o.availableEvents) ? o.availableEvents : [];
38873
+ }
38874
+ function objAvailableActions(o) {
38875
+ return Array.isArray(o.availableActions) ? o.availableActions : [];
38876
+ }
38877
+ function objRules(o) {
38878
+ return Array.isArray(o.rules) ? o.rules : [];
38879
+ }
38880
+ function objMaxRules(o) {
38881
+ const n = Number(o.maxRules);
38882
+ return Number.isFinite(n) && n > 0 ? n : 3;
38883
+ }
38884
+ var init_puzzleObject = __esm({
38885
+ "components/game/organisms/puzzles/event-handler/puzzleObject.ts"() {
38886
+ }
38887
+ });
38707
38888
  function ObjectRulePanel({
38708
38889
  object,
38709
38890
  onRulesChange,
@@ -38711,55 +38892,63 @@ function ObjectRulePanel({
38711
38892
  className
38712
38893
  }) {
38713
38894
  const { t } = hooks.useTranslate();
38714
- const maxRules = object.maxRules || 3;
38715
- const canAdd = object.rules.length < maxRules;
38895
+ const id = objId(object);
38896
+ const name = objName(object);
38897
+ const icon = objIcon(object);
38898
+ const states = objStates(object);
38899
+ const currentState = objCurrentState(object);
38900
+ const availableEvents = objAvailableEvents(object);
38901
+ const availableActions = objAvailableActions(object);
38902
+ const rules = objRules(object);
38903
+ const maxRules = objMaxRules(object);
38904
+ const canAdd = rules.length < maxRules;
38716
38905
  const handleRuleChange = React85.useCallback((index, updatedRule) => {
38717
- const newRules = [...object.rules];
38906
+ const newRules = [...rules];
38718
38907
  newRules[index] = updatedRule;
38719
- onRulesChange(object.id, newRules);
38720
- }, [object.id, object.rules, onRulesChange]);
38908
+ onRulesChange(id, newRules);
38909
+ }, [id, rules, onRulesChange]);
38721
38910
  const handleRuleRemove = React85.useCallback((index) => {
38722
- const newRules = object.rules.filter((_, i) => i !== index);
38723
- onRulesChange(object.id, newRules);
38724
- }, [object.id, object.rules, onRulesChange]);
38911
+ const newRules = rules.filter((_, i) => i !== index);
38912
+ onRulesChange(id, newRules);
38913
+ }, [id, rules, onRulesChange]);
38725
38914
  const handleAddRule = React85.useCallback(() => {
38726
38915
  if (!canAdd || disabled) return;
38727
- const firstEvent = object.availableEvents[0]?.value || "";
38728
- const firstAction = object.availableActions[0]?.value || "";
38916
+ const firstEvent = availableEvents[0]?.value || "";
38917
+ const firstAction = availableActions[0]?.value || "";
38729
38918
  const newRule = {
38730
38919
  id: `rule-${nextRuleId++}`,
38731
38920
  whenEvent: firstEvent,
38732
38921
  thenAction: firstAction
38733
38922
  };
38734
- onRulesChange(object.id, [...object.rules, newRule]);
38735
- }, [canAdd, disabled, object, onRulesChange]);
38923
+ onRulesChange(id, [...rules, newRule]);
38924
+ }, [canAdd, disabled, id, rules, availableEvents, availableActions, onRulesChange]);
38736
38925
  const machine = {
38737
- name: object.name,
38738
- states: object.states,
38739
- currentState: object.currentState,
38740
- transitions: object.rules.map((r) => ({
38741
- from: object.currentState,
38742
- to: object.states.find((s) => s !== object.currentState) || object.currentState,
38926
+ name,
38927
+ states,
38928
+ currentState,
38929
+ transitions: rules.map((r) => ({
38930
+ from: currentState,
38931
+ to: states.find((s) => s !== currentState) || currentState,
38743
38932
  event: r.whenEvent
38744
38933
  }))
38745
38934
  };
38746
38935
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
38747
38936
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center", gap: "sm", children: [
38748
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: object.icon }),
38937
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: icon }),
38749
38938
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
38750
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
38751
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + object.currentState })
38939
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: name }),
38940
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + currentState })
38752
38941
  ] })
38753
38942
  ] }),
38754
38943
  /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
38755
38944
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38756
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
38757
- object.rules.map((rule, i) => /* @__PURE__ */ jsxRuntime.jsx(
38945
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: rules.length, max: maxRules }) + ":" }),
38946
+ rules.map((rule, i) => /* @__PURE__ */ jsxRuntime.jsx(
38758
38947
  RuleEditor,
38759
38948
  {
38760
38949
  rule,
38761
- availableEvents: object.availableEvents,
38762
- availableActions: object.availableActions,
38950
+ availableEvents,
38951
+ availableActions,
38763
38952
  onChange: (r) => handleRuleChange(i, r),
38764
38953
  onRemove: () => handleRuleRemove(i),
38765
38954
  disabled
@@ -38777,6 +38966,7 @@ var init_ObjectRulePanel = __esm({
38777
38966
  init_cn();
38778
38967
  init_TraitStateViewer();
38779
38968
  init_RuleEditor();
38969
+ init_puzzleObject();
38780
38970
  nextRuleId = 1;
38781
38971
  ObjectRulePanel.displayName = "ObjectRulePanel";
38782
38972
  }
@@ -38843,11 +39033,11 @@ function EventHandlerBoard({
38843
39033
  }) {
38844
39034
  const { emit } = useEventBus();
38845
39035
  const { t } = hooks.useTranslate();
38846
- const resolved = Array.isArray(entity) ? entity[0] : entity;
38847
- const entityObjects = resolved?.objects ?? [];
38848
- const [objects, setObjects] = React85.useState(entityObjects);
39036
+ const resolved = boardEntity(entity);
39037
+ const entityObjects = rows(resolved?.objects);
39038
+ const [objects, setObjects] = React85.useState(() => [...entityObjects]);
38849
39039
  const [selectedObjectId, setSelectedObjectId] = React85.useState(
38850
- entityObjects[0]?.id || null
39040
+ entityObjects[0] ? objId(entityObjects[0]) : null
38851
39041
  );
38852
39042
  const [headerError, setHeaderError] = React85.useState(false);
38853
39043
  const [playState, setPlayState] = React85.useState("editing");
@@ -38858,10 +39048,10 @@ function EventHandlerBoard({
38858
39048
  React85.useEffect(() => () => {
38859
39049
  if (timerRef.current) clearTimeout(timerRef.current);
38860
39050
  }, []);
38861
- const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
39051
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
38862
39052
  const handleRulesChange = React85.useCallback((objectId, rules) => {
38863
39053
  setObjects((prev) => prev.map(
38864
- (o) => o.id === objectId ? { ...o, rules } : o
39054
+ (o) => objId(o) === objectId ? { ...o, rules } : o
38865
39055
  ));
38866
39056
  }, []);
38867
39057
  const addLogEntry = React85.useCallback((icon, message, status = "done") => {
@@ -38875,11 +39065,12 @@ function EventHandlerBoard({
38875
39065
  setEventLog([]);
38876
39066
  const allRules = [];
38877
39067
  objects.forEach((obj) => {
38878
- obj.rules.forEach((rule) => {
39068
+ objRules(obj).forEach((rule) => {
38879
39069
  allRules.push({ object: obj, rule });
38880
39070
  });
38881
39071
  });
38882
- const triggers = resolved?.triggerEvents || [];
39072
+ const triggers = Array.isArray(resolved?.triggerEvents) ? resolved.triggerEvents : [];
39073
+ const goalEvent = str(resolved?.goalEvent);
38883
39074
  const eventQueue = [...triggers];
38884
39075
  const firedEvents = /* @__PURE__ */ new Set();
38885
39076
  let stepIdx = 0;
@@ -38908,14 +39099,14 @@ function EventHandlerBoard({
38908
39099
  addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
38909
39100
  } else {
38910
39101
  matching.forEach(({ object, rule }) => {
38911
- addLogEntry(object.icon, t("eventHandler.heardEvent", { object: object.name, event: currentEvent, action: rule.thenAction }), "done");
39102
+ addLogEntry(objIcon(object), t("eventHandler.heardEvent", { object: objName(object), event: currentEvent, action: rule.thenAction }), "done");
38912
39103
  eventQueue.push(rule.thenAction);
38913
- if (rule.thenAction === resolved?.goalEvent) {
39104
+ if (rule.thenAction === goalEvent) {
38914
39105
  goalReached = true;
38915
39106
  }
38916
39107
  });
38917
39108
  }
38918
- if (currentEvent === resolved?.goalEvent) {
39109
+ if (currentEvent === goalEvent) {
38919
39110
  goalReached = true;
38920
39111
  }
38921
39112
  stepIdx++;
@@ -38933,65 +39124,75 @@ function EventHandlerBoard({
38933
39124
  }, []);
38934
39125
  const handleReset = React85.useCallback(() => {
38935
39126
  if (timerRef.current) clearTimeout(timerRef.current);
38936
- setObjects(resolved?.objects ?? []);
39127
+ const resetObjects = rows(resolved?.objects);
39128
+ setObjects([...resetObjects]);
38937
39129
  setPlayState("editing");
38938
39130
  setEventLog([]);
38939
- setSelectedObjectId((resolved?.objects ?? [])[0]?.id || null);
39131
+ setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
38940
39132
  setAttempts(0);
38941
39133
  }, [resolved?.objects]);
38942
39134
  if (!resolved) return null;
38943
39135
  const objectViewers = objects.map((obj) => {
39136
+ const states = objStates(obj);
39137
+ const currentState = objCurrentState(obj);
38944
39138
  const machine = {
38945
- name: obj.name,
38946
- states: obj.states,
38947
- currentState: obj.currentState,
38948
- transitions: obj.rules.map((r) => ({
38949
- from: obj.currentState,
38950
- to: obj.states.find((s) => s !== obj.currentState) || obj.currentState,
39139
+ name: objName(obj),
39140
+ states,
39141
+ currentState,
39142
+ transitions: objRules(obj).map((r) => ({
39143
+ from: currentState,
39144
+ to: states.find((s) => s !== currentState) || currentState,
38951
39145
  event: r.whenEvent
38952
39146
  }))
38953
39147
  };
38954
39148
  return { obj, machine };
38955
39149
  });
38956
- const showHint = attempts >= 3 && resolved.hint;
39150
+ const hint = str(resolved.hint);
39151
+ const showHint = attempts >= 3 && hint;
39152
+ const theme = resolved.theme ?? void 0;
39153
+ const themeBackground = theme?.background;
39154
+ const headerImage = str(resolved.headerImage);
38957
39155
  const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
38958
39156
  return /* @__PURE__ */ jsxRuntime.jsxs(
38959
39157
  VStack,
38960
39158
  {
38961
39159
  className: cn("p-4 gap-6", className),
38962
39160
  style: {
38963
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
39161
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
38964
39162
  backgroundSize: "cover",
38965
39163
  backgroundPosition: "center"
38966
39164
  },
38967
39165
  children: [
38968
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
39166
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
38969
39167
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38970
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
38971
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
39168
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
39169
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
38972
39170
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
38973
39171
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
38974
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.goalCondition })
39172
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: str(resolved.goalCondition) })
38975
39173
  ] })
38976
39174
  ] }),
38977
39175
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38978
39176
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
38979
- /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsxRuntime.jsx(
38980
- Box,
38981
- {
38982
- className: cn(
38983
- "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
38984
- selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
38985
- ),
38986
- onClick: () => setSelectedObjectId(obj.id),
38987
- children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
38988
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: obj.icon }),
38989
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
38990
- /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
38991
- ] })
38992
- },
38993
- obj.id
38994
- )) })
39177
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => {
39178
+ const oid = objId(obj);
39179
+ return /* @__PURE__ */ jsxRuntime.jsx(
39180
+ Box,
39181
+ {
39182
+ className: cn(
39183
+ "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
39184
+ selectedObjectId === oid ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
39185
+ ),
39186
+ onClick: () => setSelectedObjectId(oid),
39187
+ children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
39188
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: objIcon(obj) }),
39189
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: objName(obj) }),
39190
+ /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
39191
+ ] })
39192
+ },
39193
+ oid
39194
+ );
39195
+ }) })
38995
39196
  ] }),
38996
39197
  selectedObject && /* @__PURE__ */ jsxRuntime.jsx(
38997
39198
  ObjectRulePanel,
@@ -39002,12 +39203,12 @@ function EventHandlerBoard({
39002
39203
  }
39003
39204
  ),
39004
39205
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
39005
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("eventHandler.chainComplete") }) }),
39206
+ playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39006
39207
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39007
39208
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
39008
39209
  showHint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
39009
39210
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
39010
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
39211
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
39011
39212
  ] }) })
39012
39213
  ] }),
39013
39214
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -39035,6 +39236,8 @@ var init_EventHandlerBoard = __esm({
39035
39236
  init_TraitStateViewer();
39036
39237
  init_ObjectRulePanel();
39037
39238
  init_EventLog();
39239
+ init_puzzleObject();
39240
+ init_boardEntity();
39038
39241
  ENCOURAGEMENT_KEYS = [
39039
39242
  "puzzle.tryAgain1",
39040
39243
  "puzzle.tryAgain2",
@@ -39123,7 +39326,10 @@ var init_FeatureGridOrganism = __esm({
39123
39326
  );
39124
39327
  React85.useCallback(
39125
39328
  (feature) => {
39126
- eventBus.emit("UI:FEATURE_CLICK", { id: feature.id, href: feature.href ?? "" });
39329
+ eventBus.emit("UI:FEATURE_CLICK", {
39330
+ id: String(feature.id ?? ""),
39331
+ href: String(feature.href ?? "")
39332
+ });
39127
39333
  },
39128
39334
  [eventBus]
39129
39335
  );
@@ -39133,14 +39339,17 @@ var init_FeatureGridOrganism = __esm({
39133
39339
  if (error) {
39134
39340
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
39135
39341
  }
39136
- const featureCards = items.map((feature) => ({
39137
- icon: feature.icon,
39138
- title: feature.title,
39139
- description: feature.description,
39140
- href: feature.href,
39141
- linkLabel: feature.linkLabel,
39142
- variant: feature.href ? "interactive" : "bordered"
39143
- }));
39342
+ const featureCards = items.map((feature) => {
39343
+ const href = feature.href != null ? String(feature.href) : void 0;
39344
+ return {
39345
+ icon: feature.icon != null ? String(feature.icon) : void 0,
39346
+ title: String(feature.title ?? ""),
39347
+ description: String(feature.description ?? ""),
39348
+ href,
39349
+ linkLabel: feature.linkLabel != null ? String(feature.linkLabel) : void 0,
39350
+ variant: href ? "interactive" : "bordered"
39351
+ };
39352
+ });
39144
39353
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
39145
39354
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
39146
39355
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
@@ -40458,22 +40667,24 @@ var init_HeroOrganism = __esm({
40458
40667
  () => Array.isArray(entity) ? entity[0] : entity && typeof entity === "object" ? entity : void 0,
40459
40668
  [entity]
40460
40669
  );
40670
+ const primaryAction = resolved?.primaryAction;
40671
+ const secondaryAction = resolved?.secondaryAction;
40461
40672
  const handlePrimaryClick = React85.useCallback(() => {
40462
- if (resolved?.primaryAction) {
40673
+ if (primaryAction) {
40463
40674
  eventBus.emit("UI:CTA_PRIMARY", {
40464
- label: resolved.primaryAction.label,
40465
- href: resolved.primaryAction.href
40675
+ label: String(primaryAction.label ?? ""),
40676
+ href: String(primaryAction.href ?? "")
40466
40677
  });
40467
40678
  }
40468
- }, [eventBus, resolved]);
40679
+ }, [eventBus, primaryAction]);
40469
40680
  const handleSecondaryClick = React85.useCallback(() => {
40470
- if (resolved?.secondaryAction) {
40681
+ if (secondaryAction) {
40471
40682
  eventBus.emit("UI:CTA_SECONDARY", {
40472
- label: resolved.secondaryAction.label,
40473
- href: resolved.secondaryAction.href
40683
+ label: String(secondaryAction.label ?? ""),
40684
+ href: String(secondaryAction.href ?? "")
40474
40685
  });
40475
40686
  }
40476
- }, [eventBus, resolved]);
40687
+ }, [eventBus, secondaryAction]);
40477
40688
  if (isLoading) {
40478
40689
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
40479
40690
  }
@@ -40483,17 +40694,19 @@ var init_HeroOrganism = __esm({
40483
40694
  if (!resolved) {
40484
40695
  return null;
40485
40696
  }
40697
+ const imageRaw = resolved.image;
40698
+ const image = imageRaw ? { src: String(imageRaw.src ?? ""), alt: String(imageRaw.alt ?? "") } : void 0;
40486
40699
  return /* @__PURE__ */ jsxRuntime.jsxs(
40487
40700
  HeroSection,
40488
40701
  {
40489
- tag: resolved.tag,
40490
- title: resolved.title,
40491
- titleAccent: resolved.titleAccent,
40492
- subtitle: resolved.subtitle,
40493
- primaryAction: resolved.primaryAction ? { label: resolved.primaryAction.label, href: resolved.primaryAction.href } : void 0,
40494
- secondaryAction: resolved.secondaryAction ? { label: resolved.secondaryAction.label, href: resolved.secondaryAction.href } : void 0,
40495
- installCommand: resolved.installCommand,
40496
- image: resolved.image,
40702
+ tag: resolved.tag != null ? String(resolved.tag) : void 0,
40703
+ title: String(resolved.title ?? ""),
40704
+ titleAccent: resolved.titleAccent != null ? String(resolved.titleAccent) : void 0,
40705
+ subtitle: String(resolved.subtitle ?? ""),
40706
+ primaryAction: primaryAction ? { label: String(primaryAction.label ?? ""), href: String(primaryAction.href ?? "") } : void 0,
40707
+ secondaryAction: secondaryAction ? { label: String(secondaryAction.label ?? ""), href: String(secondaryAction.href ?? "") } : void 0,
40708
+ installCommand: resolved.installCommand != null ? String(resolved.installCommand) : void 0,
40709
+ image,
40497
40710
  imagePosition: resolved.imagePosition,
40498
40711
  background: resolved.background,
40499
40712
  className: cn(className),
@@ -40502,8 +40715,8 @@ var init_HeroOrganism = __esm({
40502
40715
  /* @__PURE__ */ jsxRuntime.jsx(
40503
40716
  _HeroClickInterceptor,
40504
40717
  {
40505
- hasPrimary: !!resolved.primaryAction,
40506
- hasSecondary: !!resolved.secondaryAction,
40718
+ hasPrimary: !!primaryAction,
40719
+ hasSecondary: !!secondaryAction,
40507
40720
  onPrimaryClick: handlePrimaryClick,
40508
40721
  onSecondaryClick: handleSecondaryClick
40509
40722
  }
@@ -40732,7 +40945,7 @@ function formatValue3(value, fieldName) {
40732
40945
  return String(value);
40733
40946
  }
40734
40947
  function formatFieldLabel2(fieldName) {
40735
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
40948
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase()).replace(/Id$/, "").trim();
40736
40949
  }
40737
40950
  var STATUS_STYLES2, StatusBadge, ProgressIndicator, List3;
40738
40951
  var init_List = __esm({
@@ -41579,20 +41792,22 @@ function NegotiatorBoard({
41579
41792
  }) {
41580
41793
  const { emit } = useEventBus();
41581
41794
  const { t } = hooks.useTranslate();
41582
- const resolved = Array.isArray(entity) ? entity[0] : entity;
41795
+ const resolved = boardEntity(entity);
41583
41796
  const [history, setHistory] = React85.useState([]);
41584
41797
  const [headerError, setHeaderError] = React85.useState(false);
41585
41798
  const [showHint, setShowHint] = React85.useState(false);
41799
+ const totalRounds = num(resolved?.totalRounds);
41800
+ const targetScore = num(resolved?.targetScore);
41586
41801
  const currentRound = history.length;
41587
- const isComplete = currentRound >= (resolved?.totalRounds ?? 0);
41802
+ const isComplete = currentRound >= totalRounds;
41588
41803
  const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
41589
41804
  const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
41590
- const won = isComplete && playerTotal >= (resolved?.targetScore ?? 0);
41591
- const actions = resolved?.actions ?? [];
41592
- const payoffMatrix = resolved?.payoffMatrix ?? [];
41805
+ const won = isComplete && playerTotal >= targetScore;
41806
+ const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
41807
+ const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
41593
41808
  const handleAction = React85.useCallback((actionId) => {
41594
41809
  if (isComplete) return;
41595
- const opponentAction = getOpponentAction(resolved?.opponentStrategy ?? "random", actions, history);
41810
+ const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
41596
41811
  const payoff = payoffMatrix.find(
41597
41812
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
41598
41813
  );
@@ -41605,42 +41820,46 @@ function NegotiatorBoard({
41605
41820
  };
41606
41821
  const newHistory = [...history, result];
41607
41822
  setHistory(newHistory);
41608
- if (newHistory.length >= (resolved?.totalRounds ?? 0)) {
41823
+ if (newHistory.length >= totalRounds) {
41609
41824
  const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
41610
- if (total >= (resolved?.targetScore ?? 0)) {
41825
+ if (total >= targetScore) {
41611
41826
  emit(`UI:${completeEvent}`, { success: true, score: total });
41612
41827
  }
41613
- if (newHistory.length >= 3 && resolved?.hint) {
41828
+ if (newHistory.length >= 3 && str(resolved?.hint)) {
41614
41829
  setShowHint(true);
41615
41830
  }
41616
41831
  }
41617
- }, [isComplete, resolved, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41832
+ }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41618
41833
  const handleReset = () => {
41619
41834
  setHistory([]);
41620
41835
  setShowHint(false);
41621
41836
  };
41622
41837
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
41623
41838
  if (!resolved) return null;
41839
+ const theme = resolved.theme ?? void 0;
41840
+ const themeBackground = theme?.background;
41841
+ const headerImage = str(resolved.headerImage);
41842
+ const hint = str(resolved.hint);
41624
41843
  return /* @__PURE__ */ jsxRuntime.jsx(
41625
41844
  Box,
41626
41845
  {
41627
41846
  className,
41628
41847
  style: {
41629
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
41848
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
41630
41849
  backgroundSize: "cover",
41631
41850
  backgroundPosition: "center"
41632
41851
  },
41633
41852
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
41634
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
41853
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
41635
41854
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
41636
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
41637
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description }),
41855
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
41856
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
41638
41857
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", children: [
41639
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(resolved.totalRounds) }) }),
41858
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(totalRounds) }) }),
41640
41859
  /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", children: [
41641
41860
  t("negotiator.target"),
41642
41861
  ": ",
41643
- resolved.targetScore
41862
+ targetScore
41644
41863
  ] })
41645
41864
  ] })
41646
41865
  ] }) }),
@@ -41689,16 +41908,16 @@ function NegotiatorBoard({
41689
41908
  ] }) }),
41690
41909
  isComplete && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
41691
41910
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.CheckCircle, size: "lg", className: won ? "text-success" : "text-error" }),
41692
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: won ? resolved.successMessage ?? t("negotiator.success") : resolved.failMessage ?? t("negotiator.failed") }),
41911
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: won ? str(resolved.successMessage) || t("negotiator.success") : str(resolved.failMessage) || t("negotiator.failed") }),
41693
41912
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
41694
41913
  t("negotiator.finalScore"),
41695
41914
  ": ",
41696
41915
  playerTotal,
41697
41916
  "/",
41698
- resolved.targetScore
41917
+ targetScore
41699
41918
  ] })
41700
41919
  ] }) }),
41701
- showHint && resolved.hint && !won && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.hint }) }),
41920
+ showHint && hint && !won && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
41702
41921
  isComplete && !won && /* @__PURE__ */ jsxRuntime.jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
41703
41922
  ] })
41704
41923
  }
@@ -41708,6 +41927,7 @@ var init_NegotiatorBoard = __esm({
41708
41927
  "components/game/organisms/puzzles/negotiator/NegotiatorBoard.tsx"() {
41709
41928
  init_atoms2();
41710
41929
  init_useEventBus();
41930
+ init_boardEntity();
41711
41931
  NegotiatorBoard.displayName = "NegotiatorBoard";
41712
41932
  }
41713
41933
  });
@@ -41743,13 +41963,13 @@ var init_PricingOrganism = __esm({
41743
41963
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
41744
41964
  }
41745
41965
  const plans = items.map((plan) => ({
41746
- name: plan.name,
41747
- price: plan.price,
41748
- description: plan.description,
41749
- features: plan.features,
41750
- action: { label: plan.actionLabel, href: plan.actionHref },
41751
- highlighted: plan.highlighted,
41752
- badge: plan.badge
41966
+ name: String(plan.name ?? ""),
41967
+ price: String(plan.price ?? ""),
41968
+ description: plan.description != null ? String(plan.description) : void 0,
41969
+ features: (plan.features ?? []).map((f3) => String(f3)),
41970
+ action: { label: String(plan.actionLabel ?? ""), href: String(plan.actionHref ?? "") },
41971
+ highlighted: Boolean(plan.highlighted),
41972
+ badge: plan.badge != null ? String(plan.badge) : void 0
41753
41973
  }));
41754
41974
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
41755
41975
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -43820,16 +44040,20 @@ function SequencerBoard({
43820
44040
  }) {
43821
44041
  const { emit } = useEventBus();
43822
44042
  const { t } = hooks.useTranslate();
43823
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44043
+ const resolved = boardEntity(entity);
44044
+ const maxSlots = num(resolved?.maxSlots);
44045
+ const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44046
+ const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44047
+ const allowDuplicates = resolved?.allowDuplicates !== false;
43824
44048
  const [headerError, setHeaderError] = React85.useState(false);
43825
44049
  const [slots, setSlots] = React85.useState(
43826
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0)
44050
+ () => Array.from({ length: maxSlots }, () => void 0)
43827
44051
  );
43828
44052
  const [playState, setPlayState] = React85.useState("idle");
43829
44053
  const [currentStep, setCurrentStep] = React85.useState(-1);
43830
44054
  const [attempts, setAttempts] = React85.useState(0);
43831
44055
  const [slotFeedback, setSlotFeedback] = React85.useState(
43832
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => null)
44056
+ () => Array.from({ length: maxSlots }, () => null)
43833
44057
  );
43834
44058
  const timerRef = React85.useRef(null);
43835
44059
  React85.useEffect(() => () => {
@@ -43863,17 +44087,17 @@ function SequencerBoard({
43863
44087
  }, [emit]);
43864
44088
  const handleReset = React85.useCallback(() => {
43865
44089
  if (timerRef.current) clearTimeout(timerRef.current);
43866
- setSlots(Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0));
44090
+ setSlots(Array.from({ length: maxSlots }, () => void 0));
43867
44091
  setPlayState("idle");
43868
44092
  setCurrentStep(-1);
43869
44093
  setAttempts(0);
43870
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
43871
- }, [resolved?.maxSlots]);
44094
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44095
+ }, [maxSlots]);
43872
44096
  const filledSlots = slots.filter((s) => !!s);
43873
44097
  const canPlay = filledSlots.length > 0 && playState === "idle";
43874
44098
  const handlePlay = React85.useCallback(() => {
43875
44099
  if (!canPlay) return;
43876
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
44100
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
43877
44101
  emit("UI:PLAY_SOUND", { key: "confirm" });
43878
44102
  const sequence = slots.map((s) => s?.id || "");
43879
44103
  if (playEvent) {
@@ -43884,10 +44108,10 @@ function SequencerBoard({
43884
44108
  let step = 0;
43885
44109
  const advance = () => {
43886
44110
  step++;
43887
- if (step >= (resolved?.maxSlots ?? 0)) {
44111
+ if (step >= maxSlots) {
43888
44112
  const playerSeq = slots.map((s) => s?.id);
43889
44113
  const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
43890
- const success = (resolved?.solutions ?? []).some(
44114
+ const success = solutions.some(
43891
44115
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
43892
44116
  );
43893
44117
  if (success) {
@@ -43899,7 +44123,7 @@ function SequencerBoard({
43899
44123
  }
43900
44124
  } else {
43901
44125
  setAttempts((prev) => prev + 1);
43902
- const feedback = computeSlotFeedback(playerSeq, resolved?.solutions ?? []);
44126
+ const feedback = computeSlotFeedback(playerSeq, solutions);
43903
44127
  setSlotFeedback(feedback);
43904
44128
  setPlayState("idle");
43905
44129
  setCurrentStep(-1);
@@ -43917,10 +44141,10 @@ function SequencerBoard({
43917
44141
  }
43918
44142
  };
43919
44143
  timerRef.current = setTimeout(advance, stepDurationMs);
43920
- }, [canPlay, slots, resolved?.maxSlots, resolved?.solutions, stepDurationMs, playEvent, completeEvent, emit]);
44144
+ }, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, emit]);
43921
44145
  const machine = {
43922
- name: resolved?.title ?? "",
43923
- description: resolved?.description ?? "",
44146
+ name: str(resolved?.title),
44147
+ description: str(resolved?.description),
43924
44148
  states: slots.map((s, i) => stepLabel(s, i)),
43925
44149
  currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
43926
44150
  transitions: slots.slice(0, -1).map((s, i) => ({
@@ -43929,37 +44153,41 @@ function SequencerBoard({
43929
44153
  event: "NEXT"
43930
44154
  }))
43931
44155
  };
43932
- const usedIds = resolved?.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
43933
- const showHint = attempts >= 3 && !!resolved?.hint;
44156
+ const usedIds = !allowDuplicates ? slots.filter(Boolean).map((s) => s?.id || "") : [];
44157
+ const hint = str(resolved?.hint);
44158
+ const showHint = attempts >= 3 && !!hint;
43934
44159
  const hasFeedback = slotFeedback.some((f3) => f3 !== null);
43935
44160
  const correctCount = slotFeedback.filter((f3) => f3 === "correct").length;
43936
44161
  const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
43937
44162
  if (!resolved) return null;
44163
+ const theme = resolved.theme ?? void 0;
44164
+ const themeBackground = theme?.background;
44165
+ const headerImage = str(resolved.headerImage);
43938
44166
  return /* @__PURE__ */ jsxRuntime.jsxs(
43939
44167
  VStack,
43940
44168
  {
43941
44169
  className: cn("p-4 gap-6", className),
43942
44170
  style: {
43943
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44171
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
43944
44172
  backgroundSize: "cover",
43945
44173
  backgroundPosition: "center"
43946
44174
  },
43947
44175
  children: [
43948
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44176
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
43949
44177
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43950
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
43951
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description })
44178
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
44179
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) })
43952
44180
  ] }),
43953
44181
  showHint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
43954
44182
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
43955
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
44183
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
43956
44184
  ] }) }),
43957
44185
  filledSlots.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
43958
44186
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43959
44187
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
43960
44188
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
43961
44189
  hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
43962
- `${correctCount}/${resolved.maxSlots} `,
44190
+ `${correctCount}/${maxSlots} `,
43963
44191
  "\u2705"
43964
44192
  ] })
43965
44193
  ] }),
@@ -43967,7 +44195,7 @@ function SequencerBoard({
43967
44195
  SequenceBar,
43968
44196
  {
43969
44197
  slots,
43970
- maxSlots: resolved.maxSlots,
44198
+ maxSlots,
43971
44199
  onSlotDrop: handleSlotDrop,
43972
44200
  onSlotRemove: handleSlotRemove,
43973
44201
  playing: playState === "playing",
@@ -43981,15 +44209,15 @@ function SequencerBoard({
43981
44209
  playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
43982
44210
  ActionPalette,
43983
44211
  {
43984
- actions: resolved.availableActions,
44212
+ actions: availableActions,
43985
44213
  usedActionIds: usedIds,
43986
- allowDuplicates: resolved.allowDuplicates !== false,
44214
+ allowDuplicates,
43987
44215
  categoryColors,
43988
44216
  label: t("sequencer.dragActions")
43989
44217
  }
43990
44218
  ),
43991
44219
  hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
43992
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("sequencer.levelComplete") }) }),
44220
+ playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
43993
44221
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
43994
44222
  /* @__PURE__ */ jsxRuntime.jsx(
43995
44223
  Button,
@@ -44013,6 +44241,7 @@ var init_SequencerBoard = __esm({
44013
44241
  init_cn();
44014
44242
  init_useEventBus();
44015
44243
  init_TraitStateViewer();
44244
+ init_boardEntity();
44016
44245
  init_SequenceBar();
44017
44246
  init_ActionPalette();
44018
44247
  ENCOURAGEMENT_KEYS2 = [
@@ -44062,18 +44291,21 @@ var init_ShowcaseOrganism = __esm({
44062
44291
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
44063
44292
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", color: "muted", align: "center", className: "max-w-2xl", children: subtitle })
44064
44293
  ] }),
44065
- /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
44066
- ShowcaseCard,
44067
- {
44068
- title: item.title,
44069
- description: item.description,
44070
- image: item.image,
44071
- href: item.href,
44072
- badge: item.badge,
44073
- accentColor: item.accentColor
44074
- },
44075
- item.id
44076
- )) })
44294
+ /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => {
44295
+ const imageRaw = item.image;
44296
+ return /* @__PURE__ */ jsxRuntime.jsx(
44297
+ ShowcaseCard,
44298
+ {
44299
+ title: String(item.title ?? ""),
44300
+ description: item.description != null ? String(item.description) : void 0,
44301
+ image: { src: String(imageRaw?.src ?? ""), alt: String(imageRaw?.alt ?? "") },
44302
+ href: item.href != null ? String(item.href) : void 0,
44303
+ badge: item.badge != null ? String(item.badge) : void 0,
44304
+ accentColor: item.accentColor != null ? String(item.accentColor) : void 0
44305
+ },
44306
+ String(item.id ?? "")
44307
+ );
44308
+ }) })
44077
44309
  ] });
44078
44310
  };
44079
44311
  ShowcaseOrganism.displayName = "ShowcaseOrganism";
@@ -44441,8 +44673,8 @@ function SimulatorBoard({
44441
44673
  }) {
44442
44674
  const { emit } = useEventBus();
44443
44675
  const { t } = hooks.useTranslate();
44444
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44445
- const parameters = resolved?.parameters ?? [];
44676
+ const resolved = boardEntity(entity);
44677
+ const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
44446
44678
  const [values, setValues] = React85.useState(() => {
44447
44679
  const init = {};
44448
44680
  for (const p2 of parameters) {
@@ -44456,15 +44688,15 @@ function SimulatorBoard({
44456
44688
  const [showHint, setShowHint] = React85.useState(false);
44457
44689
  const computeOutput = React85.useCallback((params) => {
44458
44690
  try {
44459
- const fn = new Function("params", `return (${resolved?.computeExpression})`);
44691
+ const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
44460
44692
  return fn(params);
44461
44693
  } catch {
44462
44694
  return 0;
44463
44695
  }
44464
44696
  }, [resolved?.computeExpression]);
44465
44697
  const output = React85.useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
44466
- const targetValue = resolved?.targetValue ?? 0;
44467
- const targetTolerance = resolved?.targetTolerance ?? 0;
44698
+ const targetValue = num(resolved?.targetValue);
44699
+ const targetTolerance = num(resolved?.targetTolerance);
44468
44700
  const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
44469
44701
  const handleParameterChange = (id, value) => {
44470
44702
  if (submitted) return;
@@ -44479,7 +44711,7 @@ function SimulatorBoard({
44479
44711
  };
44480
44712
  const handleReset = () => {
44481
44713
  setSubmitted(false);
44482
- if (attempts >= 2 && resolved?.hint) {
44714
+ if (attempts >= 2 && str(resolved?.hint)) {
44483
44715
  setShowHint(true);
44484
44716
  }
44485
44717
  };
@@ -44494,20 +44726,26 @@ function SimulatorBoard({
44494
44726
  setShowHint(false);
44495
44727
  };
44496
44728
  if (!resolved) return null;
44729
+ const theme = resolved.theme ?? void 0;
44730
+ const themeBackground = theme?.background;
44731
+ const headerImage = str(resolved.headerImage);
44732
+ const hint = str(resolved.hint);
44733
+ const outputLabel = str(resolved.outputLabel);
44734
+ const outputUnit = str(resolved.outputUnit);
44497
44735
  return /* @__PURE__ */ jsxRuntime.jsx(
44498
44736
  Box,
44499
44737
  {
44500
44738
  className,
44501
44739
  style: {
44502
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44740
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
44503
44741
  backgroundSize: "cover",
44504
44742
  backgroundPosition: "center"
44505
44743
  },
44506
44744
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
44507
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44745
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
44508
44746
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
44509
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
44510
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
44747
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
44748
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
44511
44749
  ] }) }),
44512
44750
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "md", children: [
44513
44751
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
@@ -44548,28 +44786,28 @@ function SimulatorBoard({
44548
44786
  ] }, param.id))
44549
44787
  ] }) }),
44550
44788
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
44551
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: resolved.outputLabel }),
44789
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
44552
44790
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", weight: "bold", children: [
44553
44791
  output.toFixed(2),
44554
44792
  " ",
44555
- resolved.outputUnit
44793
+ outputUnit
44556
44794
  ] }),
44557
44795
  submitted && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
44558
44796
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: isCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
44559
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? resolved.successMessage ?? t("simulator.correct") : resolved.failMessage ?? t("simulator.incorrect") })
44797
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? str(resolved.successMessage) || t("simulator.correct") : str(resolved.failMessage) || t("simulator.incorrect") })
44560
44798
  ] }),
44561
44799
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44562
44800
  t("simulator.target"),
44563
44801
  ": ",
44564
44802
  targetValue,
44565
44803
  " ",
44566
- resolved.outputUnit,
44804
+ outputUnit,
44567
44805
  " (\xB1",
44568
44806
  targetTolerance,
44569
44807
  ")"
44570
44808
  ] })
44571
44809
  ] }) }),
44572
- showHint && resolved.hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.hint }) }),
44810
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
44573
44811
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
44574
44812
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
44575
44813
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Play, size: "sm" }),
@@ -44588,6 +44826,7 @@ var init_SimulatorBoard = __esm({
44588
44826
  "components/game/organisms/puzzles/simulator/SimulatorBoard.tsx"() {
44589
44827
  init_atoms2();
44590
44828
  init_useEventBus();
44829
+ init_boardEntity();
44591
44830
  SimulatorBoard.displayName = "SimulatorBoard";
44592
44831
  }
44593
44832
  });
@@ -45013,22 +45252,25 @@ function VariablePanel({
45013
45252
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
45014
45253
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
45015
45254
  variables.map((v) => {
45016
- const max = v.max ?? 100;
45017
- const min = v.min ?? 0;
45018
- const pct = Math.round((v.value - min) / (max - min) * 100);
45255
+ const name = v.name == null ? "" : String(v.name);
45256
+ const value = numField(v.value);
45257
+ const max = numField(v.max, 100);
45258
+ const min = numField(v.min, 0);
45259
+ const unit = v.unit == null ? "" : String(v.unit);
45260
+ const pct = Math.round((value - min) / (max - min) * 100);
45019
45261
  const isHigh = pct > 80;
45020
45262
  const isLow = pct < 20;
45021
45263
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
45022
45264
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
45023
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: v.name }),
45265
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: name }),
45024
45266
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: cn(
45025
45267
  isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
45026
45268
  ), children: [
45027
- v.value,
45028
- v.unit || "",
45269
+ value,
45270
+ unit,
45029
45271
  " / ",
45030
45272
  max,
45031
- v.unit || ""
45273
+ unit
45032
45274
  ] })
45033
45275
  ] }),
45034
45276
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -45039,14 +45281,19 @@ function VariablePanel({
45039
45281
  size: "sm"
45040
45282
  }
45041
45283
  )
45042
- ] }, v.name);
45284
+ ] }, name);
45043
45285
  })
45044
45286
  ] });
45045
45287
  }
45288
+ var numField;
45046
45289
  var init_VariablePanel = __esm({
45047
45290
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
45048
45291
  init_atoms2();
45049
45292
  init_cn();
45293
+ numField = (v, fallback = 0) => {
45294
+ const n = Number(v);
45295
+ return Number.isFinite(n) ? n : fallback;
45296
+ };
45050
45297
  VariablePanel.displayName = "VariablePanel";
45051
45298
  }
45052
45299
  });
@@ -45073,14 +45320,21 @@ function StateArchitectBoard({
45073
45320
  }) {
45074
45321
  const { emit } = useEventBus();
45075
45322
  const { t } = hooks.useTranslate();
45076
- const resolved = Array.isArray(entity) ? entity[0] : entity;
45077
- const [transitions, setTransitions] = React85.useState(resolved?.transitions ?? []);
45323
+ const resolved = boardEntity(entity);
45324
+ const entityStates = Array.isArray(resolved?.states) ? resolved.states : [];
45325
+ const initialState = str(resolved?.initialState);
45326
+ const entityName = str(resolved?.entityName);
45327
+ const availableEvents = Array.isArray(resolved?.availableEvents) ? resolved.availableEvents : [];
45328
+ const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
45329
+ const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
45330
+ const entityVariables = rows(resolved?.variables);
45331
+ const [transitions, setTransitions] = React85.useState(entityTransitions);
45078
45332
  const [headerError, setHeaderError] = React85.useState(false);
45079
45333
  const [playState, setPlayState] = React85.useState("editing");
45080
- const [currentState, setCurrentState] = React85.useState(resolved?.initialState ?? "");
45334
+ const [currentState, setCurrentState] = React85.useState(initialState);
45081
45335
  const [selectedState, setSelectedState] = React85.useState(null);
45082
45336
  const [testResults, setTestResults] = React85.useState([]);
45083
- const [variables, setVariables] = React85.useState(resolved?.variables ?? []);
45337
+ const [variables, setVariables] = React85.useState(() => [...entityVariables]);
45084
45338
  const [attempts, setAttempts] = React85.useState(0);
45085
45339
  const timerRef = React85.useRef(null);
45086
45340
  const [addingFrom, setAddingFrom] = React85.useState(null);
@@ -45089,12 +45343,12 @@ function StateArchitectBoard({
45089
45343
  }, []);
45090
45344
  const GRAPH_W = 500;
45091
45345
  const GRAPH_H = 400;
45092
- const positions = React85.useMemo(() => layoutStates(resolved?.states ?? [], GRAPH_W, GRAPH_H), [resolved?.states]);
45346
+ const positions = React85.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
45093
45347
  const handleStateClick = React85.useCallback((state) => {
45094
45348
  if (playState !== "editing") return;
45095
45349
  if (addingFrom) {
45096
45350
  if (addingFrom !== state) {
45097
- const event = resolved?.availableEvents[0] || "EVENT";
45351
+ const event = availableEvents[0] || "EVENT";
45098
45352
  const newTrans = {
45099
45353
  id: `t-${nextTransId++}`,
45100
45354
  from: addingFrom,
@@ -45107,7 +45361,7 @@ function StateArchitectBoard({
45107
45361
  } else {
45108
45362
  setSelectedState(state);
45109
45363
  }
45110
- }, [playState, addingFrom, resolved?.availableEvents]);
45364
+ }, [playState, addingFrom, availableEvents]);
45111
45365
  const handleStartAddTransition = React85.useCallback(() => {
45112
45366
  if (!selectedState) return;
45113
45367
  setAddingFrom(selectedState);
@@ -45116,9 +45370,9 @@ function StateArchitectBoard({
45116
45370
  setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45117
45371
  }, []);
45118
45372
  const machine = React85.useMemo(() => ({
45119
- name: resolved?.entityName ?? "",
45120
- description: resolved?.description ?? "",
45121
- states: resolved?.states ?? [],
45373
+ name: entityName,
45374
+ description: str(resolved?.description),
45375
+ states: entityStates,
45122
45376
  currentState,
45123
45377
  transitions: transitions.map((t2) => ({
45124
45378
  from: t2.from,
@@ -45126,7 +45380,7 @@ function StateArchitectBoard({
45126
45380
  event: t2.event,
45127
45381
  guardHint: t2.guardHint
45128
45382
  }))
45129
- }), [resolved, currentState, transitions]);
45383
+ }), [entityName, resolved, entityStates, currentState, transitions]);
45130
45384
  const handleTest = React85.useCallback(() => {
45131
45385
  if (playState !== "editing") return;
45132
45386
  if (testEvent) emit(`UI:${testEvent}`, {});
@@ -45135,7 +45389,7 @@ function StateArchitectBoard({
45135
45389
  const results = [];
45136
45390
  let testIdx = 0;
45137
45391
  const runNextTest = () => {
45138
- if (testIdx >= (resolved?.testCases.length ?? 0)) {
45392
+ if (testIdx >= testCases.length) {
45139
45393
  const allPassed = results.every((r) => r.passed);
45140
45394
  setPlayState(allPassed ? "success" : "fail");
45141
45395
  setTestResults(results);
@@ -45150,9 +45404,9 @@ function StateArchitectBoard({
45150
45404
  }
45151
45405
  return;
45152
45406
  }
45153
- const testCase = resolved?.testCases[testIdx];
45407
+ const testCase = testCases[testIdx];
45154
45408
  if (!testCase) return;
45155
- let state = resolved.initialState;
45409
+ let state = initialState;
45156
45410
  for (const event of testCase.events) {
45157
45411
  const trans = transitions.find((t2) => t2.from === state && t2.event === event);
45158
45412
  if (trans) {
@@ -45170,53 +45424,57 @@ function StateArchitectBoard({
45170
45424
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45171
45425
  };
45172
45426
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45173
- }, [playState, transitions, resolved, stepDurationMs, testEvent, completeEvent, emit]);
45427
+ }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45174
45428
  const handleTryAgain = React85.useCallback(() => {
45175
45429
  if (timerRef.current) clearTimeout(timerRef.current);
45176
45430
  setPlayState("editing");
45177
- setCurrentState(resolved?.initialState ?? "");
45431
+ setCurrentState(initialState);
45178
45432
  setTestResults([]);
45179
- }, [resolved?.initialState]);
45433
+ }, [initialState]);
45180
45434
  const handleReset = React85.useCallback(() => {
45181
45435
  if (timerRef.current) clearTimeout(timerRef.current);
45182
- setTransitions(resolved?.transitions ?? []);
45436
+ setTransitions(entityTransitions);
45183
45437
  setPlayState("editing");
45184
- setCurrentState(resolved?.initialState ?? "");
45438
+ setCurrentState(initialState);
45185
45439
  setTestResults([]);
45186
- setVariables(resolved?.variables ?? []);
45440
+ setVariables([...entityVariables]);
45187
45441
  setSelectedState(null);
45188
45442
  setAddingFrom(null);
45189
45443
  setAttempts(0);
45190
- }, [resolved]);
45444
+ }, [entityTransitions, initialState, entityVariables]);
45191
45445
  const codeData = React85.useMemo(() => ({
45192
- name: resolved?.entityName ?? "",
45193
- states: resolved?.states ?? [],
45194
- initialState: resolved?.initialState ?? "",
45446
+ name: entityName,
45447
+ states: entityStates,
45448
+ initialState,
45195
45449
  transitions: transitions.map((t2) => ({
45196
45450
  from: t2.from,
45197
45451
  to: t2.to,
45198
45452
  event: t2.event,
45199
45453
  ...t2.guardHint ? { guard: t2.guardHint } : {}
45200
45454
  }))
45201
- }), [resolved, transitions]);
45455
+ }), [entityName, entityStates, initialState, transitions]);
45202
45456
  if (!resolved) return null;
45457
+ const theme = resolved.theme ?? void 0;
45458
+ const themeBackground = theme?.background;
45459
+ const headerImage = str(resolved.headerImage);
45460
+ const hint = str(resolved.hint);
45203
45461
  return /* @__PURE__ */ jsxRuntime.jsxs(
45204
45462
  VStack,
45205
45463
  {
45206
45464
  className: cn("p-4 gap-6", className),
45207
45465
  style: {
45208
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
45466
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
45209
45467
  backgroundSize: "cover",
45210
45468
  backgroundPosition: "center"
45211
45469
  },
45212
45470
  children: [
45213
- resolved.headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: resolved.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : resolved.headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
45471
+ headerImage && !headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 overflow-hidden rounded-container", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : headerImage && headerError ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "w-full h-32 rounded-container bg-gradient-to-br from-muted to-accent opacity-60" }) : null,
45214
45472
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
45215
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
45216
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
45473
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
45474
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
45217
45475
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
45218
45476
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
45219
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.hint })
45477
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: hint })
45220
45478
  ] })
45221
45479
  ] }),
45222
45480
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
@@ -45264,14 +45522,14 @@ function StateArchitectBoard({
45264
45522
  ]
45265
45523
  }
45266
45524
  ),
45267
- resolved.states.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
45525
+ entityStates.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
45268
45526
  StateNode2,
45269
45527
  {
45270
45528
  name: state,
45271
45529
  position: positions[state],
45272
45530
  isCurrent: state === currentState,
45273
45531
  isSelected: state === selectedState,
45274
- isInitial: state === resolved.initialState,
45532
+ isInitial: state === initialState,
45275
45533
  onClick: () => handleStateClick(state)
45276
45534
  },
45277
45535
  state
@@ -45318,7 +45576,7 @@ function StateArchitectBoard({
45318
45576
  /* @__PURE__ */ jsxRuntime.jsx(
45319
45577
  VariablePanel,
45320
45578
  {
45321
- entityName: resolved.entityName,
45579
+ entityName,
45322
45580
  variables
45323
45581
  }
45324
45582
  ),
@@ -45333,12 +45591,12 @@ function StateArchitectBoard({
45333
45591
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(CodeView, { data: codeData, label: "View Code" })
45334
45592
  ] })
45335
45593
  ] }),
45336
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: resolved.successMessage || t("stateArchitect.allPassed") }) }),
45594
+ playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
45337
45595
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
45338
45596
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
45339
- attempts >= 3 && resolved.hint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
45597
+ attempts >= 3 && hint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
45340
45598
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
45341
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
45599
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
45342
45600
  ] }) })
45343
45601
  ] }),
45344
45602
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -45368,6 +45626,7 @@ var init_StateArchitectBoard = __esm({
45368
45626
  init_TransitionArrow();
45369
45627
  init_VariablePanel();
45370
45628
  init_CodeView();
45629
+ init_boardEntity();
45371
45630
  ENCOURAGEMENT_KEYS3 = [
45372
45631
  "puzzle.tryAgain1",
45373
45632
  "puzzle.tryAgain2",
@@ -45404,8 +45663,8 @@ var init_StatsOrganism = __esm({
45404
45663
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45405
45664
  }
45406
45665
  const stats = items.map((item) => ({
45407
- value: item.value,
45408
- label: item.label
45666
+ value: String(item.value ?? ""),
45667
+ label: String(item.label ?? "")
45409
45668
  }));
45410
45669
  return /* @__PURE__ */ jsxRuntime.jsx(
45411
45670
  StatsGrid,
@@ -45451,10 +45710,10 @@ var init_StepFlowOrganism = __esm({
45451
45710
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45452
45711
  }
45453
45712
  const steps = items.map((item) => ({
45454
- number: item.number,
45455
- title: item.title,
45456
- description: item.description,
45457
- icon: item.icon
45713
+ number: item.number != null ? Number(item.number) : void 0,
45714
+ title: String(item.title ?? ""),
45715
+ description: String(item.description ?? ""),
45716
+ icon: item.icon != null ? String(item.icon) : void 0
45458
45717
  }));
45459
45718
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
45460
45719
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -45627,13 +45886,13 @@ var init_TeamOrganism = __esm({
45627
45886
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((member) => /* @__PURE__ */ jsxRuntime.jsx(
45628
45887
  TeamCard,
45629
45888
  {
45630
- name: member.name,
45631
- nameAr: member.nameAr,
45632
- role: member.role,
45633
- bio: member.bio,
45634
- avatar: member.avatar
45889
+ name: String(member.name ?? ""),
45890
+ nameAr: member.nameAr != null ? String(member.nameAr) : void 0,
45891
+ role: String(member.role ?? ""),
45892
+ bio: String(member.bio ?? ""),
45893
+ avatar: member.avatar != null ? String(member.avatar) : void 0
45635
45894
  },
45636
- member.id
45895
+ String(member.id ?? "")
45637
45896
  )) })
45638
45897
  ] });
45639
45898
  };
@@ -45870,8 +46129,8 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45870
46129
  const [turn, setTurn] = React85.useState(1);
45871
46130
  const [gameResult, setGameResult] = React85.useState(null);
45872
46131
  const checkGameEnd = React85.useCallback((currentUnits) => {
45873
- const pa = currentUnits.filter((u) => u.team === "player" && u.health > 0);
45874
- const ea = currentUnits.filter((u) => u.team === "enemy" && u.health > 0);
46132
+ const pa = currentUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
46133
+ const ea = currentUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
45875
46134
  if (pa.length === 0) {
45876
46135
  setGameResult("defeat");
45877
46136
  setPhase("game_over");
@@ -45889,34 +46148,36 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45889
46148
  }
45890
46149
  }, [onGameEnd, gameEndEvent, eventBus]);
45891
46150
  const handleUnitClick = React85.useCallback((unitId) => {
45892
- const unit = units.find((u) => u.id === unitId);
46151
+ const unit = units.find((u) => str(u.id) === unitId);
45893
46152
  if (!unit) return;
45894
46153
  if (unitClickEvent) {
45895
46154
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
45896
46155
  }
45897
46156
  if (phase === "observation" || phase === "selection") {
45898
- if (unit.team === "player") {
46157
+ if (unitTeam(unit) === "player") {
45899
46158
  setSelectedUnitId(unitId);
45900
46159
  setPhase("movement");
45901
46160
  }
45902
46161
  } else if (phase === "action") {
45903
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46162
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45904
46163
  if (!selectedUnit) return;
45905
- if (unit.team === "enemy") {
45906
- const dx = Math.abs(unit.position.x - selectedUnit.position.x);
45907
- const dy = Math.abs(unit.position.y - selectedUnit.position.y);
46164
+ if (unitTeam(unit) === "enemy") {
46165
+ const up = unitPosition(unit);
46166
+ const sp = unitPosition(selectedUnit);
46167
+ const dx = Math.abs(up.x - sp.x);
46168
+ const dy = Math.abs(up.y - sp.y);
45908
46169
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
45909
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
45910
- const newHealth = Math.max(0, unit.health - damage);
46170
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
46171
+ const newHealth = Math.max(0, unitHealth(unit) - damage);
45911
46172
  const updatedUnits = units.map(
45912
- (u) => u.id === unit.id ? { ...u, health: newHealth } : u
46173
+ (u) => str(u.id) === str(unit.id) ? { ...u, health: newHealth } : u
45913
46174
  );
45914
46175
  setUnits(updatedUnits);
45915
46176
  onAttack?.(selectedUnit, unit, damage);
45916
46177
  if (attackEvent) {
45917
46178
  eventBus.emit(`UI:${attackEvent}`, {
45918
- attackerId: selectedUnit.id,
45919
- targetId: unit.id,
46179
+ attackerId: str(selectedUnit.id),
46180
+ targetId: str(unit.id),
45920
46181
  damage
45921
46182
  });
45922
46183
  }
@@ -45933,16 +46194,20 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45933
46194
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
45934
46195
  }
45935
46196
  if (phase === "movement" && selectedUnitId) {
45936
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46197
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45937
46198
  if (!selectedUnit) return;
45938
- const dx = Math.abs(x - selectedUnit.position.x);
45939
- const dy = Math.abs(y - selectedUnit.position.y);
46199
+ const sp = unitPosition(selectedUnit);
46200
+ const dx = Math.abs(x - sp.x);
46201
+ const dy = Math.abs(y - sp.y);
45940
46202
  const dist = dx + dy;
45941
- if (dist > 0 && dist <= selectedUnit.movement) {
45942
- if (!units.some((u) => u.position.x === x && u.position.y === y && u.health > 0)) {
46203
+ if (dist > 0 && dist <= num(selectedUnit.movement)) {
46204
+ if (!units.some((u) => {
46205
+ const p2 = unitPosition(u);
46206
+ return p2.x === x && p2.y === y && unitHealth(u) > 0;
46207
+ })) {
45943
46208
  setUnits(
45944
46209
  (prev) => prev.map(
45945
- (u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u
46210
+ (u) => str(u.id) === selectedUnitId ? { ...u, position: { x, y } } : u
45946
46211
  )
45947
46212
  );
45948
46213
  setPhase("action");
@@ -45985,12 +46250,13 @@ var init_useBattleState = __esm({
45985
46250
  "components/game/organisms/hooks/useBattleState.ts"() {
45986
46251
  "use client";
45987
46252
  init_useEventBus();
46253
+ init_boardEntity();
45988
46254
  }
45989
46255
  });
45990
46256
  function UncontrolledBattleBoard({ entity, ...rest }) {
45991
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46257
+ const resolved = boardEntity(entity);
45992
46258
  const battleState = useBattleState(
45993
- resolved?.initialUnits ?? [],
46259
+ rows(resolved?.initialUnits),
45994
46260
  {
45995
46261
  tileClickEvent: rest.tileClickEvent,
45996
46262
  unitClickEvent: rest.unitClickEvent,
@@ -46026,10 +46292,23 @@ function UncontrolledBattleBoard({ entity, ...rest }) {
46026
46292
  var init_UncontrolledBattleBoard = __esm({
46027
46293
  "components/game/organisms/UncontrolledBattleBoard.tsx"() {
46028
46294
  init_BattleBoard();
46295
+ init_boardEntity();
46029
46296
  init_useBattleState();
46030
46297
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
46031
46298
  }
46032
46299
  });
46300
+ function heroPosition(h) {
46301
+ return vec2(h.position);
46302
+ }
46303
+ function heroOwner(h) {
46304
+ return str(h.owner);
46305
+ }
46306
+ function heroMovement(h) {
46307
+ return num(h.movement);
46308
+ }
46309
+ function hexPassable(h) {
46310
+ return h.passable !== false;
46311
+ }
46033
46312
  function defaultIsInRange(from, to, range) {
46034
46313
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
46035
46314
  }
@@ -46060,36 +46339,36 @@ function WorldMapBoard({
46060
46339
  className
46061
46340
  }) {
46062
46341
  const eventBus = useEventBus();
46063
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46064
- const hexes = resolved?.hexes ?? [];
46065
- const heroes = resolved?.heroes ?? [];
46066
- const features = resolved?.features ?? [];
46067
- const selectedHeroId = resolved?.selectedHeroId;
46342
+ const resolved = boardEntity(entity);
46343
+ const hexes = rows(resolved?.hexes);
46344
+ const heroes = rows(resolved?.heroes);
46345
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
46346
+ const selectedHeroId = resolved?.selectedHeroId ?? null;
46068
46347
  const assetManifest = resolved?.assetManifest;
46069
46348
  const backgroundImage = resolved?.backgroundImage;
46070
46349
  const [hoveredTile, setHoveredTile] = React85.useState(null);
46071
46350
  const selectedHero = React85.useMemo(
46072
- () => heroes.find((h) => h.id === selectedHeroId) ?? null,
46351
+ () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46073
46352
  [heroes, selectedHeroId]
46074
46353
  );
46075
46354
  const tiles = React85.useMemo(
46076
46355
  () => hexes.map((hex) => ({
46077
- x: hex.x,
46078
- y: hex.y,
46079
- terrain: hex.terrain,
46080
- terrainSprite: hex.terrainSprite
46356
+ x: num(hex.x),
46357
+ y: num(hex.y),
46358
+ terrain: str(hex.terrain),
46359
+ terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
46081
46360
  })),
46082
46361
  [hexes]
46083
46362
  );
46084
46363
  const baseUnits = React85.useMemo(
46085
46364
  () => heroes.map((hero) => ({
46086
- id: hero.id,
46087
- position: hero.position,
46088
- name: hero.name,
46089
- team: hero.owner === "enemy" ? "enemy" : "player",
46365
+ id: str(hero.id),
46366
+ position: heroPosition(hero),
46367
+ name: str(hero.name),
46368
+ team: heroOwner(hero) === "enemy" ? "enemy" : "player",
46090
46369
  health: 100,
46091
46370
  maxHealth: 100,
46092
- sprite: hero.sprite
46371
+ sprite: hero.sprite == null ? void 0 : str(hero.sprite)
46093
46372
  })),
46094
46373
  [heroes]
46095
46374
  );
@@ -46130,73 +46409,94 @@ function WorldMapBoard({
46130
46409
  const isoUnits = React85.useMemo(() => {
46131
46410
  if (movingPositions.size === 0) return baseUnits;
46132
46411
  return baseUnits.map((u) => {
46133
- const pos = movingPositions.get(u.id);
46412
+ const pos = u.id == null ? void 0 : movingPositions.get(u.id);
46134
46413
  return pos ? { ...u, position: pos } : u;
46135
46414
  });
46136
46415
  }, [baseUnits, movingPositions]);
46137
46416
  const validMoves = React85.useMemo(() => {
46138
- if (!selectedHero || selectedHero.movement <= 0) return [];
46417
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46418
+ const sp = heroPosition(selectedHero);
46419
+ const sOwner = heroOwner(selectedHero);
46420
+ const range = heroMovement(selectedHero);
46139
46421
  const moves = [];
46140
46422
  hexes.forEach((hex) => {
46141
- if (hex.passable === false) return;
46142
- if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
46143
- if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
46144
- if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
46145
- moves.push({ x: hex.x, y: hex.y });
46423
+ const hx = num(hex.x);
46424
+ const hy = num(hex.y);
46425
+ if (!hexPassable(hex)) return;
46426
+ if (hx === sp.x && hy === sp.y) return;
46427
+ if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46428
+ if (heroes.some((h) => {
46429
+ const hp = heroPosition(h);
46430
+ return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
46431
+ })) return;
46432
+ moves.push({ x: hx, y: hy });
46146
46433
  });
46147
46434
  return moves;
46148
46435
  }, [selectedHero, hexes, heroes, isInRange]);
46149
46436
  const attackTargets = React85.useMemo(() => {
46150
- if (!selectedHero || selectedHero.movement <= 0) return [];
46151
- return heroes.filter((h) => h.owner !== selectedHero.owner).filter((h) => isInRange(selectedHero.position, h.position, selectedHero.movement)).map((h) => h.position);
46437
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46438
+ const sp = heroPosition(selectedHero);
46439
+ const sOwner = heroOwner(selectedHero);
46440
+ const range = heroMovement(selectedHero);
46441
+ return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
46152
46442
  }, [selectedHero, heroes, isInRange]);
46153
- const maxY = Math.max(...hexes.map((h) => h.y), 0);
46443
+ const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
46154
46444
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46155
46445
  const tileToScreen = React85.useCallback(
46156
46446
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46157
46447
  [scale, baseOffsetX]
46158
46448
  );
46159
46449
  const hoveredHex = React85.useMemo(
46160
- () => hoveredTile ? hexes.find((h) => h.x === hoveredTile.x && h.y === hoveredTile.y) ?? null : null,
46450
+ () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
46161
46451
  [hoveredTile, hexes]
46162
46452
  );
46163
46453
  const hoveredHero = React85.useMemo(
46164
- () => hoveredTile ? heroes.find((h) => h.position.x === hoveredTile.x && h.position.y === hoveredTile.y) ?? null : null,
46454
+ () => hoveredTile ? heroes.find((h) => {
46455
+ const hp = heroPosition(h);
46456
+ return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
46457
+ }) ?? null : null,
46165
46458
  [hoveredTile, heroes]
46166
46459
  );
46167
46460
  const handleTileClick = React85.useCallback((x, y) => {
46168
46461
  if (movementAnimRef.current) return;
46169
- const hex = hexes.find((h) => h.x === x && h.y === y);
46462
+ const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
46170
46463
  if (!hex) return;
46171
46464
  if (tileClickEvent) {
46172
46465
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46173
46466
  }
46174
46467
  if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
46175
- startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
46176
- onHeroMove?.(selectedHero.id, x, y);
46468
+ const heroId = str(selectedHero.id);
46469
+ startMoveAnimation(heroId, { ...heroPosition(selectedHero) }, { x, y }, () => {
46470
+ onHeroMove?.(heroId, x, y);
46177
46471
  if (heroMoveEvent) {
46178
- eventBus.emit(`UI:${heroMoveEvent}`, { heroId: selectedHero.id, toX: x, toY: y });
46472
+ eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46179
46473
  }
46180
- if (hex.feature && hex.feature !== "none") {
46181
- onFeatureEnter?.(selectedHero.id, hex);
46474
+ const feature = str(hex.feature);
46475
+ if (feature && feature !== "none") {
46476
+ onFeatureEnter?.(heroId, hex);
46182
46477
  if (featureEnterEvent) {
46183
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId: selectedHero.id, feature: hex.feature, hex });
46478
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
46184
46479
  }
46185
46480
  }
46186
46481
  });
46187
46482
  return;
46188
46483
  }
46189
- const enemy = heroes.find((h) => h.position.x === x && h.position.y === y && h.owner === "enemy");
46484
+ const enemy = heroes.find((h) => {
46485
+ const hp = heroPosition(h);
46486
+ return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
46487
+ });
46190
46488
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46191
- onBattleEncounter?.(selectedHero.id, enemy.id);
46489
+ const attackerId = str(selectedHero.id);
46490
+ const defenderId = str(enemy.id);
46491
+ onBattleEncounter?.(attackerId, defenderId);
46192
46492
  if (battleEncounterEvent) {
46193
- eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId: selectedHero.id, defenderId: enemy.id });
46493
+ eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46194
46494
  }
46195
46495
  }
46196
46496
  }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46197
46497
  const handleUnitClick = React85.useCallback((unitId) => {
46198
- const hero = heroes.find((h) => h.id === unitId);
46199
- if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
46498
+ const hero = heroes.find((h) => str(h.id) === unitId);
46499
+ if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
46200
46500
  onHeroSelect?.(unitId);
46201
46501
  if (heroSelectEvent) {
46202
46502
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
@@ -46269,6 +46569,7 @@ var init_WorldMapBoard = __esm({
46269
46569
  init_Stack();
46270
46570
  init_LoadingState();
46271
46571
  init_IsometricCanvas2();
46572
+ init_boardEntity();
46272
46573
  init_isometric();
46273
46574
  WorldMapBoard.displayName = "WorldMapBoard";
46274
46575
  }