@almadar/ui 5.21.12 → 5.22.3

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 +2432 -3173
  2. package/dist/avl/index.js +1373 -2114
  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 +2016 -1668
  72. package/dist/components/index.js +1128 -780
  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 +912 -624
  84. package/dist/providers/index.js +912 -624
  85. package/dist/runtime/index.cjs +914 -626
  86. package/dist/runtime/index.js +914 -626
  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;
@@ -10177,7 +10117,7 @@ var init_MapView = __esm({
10177
10117
  shadowSize: [41, 41]
10178
10118
  });
10179
10119
  L.Marker.prototype.options.icon = defaultIcon;
10180
- 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;
10181
10121
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
10182
10122
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
10183
10123
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -10223,7 +10163,7 @@ var init_MapView = __esm({
10223
10163
  }) {
10224
10164
  const eventBus = useEventBus2();
10225
10165
  const [clickedPosition, setClickedPosition] = useState100(null);
10226
- const handleMapClick = useCallback113((lat, lng) => {
10166
+ const handleMapClick = useCallback114((lat, lng) => {
10227
10167
  if (showClickedPin) {
10228
10168
  setClickedPosition({ lat, lng });
10229
10169
  }
@@ -10232,7 +10172,7 @@ var init_MapView = __esm({
10232
10172
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
10233
10173
  }
10234
10174
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
10235
- const handleMarkerClick = useCallback113((marker) => {
10175
+ const handleMarkerClick = useCallback114((marker) => {
10236
10176
  onMarkerClick?.(marker);
10237
10177
  if (markerClickEvent) {
10238
10178
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -10453,7 +10393,7 @@ function InputPattern({
10453
10393
  function TextareaPattern({
10454
10394
  value = "",
10455
10395
  placeholder,
10456
- rows = 4,
10396
+ rows: rows2 = 4,
10457
10397
  disabled = false,
10458
10398
  fieldError,
10459
10399
  onChange,
@@ -10473,7 +10413,7 @@ function TextareaPattern({
10473
10413
  {
10474
10414
  value: localValue,
10475
10415
  placeholder,
10476
- rows,
10416
+ rows: rows2,
10477
10417
  disabled,
10478
10418
  error: fieldError,
10479
10419
  onChange: handleChange,
@@ -10958,6 +10898,91 @@ var init_ActionPalette = __esm({
10958
10898
  ActionPalette.displayName = "ActionPalette";
10959
10899
  }
10960
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
+ });
10961
10986
  var AuthLayout;
10962
10987
  var init_AuthLayout = __esm({
10963
10988
  "components/core/templates/AuthLayout.tsx"() {
@@ -12299,6 +12324,39 @@ var init_IsometricCanvas2 = __esm({
12299
12324
  init_IsometricCanvas();
12300
12325
  }
12301
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
+ });
12302
12360
  function BattleBoard({
12303
12361
  entity,
12304
12362
  scale = 0.45,
@@ -12325,43 +12383,49 @@ function BattleBoard({
12325
12383
  attackEvent,
12326
12384
  className
12327
12385
  }) {
12328
- const tiles = entity.tiles;
12329
- const features = entity.features ?? [];
12330
- const boardWidth = entity.boardWidth ?? 8;
12331
- const boardHeight = entity.boardHeight ?? 6;
12332
- const assetManifest = entity.assetManifest;
12333
- const backgroundImage = entity.backgroundImage;
12334
- const units = entity.units;
12335
- const selectedUnitId = entity.selectedUnitId;
12336
- const currentPhase = entity.phase;
12337
- const currentTurn = entity.turn;
12338
- 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;
12339
12398
  const eventBus = useEventBus();
12340
12399
  const { t } = hooks.useTranslate();
12341
12400
  const [hoveredTile, setHoveredTile] = React85.useState(null);
12342
12401
  const [isShaking, setIsShaking] = React85.useState(false);
12343
12402
  const selectedUnit = React85.useMemo(
12344
- () => units.find((u) => u.id === selectedUnitId) ?? null,
12403
+ () => units.find((u) => str(u.id) === selectedUnitId) ?? null,
12345
12404
  [units, selectedUnitId]
12346
12405
  );
12347
12406
  const hoveredUnit = React85.useMemo(() => {
12348
12407
  if (!hoveredTile) return null;
12349
- return units.find(
12350
- (u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
12351
- ) ?? 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;
12352
12412
  }, [hoveredTile, units]);
12353
- const playerUnits = React85.useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
12354
- 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]);
12355
12415
  const validMoves = React85.useMemo(() => {
12356
12416
  if (!selectedUnit || currentPhase !== "movement") return [];
12357
12417
  const moves = [];
12358
- const range = selectedUnit.movement;
12418
+ const range = num(selectedUnit.movement);
12419
+ const origin = unitPosition(selectedUnit);
12359
12420
  for (let dy = -range; dy <= range; dy++) {
12360
12421
  for (let dx = -range; dx <= range; dx++) {
12361
- const nx = selectedUnit.position.x + dx;
12362
- const ny = selectedUnit.position.y + dy;
12422
+ const nx = origin.x + dx;
12423
+ const ny = origin.y + dy;
12363
12424
  const dist = Math.abs(dx) + Math.abs(dy);
12364
- 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
+ })) {
12365
12429
  moves.push({ x: nx, y: ny });
12366
12430
  }
12367
12431
  }
@@ -12370,11 +12434,14 @@ function BattleBoard({
12370
12434
  }, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
12371
12435
  const attackTargets = React85.useMemo(() => {
12372
12436
  if (!selectedUnit || currentPhase !== "action") return [];
12373
- return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
12374
- const dx = Math.abs(u.position.x - selectedUnit.position.x);
12375
- 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);
12376
12443
  return dx <= 1 && dy <= 1 && dx + dy > 0;
12377
- }).map((u) => u.position);
12444
+ }).map((u) => unitPosition(u));
12378
12445
  }, [selectedUnit, currentPhase, units]);
12379
12446
  const MOVE_SPEED_MS_PER_TILE = 300;
12380
12447
  const movementAnimRef = React85.useRef(null);
@@ -12414,23 +12481,25 @@ function BattleBoard({
12414
12481
  return () => clearInterval(interval);
12415
12482
  }, []);
12416
12483
  const isoUnits = React85.useMemo(() => {
12417
- return units.filter((u) => u.health > 0).map((unit) => {
12418
- 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;
12419
12488
  return {
12420
- id: unit.id,
12489
+ id,
12421
12490
  position: pos,
12422
- name: unit.name,
12423
- team: unit.team,
12424
- health: unit.health,
12425
- maxHealth: unit.maxHealth,
12426
- unitType: unit.unitType,
12427
- heroId: unit.heroId,
12428
- sprite: unit.sprite,
12429
- traits: unit.traits?.map((t2) => ({
12430
- name: t2.name,
12431
- currentState: t2.currentState,
12432
- states: t2.states,
12433
- 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
12434
12503
  }))
12435
12504
  };
12436
12505
  });
@@ -12442,8 +12511,8 @@ function BattleBoard({
12442
12511
  [scale, baseOffsetX]
12443
12512
  );
12444
12513
  const checkGameEnd = React85.useCallback(() => {
12445
- const pa = units.filter((u) => u.team === "player" && u.health > 0);
12446
- 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);
12447
12516
  if (pa.length === 0) {
12448
12517
  onGameEnd?.("defeat");
12449
12518
  if (gameEndEvent) {
@@ -12457,21 +12526,22 @@ function BattleBoard({
12457
12526
  }
12458
12527
  }, [units, onGameEnd, gameEndEvent, eventBus]);
12459
12528
  const handleUnitClick = React85.useCallback((unitId) => {
12460
- const unit = units.find((u) => u.id === unitId);
12529
+ const unit = units.find((u) => str(u.id) === unitId);
12461
12530
  if (!unit) return;
12462
12531
  if (unitClickEvent) {
12463
12532
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
12464
12533
  }
12465
12534
  if (currentPhase === "action" && selectedUnit) {
12466
- if (unit.team === "enemy" && attackTargets.some((t2) => t2.x === unit.position.x && t2.y === unit.position.y)) {
12467
- 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));
12468
12538
  setIsShaking(true);
12469
12539
  setTimeout(() => setIsShaking(false), 300);
12470
12540
  onAttack?.(selectedUnit, unit, damage);
12471
12541
  if (attackEvent) {
12472
12542
  eventBus.emit(`UI:${attackEvent}`, {
12473
- attackerId: selectedUnit.id,
12474
- targetId: unit.id,
12543
+ attackerId: str(selectedUnit.id),
12544
+ targetId: str(unit.id),
12475
12545
  damage
12476
12546
  });
12477
12547
  }
@@ -12486,9 +12556,9 @@ function BattleBoard({
12486
12556
  if (currentPhase === "movement" && selectedUnit) {
12487
12557
  if (movementAnimRef.current) return;
12488
12558
  if (validMoves.some((m) => m.x === x && m.y === y)) {
12489
- const from = { ...selectedUnit.position };
12559
+ const from = { ...unitPosition(selectedUnit) };
12490
12560
  const to = { x, y };
12491
- startMoveAnimation(selectedUnit.id, from, to, () => {
12561
+ startMoveAnimation(str(selectedUnit.id), from, to, () => {
12492
12562
  onUnitMove?.(selectedUnit, to);
12493
12563
  });
12494
12564
  }
@@ -12646,6 +12716,7 @@ var init_BattleBoard = __esm({
12646
12716
  init_Typography();
12647
12717
  init_Stack();
12648
12718
  init_IsometricCanvas2();
12719
+ init_boardEntity();
12649
12720
  init_isometric();
12650
12721
  BattleBoard.displayName = "BattleBoard";
12651
12722
  }
@@ -13868,24 +13939,24 @@ var init_CodeBlock = __esm({
13868
13939
  return;
13869
13940
  }
13870
13941
  lineEls.forEach((el) => {
13871
- const num = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13872
- if (hiddenLines.has(num)) {
13942
+ const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13943
+ if (hiddenLines.has(num2)) {
13873
13944
  el.style.display = "none";
13874
13945
  return;
13875
13946
  }
13876
13947
  el.style.display = "";
13877
13948
  el.style.position = "relative";
13878
13949
  el.style.paddingLeft = "1.2em";
13879
- const region = foldStartMap.get(num);
13950
+ const region = foldStartMap.get(num2);
13880
13951
  if (!region) return;
13881
- const isCollapsed = collapsed.has(num);
13952
+ const isCollapsed = collapsed.has(num2);
13882
13953
  const toggle = document.createElement("span");
13883
13954
  toggle.className = "fold-toggle";
13884
13955
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13885
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%";
13886
13957
  toggle.addEventListener("click", (e) => {
13887
13958
  e.stopPropagation();
13888
- toggleFoldRef.current(num);
13959
+ toggleFoldRef.current(num2);
13889
13960
  });
13890
13961
  el.insertBefore(toggle, el.firstChild);
13891
13962
  if (isCollapsed) {
@@ -16138,10 +16209,13 @@ var init_BookChapterView = __esm({
16138
16209
  init_cn();
16139
16210
  BookChapterView = ({
16140
16211
  chapter,
16212
+ orbitalSchema,
16141
16213
  direction,
16142
16214
  className
16143
16215
  }) => {
16144
16216
  const { t: _t } = hooks.useTranslate();
16217
+ const title = String(chapter.title ?? "");
16218
+ const content = String(chapter.content ?? "");
16145
16219
  return /* @__PURE__ */ jsxRuntime.jsxs(
16146
16220
  VStack,
16147
16221
  {
@@ -16149,16 +16223,16 @@ var init_BookChapterView = __esm({
16149
16223
  className: cn("px-6 py-8 max-w-4xl mx-auto w-full", className),
16150
16224
  style: { direction },
16151
16225
  children: [
16152
- /* @__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 }),
16153
16227
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
16154
- !!chapter.orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
16228
+ !!orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
16155
16229
  JazariStateMachine,
16156
16230
  {
16157
- schema: chapter.orbitalSchema,
16231
+ schema: orbitalSchema,
16158
16232
  direction
16159
16233
  }
16160
16234
  ) }),
16161
- /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content: chapter.content, direction })
16235
+ /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content, direction })
16162
16236
  ]
16163
16237
  }
16164
16238
  );
@@ -16256,7 +16330,7 @@ var init_BookNavBar = __esm({
16256
16330
  BookNavBar = ({
16257
16331
  currentPage,
16258
16332
  totalPages,
16259
- chapterTitle,
16333
+ chapterTitle: chapterTitle2,
16260
16334
  direction,
16261
16335
  className
16262
16336
  }) => {
@@ -16297,12 +16371,12 @@ var init_BookNavBar = __esm({
16297
16371
  )
16298
16372
  ] }),
16299
16373
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex-1 mx-4 max-w-md", children: [
16300
- chapterTitle && /* @__PURE__ */ jsxRuntime.jsx(
16374
+ chapterTitle2 && /* @__PURE__ */ jsxRuntime.jsx(
16301
16375
  Typography,
16302
16376
  {
16303
16377
  variant: "caption",
16304
16378
  className: "text-center block truncate text-muted-foreground",
16305
- children: chapterTitle
16379
+ children: chapterTitle2
16306
16380
  }
16307
16381
  ),
16308
16382
  /* @__PURE__ */ jsxRuntime.jsx(ProgressBar, { value: progress, size: "sm", variant: "primary" })
@@ -16369,31 +16443,35 @@ var init_BookTableOfContents = __esm({
16369
16443
  style: { direction },
16370
16444
  children: [
16371
16445
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold text-center mb-4", children: t("book.tableOfContents") }),
16372
- parts.map((part, partIdx) => /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
16373
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", children: [
16374
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16375
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", className: "font-semibold", children: part.title })
16376
- ] }),
16377
- /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: part.chapters.map((chapter) => {
16378
- const isCurrent = chapter.id === currentChapterId;
16379
- return /* @__PURE__ */ jsxRuntime.jsx(
16380
- Button,
16381
- {
16382
- variant: "ghost",
16383
- size: "sm",
16384
- action: "BOOK_NAVIGATE",
16385
- actionPayload: { chapterId: chapter.id },
16386
- className: cn(
16387
- "justify-start text-left w-full",
16388
- direction === "rtl" && "text-right",
16389
- isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16390
- ),
16391
- children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: chapter.title }) })
16392
- },
16393
- chapter.id
16394
- );
16395
- }) })
16396
- ] }, 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
+ })
16397
16475
  ]
16398
16476
  }
16399
16477
  );
@@ -16515,27 +16593,41 @@ function resolveFieldMap(fieldMap) {
16515
16593
  function get(obj, key) {
16516
16594
  return obj[key];
16517
16595
  }
16596
+ function asStr(v) {
16597
+ return v == null ? "" : String(v);
16598
+ }
16518
16599
  function mapBookData(raw, fields = IDENTITY_BOOK_FIELDS) {
16519
16600
  const rawParts = get(raw, fields.parts) ?? [];
16520
- return {
16521
- title: get(raw, fields.title) ?? "",
16522
- subtitle: get(raw, fields.subtitle),
16523
- author: get(raw, fields.author),
16524
- coverImageUrl: get(raw, fields.coverImageUrl),
16525
- direction: get(raw, fields.direction) ?? void 0,
16526
- parts: rawParts.map((part) => {
16527
- const rawChapters = get(part, fields.chapters) ?? [];
16528
- return {
16529
- title: get(part, fields.partTitle) ?? "",
16530
- chapters: rawChapters.map((ch) => ({
16531
- id: get(ch, fields.chapterId) ?? "",
16532
- title: get(ch, fields.chapterTitle) ?? "",
16533
- content: get(ch, fields.chapterContent) ?? "",
16534
- orbitalSchema: get(ch, fields.chapterOrbitalSchema)
16535
- }))
16536
- };
16537
- })
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
16538
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 };
16539
16631
  }
16540
16632
  var IDENTITY_BOOK_FIELDS, AR_BOOK_FIELDS, FIELD_MAP_REGISTRY;
16541
16633
  var init_types2 = __esm({
@@ -16573,10 +16665,7 @@ var init_types2 = __esm({
16573
16665
  };
16574
16666
  }
16575
16667
  });
16576
- function flattenChapters(book) {
16577
- return book.parts.flatMap((part) => part.chapters);
16578
- }
16579
- var PRINT_STYLES, BookViewer;
16668
+ var chapterId, chapterTitle, PRINT_STYLES, BookViewer;
16580
16669
  var init_BookViewer = __esm({
16581
16670
  "components/marketing/organisms/book/BookViewer.tsx"() {
16582
16671
  init_Box();
@@ -16589,6 +16678,8 @@ var init_BookViewer = __esm({
16589
16678
  init_BookNavBar();
16590
16679
  init_EmptyState();
16591
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);
16592
16683
  PRINT_STYLES = `
16593
16684
  @media print {
16594
16685
  .book-viewer-page {
@@ -16617,14 +16708,14 @@ var init_BookViewer = __esm({
16617
16708
  return mapBookData(raw, resolvedFieldMap);
16618
16709
  }, [entity, resolvedFieldMap]);
16619
16710
  const direction = book?.direction ?? "ltr";
16620
- const chapters = React85.useMemo(() => book ? flattenChapters(book) : [], [book]);
16711
+ const chapters = React85.useMemo(() => book ? book.chapters : [], [book]);
16621
16712
  const totalPages = 2 + chapters.length;
16622
16713
  const navigateTo = React85.useCallback(
16623
16714
  (page) => {
16624
16715
  const clamped = Math.max(0, Math.min(page, totalPages - 1));
16625
16716
  setCurrentPage(clamped);
16626
- const chapterId = clamped >= 2 ? chapters[clamped - 2]?.id : void 0;
16627
- 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 });
16628
16719
  },
16629
16720
  [totalPages, chapters, eventBus]
16630
16721
  );
@@ -16636,8 +16727,8 @@ var init_BookViewer = __esm({
16636
16727
  eventBus.on("UI:BOOK_PAGE_NEXT", () => navigateTo(currentPage + 1)),
16637
16728
  eventBus.on("UI:BOOK_PRINT", () => window.print()),
16638
16729
  eventBus.on("UI:BOOK_NAVIGATE", (event) => {
16639
- const chapterId = event.payload?.chapterId;
16640
- const idx = chapters.findIndex((ch) => ch.id === chapterId);
16730
+ const targetId = event.payload?.chapterId;
16731
+ const idx = chapters.findIndex((ch) => chapterId(ch) === targetId);
16641
16732
  if (idx >= 0) navigateTo(idx + 2);
16642
16733
  })
16643
16734
  ];
@@ -16654,9 +16745,11 @@ var init_BookViewer = __esm({
16654
16745
  style.remove();
16655
16746
  };
16656
16747
  }, []);
16657
- const currentChapterId = currentPage >= 2 ? chapters[currentPage - 2]?.id : void 0;
16658
- 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;
16659
16750
  if (!book) return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { message: t("book.noData") });
16751
+ const cover = book.cover;
16752
+ const coverTitle = String(cover.title ?? "");
16660
16753
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("relative h-full overflow-hidden bg-background", className), children: [
16661
16754
  /* @__PURE__ */ jsxRuntime.jsxs(
16662
16755
  Box,
@@ -16668,10 +16761,10 @@ var init_BookViewer = __esm({
16668
16761
  /* @__PURE__ */ jsxRuntime.jsx(
16669
16762
  BookCoverPage,
16670
16763
  {
16671
- title: book.title,
16672
- subtitle: book.subtitle,
16673
- author: book.author,
16674
- 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,
16675
16768
  direction
16676
16769
  }
16677
16770
  ),
@@ -16682,23 +16775,27 @@ var init_BookViewer = __esm({
16682
16775
  direction
16683
16776
  }
16684
16777
  ),
16685
- chapters.map((chapter) => /* @__PURE__ */ jsxRuntime.jsx(
16686
- BookChapterView,
16687
- {
16688
- chapter,
16689
- direction
16690
- },
16691
- chapter.id
16692
- ))
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
+ })
16693
16790
  ] }),
16694
16791
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "print:hidden", children: [
16695
16792
  currentPage === 0 && /* @__PURE__ */ jsxRuntime.jsx(
16696
16793
  BookCoverPage,
16697
16794
  {
16698
- title: book.title,
16699
- subtitle: book.subtitle,
16700
- author: book.author,
16701
- 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,
16702
16799
  direction
16703
16800
  }
16704
16801
  ),
@@ -16714,6 +16811,7 @@ var init_BookViewer = __esm({
16714
16811
  BookChapterView,
16715
16812
  {
16716
16813
  chapter: chapters[currentPage - 2],
16814
+ orbitalSchema: currentChapterId ? book.schemaByChapterId[currentChapterId] : void 0,
16717
16815
  direction
16718
16816
  }
16719
16817
  )
@@ -16726,7 +16824,7 @@ var init_BookViewer = __esm({
16726
16824
  {
16727
16825
  currentPage,
16728
16826
  totalPages,
16729
- chapterTitle: currentPage === 0 ? book.title : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16827
+ chapterTitle: currentPage === 0 ? coverTitle : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16730
16828
  direction
16731
16829
  }
16732
16830
  )
@@ -16824,7 +16922,7 @@ var init_Grid = __esm({
16824
16922
  };
16825
16923
  Grid = ({
16826
16924
  cols = 1,
16827
- rows,
16925
+ rows: rows2,
16828
16926
  gap = "md",
16829
16927
  rowGap,
16830
16928
  colGap,
@@ -16836,7 +16934,7 @@ var init_Grid = __esm({
16836
16934
  children,
16837
16935
  as: Component = "div"
16838
16936
  }) => {
16839
- const mergedStyle = rows ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, ...style } : style;
16937
+ const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
16840
16938
  return React85__namespace.default.createElement(
16841
16939
  Component,
16842
16940
  {
@@ -17552,14 +17650,14 @@ function BuilderBoard({
17552
17650
  }) {
17553
17651
  const { emit } = useEventBus();
17554
17652
  const { t } = hooks.useTranslate();
17555
- const resolved = Array.isArray(entity) ? entity[0] : entity;
17653
+ const resolved = boardEntity(entity);
17556
17654
  const [placements, setPlacements] = React85.useState({});
17557
17655
  const [headerError, setHeaderError] = React85.useState(false);
17558
17656
  const [submitted, setSubmitted] = React85.useState(false);
17559
17657
  const [attempts, setAttempts] = React85.useState(0);
17560
17658
  const [showHint, setShowHint] = React85.useState(false);
17561
- const components = resolved?.components ?? [];
17562
- const slots = resolved?.slots ?? [];
17659
+ const components = Array.isArray(resolved?.components) ? resolved.components : [];
17660
+ const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
17563
17661
  const usedComponentIds = new Set(Object.values(placements));
17564
17662
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
17565
17663
  const [selectedComponent, setSelectedComponent] = React85.useState(null);
@@ -17593,7 +17691,7 @@ function BuilderBoard({
17593
17691
  }, [slots, placements, attempts, completeEvent, emit]);
17594
17692
  const handleReset = () => {
17595
17693
  setSubmitted(false);
17596
- if (attempts >= 2 && resolved?.hint) {
17694
+ if (attempts >= 2 && str(resolved?.hint)) {
17597
17695
  setShowHint(true);
17598
17696
  }
17599
17697
  };
@@ -17606,20 +17704,24 @@ function BuilderBoard({
17606
17704
  };
17607
17705
  const getComponentById = (id) => components.find((c) => c.id === id);
17608
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);
17609
17711
  return /* @__PURE__ */ jsxRuntime.jsx(
17610
17712
  Box,
17611
17713
  {
17612
17714
  className,
17613
17715
  style: {
17614
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
17716
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
17615
17717
  backgroundSize: "cover",
17616
17718
  backgroundPosition: "center"
17617
17719
  },
17618
17720
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
17619
- 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,
17620
17722
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17621
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
17622
- /* @__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) })
17623
17725
  ] }) }),
17624
17726
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17625
17727
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.components") }),
@@ -17679,9 +17781,9 @@ function BuilderBoard({
17679
17781
  ] }) }),
17680
17782
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
17681
17783
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
17682
- /* @__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") })
17683
17785
  ] }) }),
17684
- 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 }) }),
17685
17787
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
17686
17788
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17687
17789
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Wrench, size: "sm" }),
@@ -17700,6 +17802,7 @@ var init_BuilderBoard = __esm({
17700
17802
  "components/game/organisms/puzzles/builder/BuilderBoard.tsx"() {
17701
17803
  init_atoms2();
17702
17804
  init_useEventBus();
17805
+ init_boardEntity();
17703
17806
  BuilderBoard.displayName = "BuilderBoard";
17704
17807
  }
17705
17808
  });
@@ -18037,21 +18140,24 @@ function CalendarGrid({
18037
18140
  eventBus.emit(`UI:${longPressEvent}`, { date: day.toISOString(), time, ...longPressPayload });
18038
18141
  }, 500);
18039
18142
  }, [longPressEvent, longPressPayload, eventBus]);
18040
- const renderEvent = (event) => /* @__PURE__ */ jsxRuntime.jsx(
18041
- Box,
18042
- {
18043
- rounded: "md",
18044
- padding: "xs",
18045
- border: true,
18046
- className: cn(
18047
- "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
18048
- event.color ? event.color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
18049
- ),
18050
- onClick: (e) => handleEventClick(event, e),
18051
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
18052
- },
18053
- event.id
18054
- );
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
+ };
18055
18161
  return /* @__PURE__ */ jsxRuntime.jsxs(
18056
18162
  Box,
18057
18163
  {
@@ -19715,7 +19821,6 @@ var init_CardGrid = __esm({
19715
19821
  alignItems = "stretch",
19716
19822
  className,
19717
19823
  children,
19718
- // EntityDisplayProps
19719
19824
  entity,
19720
19825
  isLoading = false,
19721
19826
  error = null,
@@ -20187,14 +20292,14 @@ var init_CaseStudyOrganism = __esm({
20187
20292
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((study) => /* @__PURE__ */ jsxRuntime.jsx(
20188
20293
  CaseStudyCard,
20189
20294
  {
20190
- title: study.title,
20191
- description: study.description,
20192
- category: study.category,
20193
- categoryColor: study.categoryColor,
20194
- href: study.href,
20195
- 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
20196
20301
  },
20197
- study.id
20302
+ String(study.id ?? "")
20198
20303
  )) })
20199
20304
  ] });
20200
20305
  };
@@ -20217,10 +20322,10 @@ function CastleBoard({
20217
20322
  className
20218
20323
  }) {
20219
20324
  const eventBus = useEventBus();
20220
- const resolved = Array.isArray(entity) ? entity[0] : entity;
20221
- const tiles = resolved?.tiles ?? [];
20222
- const features = resolved?.features ?? [];
20223
- 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 : [];
20224
20329
  const assetManifest = resolved?.assetManifest;
20225
20330
  const backgroundImage = resolved?.backgroundImage;
20226
20331
  const [hoveredTile, setHoveredTile] = React85.useState(null);
@@ -20248,7 +20353,7 @@ function CastleBoard({
20248
20353
  onFeatureClick?.(feature);
20249
20354
  if (featureClickEvent) {
20250
20355
  eventBus.emit(`UI:${featureClickEvent}`, {
20251
- featureId: feature.id,
20356
+ featureId: feature.id ?? "",
20252
20357
  featureType: feature.type,
20253
20358
  x: feature.x,
20254
20359
  y: feature.y
@@ -20316,6 +20421,7 @@ var init_CastleBoard = __esm({
20316
20421
  init_cn();
20317
20422
  init_useEventBus();
20318
20423
  init_IsometricCanvas2();
20424
+ init_boardEntity();
20319
20425
  init_isometric();
20320
20426
  CastleBoard.displayName = "CastleBoard";
20321
20427
  }
@@ -21126,14 +21232,14 @@ function ClassifierBoard({
21126
21232
  }) {
21127
21233
  const { emit } = useEventBus();
21128
21234
  const { t } = hooks.useTranslate();
21129
- const resolved = Array.isArray(entity) ? entity[0] : entity;
21235
+ const resolved = boardEntity(entity);
21130
21236
  const [assignments, setAssignments] = React85.useState({});
21131
21237
  const [headerError, setHeaderError] = React85.useState(false);
21132
21238
  const [submitted, setSubmitted] = React85.useState(false);
21133
21239
  const [attempts, setAttempts] = React85.useState(0);
21134
21240
  const [showHint, setShowHint] = React85.useState(false);
21135
- const items = resolved?.items ?? [];
21136
- const categories = resolved?.categories ?? [];
21241
+ const items = Array.isArray(resolved?.items) ? resolved.items : [];
21242
+ const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21137
21243
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21138
21244
  const allAssigned = Object.keys(assignments).length === items.length;
21139
21245
  const results = submitted ? items.map((item) => ({
@@ -21165,7 +21271,7 @@ function ClassifierBoard({
21165
21271
  }, [items, assignments, attempts, completeEvent, emit]);
21166
21272
  const handleReset = () => {
21167
21273
  setSubmitted(false);
21168
- if (attempts >= 2 && resolved?.hint) {
21274
+ if (attempts >= 2 && str(resolved?.hint)) {
21169
21275
  setShowHint(true);
21170
21276
  }
21171
21277
  };
@@ -21176,20 +21282,25 @@ function ClassifierBoard({
21176
21282
  setShowHint(false);
21177
21283
  };
21178
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);
21179
21290
  return /* @__PURE__ */ jsxRuntime.jsx(
21180
21291
  Box,
21181
21292
  {
21182
21293
  className,
21183
21294
  style: {
21184
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
21295
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
21185
21296
  backgroundSize: "cover",
21186
21297
  backgroundPosition: "center"
21187
21298
  },
21188
21299
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
21189
- 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,
21190
21301
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21191
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
21192
- /* @__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) })
21193
21304
  ] }) }),
21194
21305
  unassignedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21195
21306
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("classifier.itemsToSort") }),
@@ -21241,10 +21352,10 @@ function ClassifierBoard({
21241
21352
  }) }),
21242
21353
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
21243
21354
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
21244
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
21245
- !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 })
21246
21357
  ] }) }),
21247
- 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 }) }),
21248
21359
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
21249
21360
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21250
21361
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -21263,6 +21374,7 @@ var init_ClassifierBoard = __esm({
21263
21374
  "components/game/organisms/puzzles/classifier/ClassifierBoard.tsx"() {
21264
21375
  init_atoms2();
21265
21376
  init_useEventBus();
21377
+ init_boardEntity();
21266
21378
  ClassifierBoard.displayName = "ClassifierBoard";
21267
21379
  }
21268
21380
  });
@@ -27633,7 +27745,7 @@ function InventoryPanel({
27633
27745
  const slotArray = Array.from({ length: safeSlots }, (_, index) => {
27634
27746
  return safeItems[index] ?? null;
27635
27747
  });
27636
- const rows = Math.ceil(safeSlots / safeColumns);
27748
+ const rows2 = Math.ceil(safeSlots / safeColumns);
27637
27749
  const handleSlotClick = React85.useCallback((index) => {
27638
27750
  if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
27639
27751
  onSelectSlot?.(index);
@@ -27702,7 +27814,7 @@ function InventoryPanel({
27702
27814
  className: "grid gap-1 bg-[var(--color-card)] p-2 rounded-container border border-border",
27703
27815
  style: {
27704
27816
  gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
27705
- gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
27817
+ gridTemplateRows: `repeat(${rows2}, ${slotSize}px)`
27706
27818
  },
27707
27819
  children: slotArray.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
27708
27820
  "button",
@@ -31666,11 +31778,11 @@ function LatticeSVG({
31666
31778
  }) {
31667
31779
  const paths = [];
31668
31780
  const cols = 5;
31669
- const rows = Math.ceil(h / (w / cols));
31781
+ const rows2 = Math.ceil(h / (w / cols));
31670
31782
  const cellW = w / cols;
31671
31783
  const cellH = cellW;
31672
31784
  const bulge = cellW * 0.3;
31673
- for (let row = 0; row < rows; row++) {
31785
+ for (let row = 0; row < rows2; row++) {
31674
31786
  for (let col = 0; col < cols; col++) {
31675
31787
  const cx = col * cellW + cellW / 2;
31676
31788
  const cy = row * cellH + cellH / 2;
@@ -32082,7 +32194,7 @@ var init_MatrixQuestion = __esm({
32082
32194
  };
32083
32195
  MatrixQuestion = ({
32084
32196
  title,
32085
- rows,
32197
+ rows: rows2,
32086
32198
  columns = DEFAULT_MATRIX_COLUMNS,
32087
32199
  values,
32088
32200
  onChange,
@@ -32092,7 +32204,7 @@ var init_MatrixQuestion = __esm({
32092
32204
  className
32093
32205
  }) => {
32094
32206
  const styles = sizeStyles13[size];
32095
- const safeRows = rows ?? [];
32207
+ const safeRows = rows2 ?? [];
32096
32208
  const safeValues = values ?? {};
32097
32209
  const eventBus = useEventBus();
32098
32210
  const handleChange = React85.useCallback(
@@ -32720,7 +32832,8 @@ var init_PositionedCanvas = __esm({
32720
32832
  dragRef.current = null;
32721
32833
  setDraggingId(null);
32722
32834
  if (!wasDrag) {
32723
- const next = selectedId === item.id ? null : item.id;
32835
+ const itemId = item.id;
32836
+ const next = selectedId === itemId ? null : itemId;
32724
32837
  onSelect?.(next);
32725
32838
  if (selectEvent) {
32726
32839
  eventBus.emit(`UI:${selectEvent}`, { id: next });
@@ -32755,15 +32868,22 @@ var init_PositionedCanvas = __esm({
32755
32868
  style: { width, height },
32756
32869
  onClick: handleContainerClick,
32757
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;
32758
32878
  const status = item.status ?? "empty";
32759
32879
  const shape = item.shape ?? "round";
32760
- const isSelected = selectedId === item.id;
32761
- const isDragging = draggingId === item.id;
32880
+ const isSelected = selectedId === itemId;
32881
+ const isDragging = draggingId === itemId;
32762
32882
  const statusBadge = STATUS_BADGE[status];
32763
32883
  return /* @__PURE__ */ jsxRuntime.jsxs(
32764
32884
  Box,
32765
32885
  {
32766
- "data-testid": `item-node-${item.id}`,
32886
+ "data-testid": `item-node-${itemId}`,
32767
32887
  "data-status": status,
32768
32888
  className: cn(
32769
32889
  "absolute flex flex-col items-center justify-center gap-1 border-2 select-none",
@@ -32774,7 +32894,7 @@ var init_PositionedCanvas = __esm({
32774
32894
  isSelected && "outline outline-2 outline-offset-2 outline-primary shadow-md",
32775
32895
  isDragging && "shadow-lg z-10"
32776
32896
  ),
32777
- style: { left: item.x, top: item.y, touchAction: "none" },
32897
+ style: { left: x, top: y, touchAction: "none" },
32778
32898
  onPointerDown: (e) => handlePointerDown(e, item),
32779
32899
  onPointerMove: handlePointerMove,
32780
32900
  onPointerUp: (e) => handlePointerUp(e, item),
@@ -32782,10 +32902,10 @@ var init_PositionedCanvas = __esm({
32782
32902
  children: [
32783
32903
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1", children: [
32784
32904
  getStatusIcon(status),
32785
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: item.label })
32905
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: label })
32786
32906
  ] }),
32787
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: item.partySize !== void 0 && status === "seated" ? `${item.partySize}/${item.capacity}` : `Cap ${item.capacity}` }),
32788
- 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 }),
32789
32909
  isSelected && /* @__PURE__ */ jsxRuntime.jsx(
32790
32910
  Badge,
32791
32911
  {
@@ -32797,7 +32917,7 @@ var init_PositionedCanvas = __esm({
32797
32917
  )
32798
32918
  ]
32799
32919
  },
32800
- item.id
32920
+ itemId
32801
32921
  );
32802
32922
  })
32803
32923
  }
@@ -33569,9 +33689,10 @@ var init_RichBlockEditor = __esm({
33569
33689
  });
33570
33690
  function collectInitiallyCollapsed(nodes, acc) {
33571
33691
  for (const n of nodes) {
33692
+ const replies = n.replies;
33572
33693
  if (n.collapsed) acc.add(n.id);
33573
- if (n.replies && n.replies.length > 0) {
33574
- collectInitiallyCollapsed(n.replies, acc);
33694
+ if (replies && replies.length > 0) {
33695
+ collectInitiallyCollapsed(replies, acc);
33575
33696
  }
33576
33697
  }
33577
33698
  }
@@ -33601,44 +33722,52 @@ var init_ReplyTree = __esm({
33601
33722
  }) => {
33602
33723
  const eventBus = useEventBus();
33603
33724
  const { t } = hooks.useTranslate();
33604
- const hasReplies = !!node.replies && node.replies.length > 0;
33605
- 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);
33606
33735
  const atMaxDepth = depth >= maxDepth;
33607
33736
  const [replyOpen, setReplyOpen] = React85.useState(false);
33608
33737
  const [draft, setDraft] = React85.useState("");
33609
33738
  const handleVote = React85.useCallback(
33610
33739
  (next) => {
33611
- onVote?.(node.id, next);
33612
- 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 });
33613
33742
  },
33614
- [node.id, onVote, voteEvent, eventBus]
33743
+ [nodeId, onVote, voteEvent, eventBus]
33615
33744
  );
33616
33745
  const handleReply = React85.useCallback(() => {
33617
- onReply?.(node.id);
33746
+ onReply?.(nodeId);
33618
33747
  setReplyOpen((open) => !open);
33619
- }, [node.id, onReply]);
33748
+ }, [nodeId, onReply]);
33620
33749
  const handleSubmitReply = React85.useCallback(() => {
33621
- const content = draft.trim();
33622
- if (!content) return;
33623
- 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 });
33624
33753
  setDraft("");
33625
33754
  setReplyOpen(false);
33626
- }, [node.id, draft, replyEvent, eventBus]);
33755
+ }, [nodeId, draft, replyEvent, eventBus]);
33627
33756
  const handleCancelReply = React85.useCallback(() => {
33628
33757
  setDraft("");
33629
33758
  setReplyOpen(false);
33630
33759
  }, []);
33631
33760
  const handleFlag = React85.useCallback(() => {
33632
- onFlag?.(node.id);
33633
- if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId: node.id });
33634
- }, [node.id, onFlag, flagEvent, eventBus]);
33761
+ onFlag?.(nodeId);
33762
+ if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId });
33763
+ }, [nodeId, onFlag, flagEvent, eventBus]);
33635
33764
  const handleContinue = React85.useCallback(() => {
33636
- onContinueThread?.(node.id);
33637
- if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId: node.id });
33638
- }, [node.id, onContinueThread, continueThreadEvent, eventBus]);
33765
+ onContinueThread?.(nodeId);
33766
+ if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId });
33767
+ }, [nodeId, onContinueThread, continueThreadEvent, eventBus]);
33639
33768
  const handleToggle = React85.useCallback(() => {
33640
- toggleCollapse(node.id);
33641
- }, [node.id, toggleCollapse]);
33769
+ toggleCollapse(nodeId);
33770
+ }, [nodeId, toggleCollapse]);
33642
33771
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-stretch min-w-0", children: [
33643
33772
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-col items-center flex-shrink-0 w-6", children: [
33644
33773
  hasReplies ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -33670,25 +33799,25 @@ var init_ReplyTree = __esm({
33670
33799
  /* @__PURE__ */ jsxRuntime.jsx(
33671
33800
  Avatar,
33672
33801
  {
33673
- src: node.authorAvatarUrl,
33674
- name: node.authorName,
33802
+ src: authorAvatarUrl,
33803
+ name: authorName,
33675
33804
  size: "sm"
33676
33805
  }
33677
33806
  ),
33678
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: node.authorName }),
33679
- /* @__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 })
33680
33809
  ] }),
33681
- /* @__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 }),
33682
33811
  showActions && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
33683
33812
  /* @__PURE__ */ jsxRuntime.jsx(
33684
33813
  VoteStack,
33685
33814
  {
33686
- count: node.voteCount ?? 0,
33687
- userVote: node.userVote ?? null,
33815
+ count: voteCount ?? 0,
33816
+ userVote: userVote ?? null,
33688
33817
  onVote: handleVote,
33689
33818
  size: "sm",
33690
33819
  variant: "horizontal",
33691
- label: t("replyTree.voteOnReplyBy", { author: node.authorName })
33820
+ label: t("replyTree.voteOnReplyBy", { author: authorName })
33692
33821
  }
33693
33822
  ),
33694
33823
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -33698,7 +33827,7 @@ var init_ReplyTree = __esm({
33698
33827
  size: "sm",
33699
33828
  leftIcon: "message-square",
33700
33829
  onClick: handleReply,
33701
- "aria-label": t("replyTree.replyTo", { author: node.authorName }),
33830
+ "aria-label": t("replyTree.replyTo", { author: authorName }),
33702
33831
  children: t("replyTree.reply")
33703
33832
  }
33704
33833
  ),
@@ -33709,7 +33838,7 @@ var init_ReplyTree = __esm({
33709
33838
  size: "sm",
33710
33839
  leftIcon: "flag",
33711
33840
  onClick: handleFlag,
33712
- "aria-label": t("replyTree.flagReplyBy", { author: node.authorName }),
33841
+ "aria-label": t("replyTree.flagReplyBy", { author: authorName }),
33713
33842
  children: t("replyTree.flag")
33714
33843
  }
33715
33844
  )
@@ -33721,9 +33850,9 @@ var init_ReplyTree = __esm({
33721
33850
  inputType: "textarea",
33722
33851
  rows: 2,
33723
33852
  value: draft,
33724
- placeholder: t("replyTree.replyToPlaceholder", { author: node.authorName }),
33853
+ placeholder: t("replyTree.replyToPlaceholder", { author: authorName }),
33725
33854
  onChange: (e) => setDraft(e.target.value),
33726
- "aria-label": t("replyTree.replyTo", { author: node.authorName })
33855
+ "aria-label": t("replyTree.replyTo", { author: authorName })
33727
33856
  }
33728
33857
  ),
33729
33858
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
@@ -33754,7 +33883,7 @@ var init_ReplyTree = __esm({
33754
33883
  ),
33755
33884
  children: t("replyTree.continueThread")
33756
33885
  }
33757
- ) : /* @__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(
33758
33887
  ReplyTreeNode,
33759
33888
  {
33760
33889
  node: child,
@@ -36074,8 +36203,8 @@ var init_WizardContainer = __esm({
36074
36203
  return void 0;
36075
36204
  if (typeof controlledStep === "number") return controlledStep;
36076
36205
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
36077
- const num = Number(controlledStep);
36078
- return isNaN(num) ? void 0 : num;
36206
+ const num2 = Number(controlledStep);
36207
+ return isNaN(num2) ? void 0 : num2;
36079
36208
  })();
36080
36209
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
36081
36210
  const totalSteps = steps.length;
@@ -37780,7 +37909,7 @@ function DebuggerBoard({
37780
37909
  }) {
37781
37910
  const { emit } = useEventBus();
37782
37911
  const { t } = hooks.useTranslate();
37783
- const resolved = Array.isArray(entity) ? entity[0] : entity;
37912
+ const resolved = boardEntity(entity);
37784
37913
  const [flaggedLines, setFlaggedLines] = React85.useState(/* @__PURE__ */ new Set());
37785
37914
  const [headerError, setHeaderError] = React85.useState(false);
37786
37915
  const [submitted, setSubmitted] = React85.useState(false);
@@ -37798,7 +37927,7 @@ function DebuggerBoard({
37798
37927
  return next;
37799
37928
  });
37800
37929
  };
37801
- const lines = resolved?.lines ?? [];
37930
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37802
37931
  const bugLines = lines.filter((l) => l.isBug);
37803
37932
  const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
37804
37933
  const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
@@ -37813,7 +37942,7 @@ function DebuggerBoard({
37813
37942
  }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37814
37943
  const handleReset = () => {
37815
37944
  setSubmitted(false);
37816
- if (attempts >= 2 && resolved?.hint) {
37945
+ if (attempts >= 2 && str(resolved?.hint)) {
37817
37946
  setShowHint(true);
37818
37947
  }
37819
37948
  };
@@ -37824,24 +37953,28 @@ function DebuggerBoard({
37824
37953
  setShowHint(false);
37825
37954
  };
37826
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);
37827
37960
  return /* @__PURE__ */ jsxRuntime.jsx(
37828
37961
  Box,
37829
37962
  {
37830
37963
  className,
37831
37964
  style: {
37832
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
37965
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
37833
37966
  backgroundSize: "cover",
37834
37967
  backgroundPosition: "center"
37835
37968
  },
37836
37969
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
37837
- 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,
37838
37971
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37839
37972
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
37840
37973
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Bug, size: "sm" }),
37841
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title })
37974
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
37842
37975
  ] }),
37843
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description }),
37844
- /* @__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)) }) })
37845
37978
  ] }) }),
37846
37979
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
37847
37980
  const isFlagged = flaggedLines.has(line.id);
@@ -37873,7 +38006,7 @@ function DebuggerBoard({
37873
38006
  );
37874
38007
  }) }) }),
37875
38008
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37876
- /* @__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")}` }),
37877
38010
  bugLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "start", children: [
37878
38011
  /* @__PURE__ */ jsxRuntime.jsx(
37879
38012
  Icon,
@@ -37889,7 +38022,7 @@ function DebuggerBoard({
37889
38022
  ] })
37890
38023
  ] }, line.id))
37891
38024
  ] }) }),
37892
- 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 }) }),
37893
38026
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
37894
38027
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
37895
38028
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -37908,6 +38041,7 @@ var init_DebuggerBoard = __esm({
37908
38041
  "components/game/organisms/puzzles/debugger/DebuggerBoard.tsx"() {
37909
38042
  init_atoms2();
37910
38043
  init_useEventBus();
38044
+ init_boardEntity();
37911
38045
  DebuggerBoard.displayName = "DebuggerBoard";
37912
38046
  }
37913
38047
  });
@@ -37945,7 +38079,7 @@ function getBadgeVariant(fieldName, value) {
37945
38079
  return "default";
37946
38080
  }
37947
38081
  function formatFieldLabel(fieldName) {
37948
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
38082
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase());
37949
38083
  }
37950
38084
  function formatFieldValue(value, fieldName) {
37951
38085
  if (typeof value === "number") {
@@ -37964,26 +38098,26 @@ function formatFieldValue(value, fieldName) {
37964
38098
  }
37965
38099
  function renderRichFieldValue(value, fieldName, fieldType) {
37966
38100
  if (value === void 0 || value === null) return "\u2014";
37967
- const str = String(value);
38101
+ const str2 = String(value);
37968
38102
  switch (fieldType) {
37969
38103
  case "image":
37970
38104
  case "url": {
37971
- 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/")) {
37972
38106
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
37973
38107
  "img",
37974
38108
  {
37975
- src: str,
38109
+ src: str2,
37976
38110
  alt: formatFieldLabel(fieldName),
37977
38111
  className: "max-w-full max-h-64 rounded-md object-contain",
37978
38112
  loading: "lazy"
37979
38113
  }
37980
38114
  ) });
37981
38115
  }
37982
- return str;
38116
+ return str2;
37983
38117
  }
37984
38118
  case "markdown":
37985
38119
  case "richtext":
37986
- 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(
37987
38121
  Box,
37988
38122
  {
37989
38123
  className: "prose prose-sm max-w-none",
@@ -38001,11 +38135,11 @@ function renderRichFieldValue(value, fieldName, fieldType) {
38001
38135
  "--tw-prose-th-borders": "var(--color-border)",
38002
38136
  "--tw-prose-td-borders": "var(--color-border)"
38003
38137
  },
38004
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str })
38138
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str2 })
38005
38139
  }
38006
38140
  ) });
38007
38141
  case "code":
38008
- 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 }) }) });
38009
38143
  case "html":
38010
38144
  return /* @__PURE__ */ jsxRuntime.jsx(
38011
38145
  Box,
@@ -38025,12 +38159,12 @@ function renderRichFieldValue(value, fieldName, fieldType) {
38025
38159
  "--tw-prose-th-borders": "var(--color-border)",
38026
38160
  "--tw-prose-td-borders": "var(--color-border)"
38027
38161
  },
38028
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str })
38162
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str2 })
38029
38163
  }
38030
38164
  );
38031
38165
  case "date":
38032
38166
  case "datetime": {
38033
- const d = new Date(str);
38167
+ const d = new Date(str2);
38034
38168
  if (!isNaN(d.getTime())) {
38035
38169
  return d.toLocaleDateString(void 0, {
38036
38170
  year: "numeric",
@@ -38039,7 +38173,7 @@ function renderRichFieldValue(value, fieldName, fieldType) {
38039
38173
  ...fieldType === "datetime" ? { hour: "2-digit", minute: "2-digit" } : {}
38040
38174
  });
38041
38175
  }
38042
- return str;
38176
+ return str2;
38043
38177
  }
38044
38178
  default:
38045
38179
  return formatFieldValue(value, fieldName);
@@ -38717,6 +38851,40 @@ var init_RuleEditor = __esm({
38717
38851
  RuleEditor.displayName = "RuleEditor";
38718
38852
  }
38719
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
+ });
38720
38888
  function ObjectRulePanel({
38721
38889
  object,
38722
38890
  onRulesChange,
@@ -38724,55 +38892,63 @@ function ObjectRulePanel({
38724
38892
  className
38725
38893
  }) {
38726
38894
  const { t } = hooks.useTranslate();
38727
- const maxRules = object.maxRules || 3;
38728
- 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;
38729
38905
  const handleRuleChange = React85.useCallback((index, updatedRule) => {
38730
- const newRules = [...object.rules];
38906
+ const newRules = [...rules];
38731
38907
  newRules[index] = updatedRule;
38732
- onRulesChange(object.id, newRules);
38733
- }, [object.id, object.rules, onRulesChange]);
38908
+ onRulesChange(id, newRules);
38909
+ }, [id, rules, onRulesChange]);
38734
38910
  const handleRuleRemove = React85.useCallback((index) => {
38735
- const newRules = object.rules.filter((_, i) => i !== index);
38736
- onRulesChange(object.id, newRules);
38737
- }, [object.id, object.rules, onRulesChange]);
38911
+ const newRules = rules.filter((_, i) => i !== index);
38912
+ onRulesChange(id, newRules);
38913
+ }, [id, rules, onRulesChange]);
38738
38914
  const handleAddRule = React85.useCallback(() => {
38739
38915
  if (!canAdd || disabled) return;
38740
- const firstEvent = object.availableEvents[0]?.value || "";
38741
- const firstAction = object.availableActions[0]?.value || "";
38916
+ const firstEvent = availableEvents[0]?.value || "";
38917
+ const firstAction = availableActions[0]?.value || "";
38742
38918
  const newRule = {
38743
38919
  id: `rule-${nextRuleId++}`,
38744
38920
  whenEvent: firstEvent,
38745
38921
  thenAction: firstAction
38746
38922
  };
38747
- onRulesChange(object.id, [...object.rules, newRule]);
38748
- }, [canAdd, disabled, object, onRulesChange]);
38923
+ onRulesChange(id, [...rules, newRule]);
38924
+ }, [canAdd, disabled, id, rules, availableEvents, availableActions, onRulesChange]);
38749
38925
  const machine = {
38750
- name: object.name,
38751
- states: object.states,
38752
- currentState: object.currentState,
38753
- transitions: object.rules.map((r) => ({
38754
- from: object.currentState,
38755
- 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,
38756
38932
  event: r.whenEvent
38757
38933
  }))
38758
38934
  };
38759
38935
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
38760
38936
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center", gap: "sm", children: [
38761
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: object.icon }),
38937
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: icon }),
38762
38938
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
38763
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
38764
- /* @__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 })
38765
38941
  ] })
38766
38942
  ] }),
38767
38943
  /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
38768
38944
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38769
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
38770
- 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(
38771
38947
  RuleEditor,
38772
38948
  {
38773
38949
  rule,
38774
- availableEvents: object.availableEvents,
38775
- availableActions: object.availableActions,
38950
+ availableEvents,
38951
+ availableActions,
38776
38952
  onChange: (r) => handleRuleChange(i, r),
38777
38953
  onRemove: () => handleRuleRemove(i),
38778
38954
  disabled
@@ -38790,6 +38966,7 @@ var init_ObjectRulePanel = __esm({
38790
38966
  init_cn();
38791
38967
  init_TraitStateViewer();
38792
38968
  init_RuleEditor();
38969
+ init_puzzleObject();
38793
38970
  nextRuleId = 1;
38794
38971
  ObjectRulePanel.displayName = "ObjectRulePanel";
38795
38972
  }
@@ -38856,11 +39033,11 @@ function EventHandlerBoard({
38856
39033
  }) {
38857
39034
  const { emit } = useEventBus();
38858
39035
  const { t } = hooks.useTranslate();
38859
- const resolved = Array.isArray(entity) ? entity[0] : entity;
38860
- const entityObjects = resolved?.objects ?? [];
38861
- const [objects, setObjects] = React85.useState(entityObjects);
39036
+ const resolved = boardEntity(entity);
39037
+ const entityObjects = rows(resolved?.objects);
39038
+ const [objects, setObjects] = React85.useState(() => [...entityObjects]);
38862
39039
  const [selectedObjectId, setSelectedObjectId] = React85.useState(
38863
- entityObjects[0]?.id || null
39040
+ entityObjects[0] ? objId(entityObjects[0]) : null
38864
39041
  );
38865
39042
  const [headerError, setHeaderError] = React85.useState(false);
38866
39043
  const [playState, setPlayState] = React85.useState("editing");
@@ -38871,10 +39048,10 @@ function EventHandlerBoard({
38871
39048
  React85.useEffect(() => () => {
38872
39049
  if (timerRef.current) clearTimeout(timerRef.current);
38873
39050
  }, []);
38874
- const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
39051
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
38875
39052
  const handleRulesChange = React85.useCallback((objectId, rules) => {
38876
39053
  setObjects((prev) => prev.map(
38877
- (o) => o.id === objectId ? { ...o, rules } : o
39054
+ (o) => objId(o) === objectId ? { ...o, rules } : o
38878
39055
  ));
38879
39056
  }, []);
38880
39057
  const addLogEntry = React85.useCallback((icon, message, status = "done") => {
@@ -38888,11 +39065,12 @@ function EventHandlerBoard({
38888
39065
  setEventLog([]);
38889
39066
  const allRules = [];
38890
39067
  objects.forEach((obj) => {
38891
- obj.rules.forEach((rule) => {
39068
+ objRules(obj).forEach((rule) => {
38892
39069
  allRules.push({ object: obj, rule });
38893
39070
  });
38894
39071
  });
38895
- const triggers = resolved?.triggerEvents || [];
39072
+ const triggers = Array.isArray(resolved?.triggerEvents) ? resolved.triggerEvents : [];
39073
+ const goalEvent = str(resolved?.goalEvent);
38896
39074
  const eventQueue = [...triggers];
38897
39075
  const firedEvents = /* @__PURE__ */ new Set();
38898
39076
  let stepIdx = 0;
@@ -38921,14 +39099,14 @@ function EventHandlerBoard({
38921
39099
  addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
38922
39100
  } else {
38923
39101
  matching.forEach(({ object, rule }) => {
38924
- 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");
38925
39103
  eventQueue.push(rule.thenAction);
38926
- if (rule.thenAction === resolved?.goalEvent) {
39104
+ if (rule.thenAction === goalEvent) {
38927
39105
  goalReached = true;
38928
39106
  }
38929
39107
  });
38930
39108
  }
38931
- if (currentEvent === resolved?.goalEvent) {
39109
+ if (currentEvent === goalEvent) {
38932
39110
  goalReached = true;
38933
39111
  }
38934
39112
  stepIdx++;
@@ -38946,65 +39124,75 @@ function EventHandlerBoard({
38946
39124
  }, []);
38947
39125
  const handleReset = React85.useCallback(() => {
38948
39126
  if (timerRef.current) clearTimeout(timerRef.current);
38949
- setObjects(resolved?.objects ?? []);
39127
+ const resetObjects = rows(resolved?.objects);
39128
+ setObjects([...resetObjects]);
38950
39129
  setPlayState("editing");
38951
39130
  setEventLog([]);
38952
- setSelectedObjectId((resolved?.objects ?? [])[0]?.id || null);
39131
+ setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
38953
39132
  setAttempts(0);
38954
39133
  }, [resolved?.objects]);
38955
39134
  if (!resolved) return null;
38956
39135
  const objectViewers = objects.map((obj) => {
39136
+ const states = objStates(obj);
39137
+ const currentState = objCurrentState(obj);
38957
39138
  const machine = {
38958
- name: obj.name,
38959
- states: obj.states,
38960
- currentState: obj.currentState,
38961
- transitions: obj.rules.map((r) => ({
38962
- from: obj.currentState,
38963
- 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,
38964
39145
  event: r.whenEvent
38965
39146
  }))
38966
39147
  };
38967
39148
  return { obj, machine };
38968
39149
  });
38969
- 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);
38970
39155
  const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
38971
39156
  return /* @__PURE__ */ jsxRuntime.jsxs(
38972
39157
  VStack,
38973
39158
  {
38974
39159
  className: cn("p-4 gap-6", className),
38975
39160
  style: {
38976
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
39161
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
38977
39162
  backgroundSize: "cover",
38978
39163
  backgroundPosition: "center"
38979
39164
  },
38980
39165
  children: [
38981
- 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,
38982
39167
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38983
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
38984
- /* @__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) }),
38985
39170
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
38986
39171
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
38987
- /* @__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) })
38988
39173
  ] })
38989
39174
  ] }),
38990
39175
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38991
39176
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
38992
- /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsxRuntime.jsx(
38993
- Box,
38994
- {
38995
- className: cn(
38996
- "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
38997
- selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
38998
- ),
38999
- onClick: () => setSelectedObjectId(obj.id),
39000
- children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
39001
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: obj.icon }),
39002
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
39003
- /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
39004
- ] })
39005
- },
39006
- obj.id
39007
- )) })
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
+ }) })
39008
39196
  ] }),
39009
39197
  selectedObject && /* @__PURE__ */ jsxRuntime.jsx(
39010
39198
  ObjectRulePanel,
@@ -39015,12 +39203,12 @@ function EventHandlerBoard({
39015
39203
  }
39016
39204
  ),
39017
39205
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
39018
- 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") }) }),
39019
39207
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39020
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) }) }),
39021
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: [
39022
39210
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
39023
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
39211
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
39024
39212
  ] }) })
39025
39213
  ] }),
39026
39214
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -39048,6 +39236,8 @@ var init_EventHandlerBoard = __esm({
39048
39236
  init_TraitStateViewer();
39049
39237
  init_ObjectRulePanel();
39050
39238
  init_EventLog();
39239
+ init_puzzleObject();
39240
+ init_boardEntity();
39051
39241
  ENCOURAGEMENT_KEYS = [
39052
39242
  "puzzle.tryAgain1",
39053
39243
  "puzzle.tryAgain2",
@@ -39136,7 +39326,10 @@ var init_FeatureGridOrganism = __esm({
39136
39326
  );
39137
39327
  React85.useCallback(
39138
39328
  (feature) => {
39139
- 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
+ });
39140
39333
  },
39141
39334
  [eventBus]
39142
39335
  );
@@ -39146,14 +39339,17 @@ var init_FeatureGridOrganism = __esm({
39146
39339
  if (error) {
39147
39340
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
39148
39341
  }
39149
- const featureCards = items.map((feature) => ({
39150
- icon: feature.icon,
39151
- title: feature.title,
39152
- description: feature.description,
39153
- href: feature.href,
39154
- linkLabel: feature.linkLabel,
39155
- variant: feature.href ? "interactive" : "bordered"
39156
- }));
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
+ });
39157
39353
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
39158
39354
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
39159
39355
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
@@ -40471,22 +40667,24 @@ var init_HeroOrganism = __esm({
40471
40667
  () => Array.isArray(entity) ? entity[0] : entity && typeof entity === "object" ? entity : void 0,
40472
40668
  [entity]
40473
40669
  );
40670
+ const primaryAction = resolved?.primaryAction;
40671
+ const secondaryAction = resolved?.secondaryAction;
40474
40672
  const handlePrimaryClick = React85.useCallback(() => {
40475
- if (resolved?.primaryAction) {
40673
+ if (primaryAction) {
40476
40674
  eventBus.emit("UI:CTA_PRIMARY", {
40477
- label: resolved.primaryAction.label,
40478
- href: resolved.primaryAction.href
40675
+ label: String(primaryAction.label ?? ""),
40676
+ href: String(primaryAction.href ?? "")
40479
40677
  });
40480
40678
  }
40481
- }, [eventBus, resolved]);
40679
+ }, [eventBus, primaryAction]);
40482
40680
  const handleSecondaryClick = React85.useCallback(() => {
40483
- if (resolved?.secondaryAction) {
40681
+ if (secondaryAction) {
40484
40682
  eventBus.emit("UI:CTA_SECONDARY", {
40485
- label: resolved.secondaryAction.label,
40486
- href: resolved.secondaryAction.href
40683
+ label: String(secondaryAction.label ?? ""),
40684
+ href: String(secondaryAction.href ?? "")
40487
40685
  });
40488
40686
  }
40489
- }, [eventBus, resolved]);
40687
+ }, [eventBus, secondaryAction]);
40490
40688
  if (isLoading) {
40491
40689
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
40492
40690
  }
@@ -40496,17 +40694,19 @@ var init_HeroOrganism = __esm({
40496
40694
  if (!resolved) {
40497
40695
  return null;
40498
40696
  }
40697
+ const imageRaw = resolved.image;
40698
+ const image = imageRaw ? { src: String(imageRaw.src ?? ""), alt: String(imageRaw.alt ?? "") } : void 0;
40499
40699
  return /* @__PURE__ */ jsxRuntime.jsxs(
40500
40700
  HeroSection,
40501
40701
  {
40502
- tag: resolved.tag,
40503
- title: resolved.title,
40504
- titleAccent: resolved.titleAccent,
40505
- subtitle: resolved.subtitle,
40506
- primaryAction: resolved.primaryAction ? { label: resolved.primaryAction.label, href: resolved.primaryAction.href } : void 0,
40507
- secondaryAction: resolved.secondaryAction ? { label: resolved.secondaryAction.label, href: resolved.secondaryAction.href } : void 0,
40508
- installCommand: resolved.installCommand,
40509
- 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,
40510
40710
  imagePosition: resolved.imagePosition,
40511
40711
  background: resolved.background,
40512
40712
  className: cn(className),
@@ -40515,8 +40715,8 @@ var init_HeroOrganism = __esm({
40515
40715
  /* @__PURE__ */ jsxRuntime.jsx(
40516
40716
  _HeroClickInterceptor,
40517
40717
  {
40518
- hasPrimary: !!resolved.primaryAction,
40519
- hasSecondary: !!resolved.secondaryAction,
40718
+ hasPrimary: !!primaryAction,
40719
+ hasSecondary: !!secondaryAction,
40520
40720
  onPrimaryClick: handlePrimaryClick,
40521
40721
  onSecondaryClick: handleSecondaryClick
40522
40722
  }
@@ -40745,7 +40945,7 @@ function formatValue3(value, fieldName) {
40745
40945
  return String(value);
40746
40946
  }
40747
40947
  function formatFieldLabel2(fieldName) {
40748
- 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();
40749
40949
  }
40750
40950
  var STATUS_STYLES2, StatusBadge, ProgressIndicator, List3;
40751
40951
  var init_List = __esm({
@@ -41592,20 +41792,22 @@ function NegotiatorBoard({
41592
41792
  }) {
41593
41793
  const { emit } = useEventBus();
41594
41794
  const { t } = hooks.useTranslate();
41595
- const resolved = Array.isArray(entity) ? entity[0] : entity;
41795
+ const resolved = boardEntity(entity);
41596
41796
  const [history, setHistory] = React85.useState([]);
41597
41797
  const [headerError, setHeaderError] = React85.useState(false);
41598
41798
  const [showHint, setShowHint] = React85.useState(false);
41799
+ const totalRounds = num(resolved?.totalRounds);
41800
+ const targetScore = num(resolved?.targetScore);
41599
41801
  const currentRound = history.length;
41600
- const isComplete = currentRound >= (resolved?.totalRounds ?? 0);
41802
+ const isComplete = currentRound >= totalRounds;
41601
41803
  const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
41602
41804
  const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
41603
- const won = isComplete && playerTotal >= (resolved?.targetScore ?? 0);
41604
- const actions = resolved?.actions ?? [];
41605
- 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 : [];
41606
41808
  const handleAction = React85.useCallback((actionId) => {
41607
41809
  if (isComplete) return;
41608
- const opponentAction = getOpponentAction(resolved?.opponentStrategy ?? "random", actions, history);
41810
+ const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
41609
41811
  const payoff = payoffMatrix.find(
41610
41812
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
41611
41813
  );
@@ -41618,42 +41820,46 @@ function NegotiatorBoard({
41618
41820
  };
41619
41821
  const newHistory = [...history, result];
41620
41822
  setHistory(newHistory);
41621
- if (newHistory.length >= (resolved?.totalRounds ?? 0)) {
41823
+ if (newHistory.length >= totalRounds) {
41622
41824
  const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
41623
- if (total >= (resolved?.targetScore ?? 0)) {
41825
+ if (total >= targetScore) {
41624
41826
  emit(`UI:${completeEvent}`, { success: true, score: total });
41625
41827
  }
41626
- if (newHistory.length >= 3 && resolved?.hint) {
41828
+ if (newHistory.length >= 3 && str(resolved?.hint)) {
41627
41829
  setShowHint(true);
41628
41830
  }
41629
41831
  }
41630
- }, [isComplete, resolved, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41832
+ }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41631
41833
  const handleReset = () => {
41632
41834
  setHistory([]);
41633
41835
  setShowHint(false);
41634
41836
  };
41635
41837
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
41636
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);
41637
41843
  return /* @__PURE__ */ jsxRuntime.jsx(
41638
41844
  Box,
41639
41845
  {
41640
41846
  className,
41641
41847
  style: {
41642
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
41848
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
41643
41849
  backgroundSize: "cover",
41644
41850
  backgroundPosition: "center"
41645
41851
  },
41646
41852
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
41647
- 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,
41648
41854
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
41649
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
41650
- /* @__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) }),
41651
41857
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", children: [
41652
- /* @__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) }) }),
41653
41859
  /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", children: [
41654
41860
  t("negotiator.target"),
41655
41861
  ": ",
41656
- resolved.targetScore
41862
+ targetScore
41657
41863
  ] })
41658
41864
  ] })
41659
41865
  ] }) }),
@@ -41702,16 +41908,16 @@ function NegotiatorBoard({
41702
41908
  ] }) }),
41703
41909
  isComplete && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
41704
41910
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.CheckCircle, size: "lg", className: won ? "text-success" : "text-error" }),
41705
- /* @__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") }),
41706
41912
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
41707
41913
  t("negotiator.finalScore"),
41708
41914
  ": ",
41709
41915
  playerTotal,
41710
41916
  "/",
41711
- resolved.targetScore
41917
+ targetScore
41712
41918
  ] })
41713
41919
  ] }) }),
41714
- 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 }) }),
41715
41921
  isComplete && !won && /* @__PURE__ */ jsxRuntime.jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
41716
41922
  ] })
41717
41923
  }
@@ -41721,6 +41927,7 @@ var init_NegotiatorBoard = __esm({
41721
41927
  "components/game/organisms/puzzles/negotiator/NegotiatorBoard.tsx"() {
41722
41928
  init_atoms2();
41723
41929
  init_useEventBus();
41930
+ init_boardEntity();
41724
41931
  NegotiatorBoard.displayName = "NegotiatorBoard";
41725
41932
  }
41726
41933
  });
@@ -41756,13 +41963,13 @@ var init_PricingOrganism = __esm({
41756
41963
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
41757
41964
  }
41758
41965
  const plans = items.map((plan) => ({
41759
- name: plan.name,
41760
- price: plan.price,
41761
- description: plan.description,
41762
- features: plan.features,
41763
- action: { label: plan.actionLabel, href: plan.actionHref },
41764
- highlighted: plan.highlighted,
41765
- 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
41766
41973
  }));
41767
41974
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
41768
41975
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -43833,16 +44040,20 @@ function SequencerBoard({
43833
44040
  }) {
43834
44041
  const { emit } = useEventBus();
43835
44042
  const { t } = hooks.useTranslate();
43836
- 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;
43837
44048
  const [headerError, setHeaderError] = React85.useState(false);
43838
44049
  const [slots, setSlots] = React85.useState(
43839
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0)
44050
+ () => Array.from({ length: maxSlots }, () => void 0)
43840
44051
  );
43841
44052
  const [playState, setPlayState] = React85.useState("idle");
43842
44053
  const [currentStep, setCurrentStep] = React85.useState(-1);
43843
44054
  const [attempts, setAttempts] = React85.useState(0);
43844
44055
  const [slotFeedback, setSlotFeedback] = React85.useState(
43845
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => null)
44056
+ () => Array.from({ length: maxSlots }, () => null)
43846
44057
  );
43847
44058
  const timerRef = React85.useRef(null);
43848
44059
  React85.useEffect(() => () => {
@@ -43876,17 +44087,17 @@ function SequencerBoard({
43876
44087
  }, [emit]);
43877
44088
  const handleReset = React85.useCallback(() => {
43878
44089
  if (timerRef.current) clearTimeout(timerRef.current);
43879
- setSlots(Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0));
44090
+ setSlots(Array.from({ length: maxSlots }, () => void 0));
43880
44091
  setPlayState("idle");
43881
44092
  setCurrentStep(-1);
43882
44093
  setAttempts(0);
43883
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
43884
- }, [resolved?.maxSlots]);
44094
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44095
+ }, [maxSlots]);
43885
44096
  const filledSlots = slots.filter((s) => !!s);
43886
44097
  const canPlay = filledSlots.length > 0 && playState === "idle";
43887
44098
  const handlePlay = React85.useCallback(() => {
43888
44099
  if (!canPlay) return;
43889
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
44100
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
43890
44101
  emit("UI:PLAY_SOUND", { key: "confirm" });
43891
44102
  const sequence = slots.map((s) => s?.id || "");
43892
44103
  if (playEvent) {
@@ -43897,10 +44108,10 @@ function SequencerBoard({
43897
44108
  let step = 0;
43898
44109
  const advance = () => {
43899
44110
  step++;
43900
- if (step >= (resolved?.maxSlots ?? 0)) {
44111
+ if (step >= maxSlots) {
43901
44112
  const playerSeq = slots.map((s) => s?.id);
43902
44113
  const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
43903
- const success = (resolved?.solutions ?? []).some(
44114
+ const success = solutions.some(
43904
44115
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
43905
44116
  );
43906
44117
  if (success) {
@@ -43912,7 +44123,7 @@ function SequencerBoard({
43912
44123
  }
43913
44124
  } else {
43914
44125
  setAttempts((prev) => prev + 1);
43915
- const feedback = computeSlotFeedback(playerSeq, resolved?.solutions ?? []);
44126
+ const feedback = computeSlotFeedback(playerSeq, solutions);
43916
44127
  setSlotFeedback(feedback);
43917
44128
  setPlayState("idle");
43918
44129
  setCurrentStep(-1);
@@ -43930,10 +44141,10 @@ function SequencerBoard({
43930
44141
  }
43931
44142
  };
43932
44143
  timerRef.current = setTimeout(advance, stepDurationMs);
43933
- }, [canPlay, slots, resolved?.maxSlots, resolved?.solutions, stepDurationMs, playEvent, completeEvent, emit]);
44144
+ }, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, emit]);
43934
44145
  const machine = {
43935
- name: resolved?.title ?? "",
43936
- description: resolved?.description ?? "",
44146
+ name: str(resolved?.title),
44147
+ description: str(resolved?.description),
43937
44148
  states: slots.map((s, i) => stepLabel(s, i)),
43938
44149
  currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
43939
44150
  transitions: slots.slice(0, -1).map((s, i) => ({
@@ -43942,37 +44153,41 @@ function SequencerBoard({
43942
44153
  event: "NEXT"
43943
44154
  }))
43944
44155
  };
43945
- const usedIds = resolved?.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
43946
- 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;
43947
44159
  const hasFeedback = slotFeedback.some((f3) => f3 !== null);
43948
44160
  const correctCount = slotFeedback.filter((f3) => f3 === "correct").length;
43949
44161
  const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
43950
44162
  if (!resolved) return null;
44163
+ const theme = resolved.theme ?? void 0;
44164
+ const themeBackground = theme?.background;
44165
+ const headerImage = str(resolved.headerImage);
43951
44166
  return /* @__PURE__ */ jsxRuntime.jsxs(
43952
44167
  VStack,
43953
44168
  {
43954
44169
  className: cn("p-4 gap-6", className),
43955
44170
  style: {
43956
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44171
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
43957
44172
  backgroundSize: "cover",
43958
44173
  backgroundPosition: "center"
43959
44174
  },
43960
44175
  children: [
43961
- 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,
43962
44177
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43963
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
43964
- /* @__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) })
43965
44180
  ] }),
43966
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: [
43967
44182
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
43968
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
44183
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
43969
44184
  ] }) }),
43970
44185
  filledSlots.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
43971
44186
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43972
44187
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
43973
44188
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
43974
44189
  hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
43975
- `${correctCount}/${resolved.maxSlots} `,
44190
+ `${correctCount}/${maxSlots} `,
43976
44191
  "\u2705"
43977
44192
  ] })
43978
44193
  ] }),
@@ -43980,7 +44195,7 @@ function SequencerBoard({
43980
44195
  SequenceBar,
43981
44196
  {
43982
44197
  slots,
43983
- maxSlots: resolved.maxSlots,
44198
+ maxSlots,
43984
44199
  onSlotDrop: handleSlotDrop,
43985
44200
  onSlotRemove: handleSlotRemove,
43986
44201
  playing: playState === "playing",
@@ -43994,15 +44209,15 @@ function SequencerBoard({
43994
44209
  playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
43995
44210
  ActionPalette,
43996
44211
  {
43997
- actions: resolved.availableActions,
44212
+ actions: availableActions,
43998
44213
  usedActionIds: usedIds,
43999
- allowDuplicates: resolved.allowDuplicates !== false,
44214
+ allowDuplicates,
44000
44215
  categoryColors,
44001
44216
  label: t("sequencer.dragActions")
44002
44217
  }
44003
44218
  ),
44004
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) }) }),
44005
- 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") }) }),
44006
44221
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
44007
44222
  /* @__PURE__ */ jsxRuntime.jsx(
44008
44223
  Button,
@@ -44026,6 +44241,7 @@ var init_SequencerBoard = __esm({
44026
44241
  init_cn();
44027
44242
  init_useEventBus();
44028
44243
  init_TraitStateViewer();
44244
+ init_boardEntity();
44029
44245
  init_SequenceBar();
44030
44246
  init_ActionPalette();
44031
44247
  ENCOURAGEMENT_KEYS2 = [
@@ -44075,18 +44291,21 @@ var init_ShowcaseOrganism = __esm({
44075
44291
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
44076
44292
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", color: "muted", align: "center", className: "max-w-2xl", children: subtitle })
44077
44293
  ] }),
44078
- /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
44079
- ShowcaseCard,
44080
- {
44081
- title: item.title,
44082
- description: item.description,
44083
- image: item.image,
44084
- href: item.href,
44085
- badge: item.badge,
44086
- accentColor: item.accentColor
44087
- },
44088
- item.id
44089
- )) })
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
+ }) })
44090
44309
  ] });
44091
44310
  };
44092
44311
  ShowcaseOrganism.displayName = "ShowcaseOrganism";
@@ -44454,8 +44673,8 @@ function SimulatorBoard({
44454
44673
  }) {
44455
44674
  const { emit } = useEventBus();
44456
44675
  const { t } = hooks.useTranslate();
44457
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44458
- const parameters = resolved?.parameters ?? [];
44676
+ const resolved = boardEntity(entity);
44677
+ const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
44459
44678
  const [values, setValues] = React85.useState(() => {
44460
44679
  const init = {};
44461
44680
  for (const p2 of parameters) {
@@ -44469,15 +44688,15 @@ function SimulatorBoard({
44469
44688
  const [showHint, setShowHint] = React85.useState(false);
44470
44689
  const computeOutput = React85.useCallback((params) => {
44471
44690
  try {
44472
- const fn = new Function("params", `return (${resolved?.computeExpression})`);
44691
+ const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
44473
44692
  return fn(params);
44474
44693
  } catch {
44475
44694
  return 0;
44476
44695
  }
44477
44696
  }, [resolved?.computeExpression]);
44478
44697
  const output = React85.useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
44479
- const targetValue = resolved?.targetValue ?? 0;
44480
- const targetTolerance = resolved?.targetTolerance ?? 0;
44698
+ const targetValue = num(resolved?.targetValue);
44699
+ const targetTolerance = num(resolved?.targetTolerance);
44481
44700
  const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
44482
44701
  const handleParameterChange = (id, value) => {
44483
44702
  if (submitted) return;
@@ -44492,7 +44711,7 @@ function SimulatorBoard({
44492
44711
  };
44493
44712
  const handleReset = () => {
44494
44713
  setSubmitted(false);
44495
- if (attempts >= 2 && resolved?.hint) {
44714
+ if (attempts >= 2 && str(resolved?.hint)) {
44496
44715
  setShowHint(true);
44497
44716
  }
44498
44717
  };
@@ -44507,20 +44726,26 @@ function SimulatorBoard({
44507
44726
  setShowHint(false);
44508
44727
  };
44509
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);
44510
44735
  return /* @__PURE__ */ jsxRuntime.jsx(
44511
44736
  Box,
44512
44737
  {
44513
44738
  className,
44514
44739
  style: {
44515
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44740
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
44516
44741
  backgroundSize: "cover",
44517
44742
  backgroundPosition: "center"
44518
44743
  },
44519
44744
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
44520
- 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,
44521
44746
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
44522
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
44523
- /* @__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) })
44524
44749
  ] }) }),
44525
44750
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "md", children: [
44526
44751
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
@@ -44561,28 +44786,28 @@ function SimulatorBoard({
44561
44786
  ] }, param.id))
44562
44787
  ] }) }),
44563
44788
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
44564
- /* @__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 }),
44565
44790
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", weight: "bold", children: [
44566
44791
  output.toFixed(2),
44567
44792
  " ",
44568
- resolved.outputUnit
44793
+ outputUnit
44569
44794
  ] }),
44570
44795
  submitted && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
44571
44796
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: isCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
44572
- /* @__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") })
44573
44798
  ] }),
44574
44799
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44575
44800
  t("simulator.target"),
44576
44801
  ": ",
44577
44802
  targetValue,
44578
44803
  " ",
44579
- resolved.outputUnit,
44804
+ outputUnit,
44580
44805
  " (\xB1",
44581
44806
  targetTolerance,
44582
44807
  ")"
44583
44808
  ] })
44584
44809
  ] }) }),
44585
- 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 }) }),
44586
44811
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
44587
44812
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
44588
44813
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Play, size: "sm" }),
@@ -44601,6 +44826,7 @@ var init_SimulatorBoard = __esm({
44601
44826
  "components/game/organisms/puzzles/simulator/SimulatorBoard.tsx"() {
44602
44827
  init_atoms2();
44603
44828
  init_useEventBus();
44829
+ init_boardEntity();
44604
44830
  SimulatorBoard.displayName = "SimulatorBoard";
44605
44831
  }
44606
44832
  });
@@ -45026,22 +45252,25 @@ function VariablePanel({
45026
45252
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
45027
45253
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
45028
45254
  variables.map((v) => {
45029
- const max = v.max ?? 100;
45030
- const min = v.min ?? 0;
45031
- 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);
45032
45261
  const isHigh = pct > 80;
45033
45262
  const isLow = pct < 20;
45034
45263
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
45035
45264
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
45036
- /* @__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 }),
45037
45266
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: cn(
45038
45267
  isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
45039
45268
  ), children: [
45040
- v.value,
45041
- v.unit || "",
45269
+ value,
45270
+ unit,
45042
45271
  " / ",
45043
45272
  max,
45044
- v.unit || ""
45273
+ unit
45045
45274
  ] })
45046
45275
  ] }),
45047
45276
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -45052,14 +45281,19 @@ function VariablePanel({
45052
45281
  size: "sm"
45053
45282
  }
45054
45283
  )
45055
- ] }, v.name);
45284
+ ] }, name);
45056
45285
  })
45057
45286
  ] });
45058
45287
  }
45288
+ var numField;
45059
45289
  var init_VariablePanel = __esm({
45060
45290
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
45061
45291
  init_atoms2();
45062
45292
  init_cn();
45293
+ numField = (v, fallback = 0) => {
45294
+ const n = Number(v);
45295
+ return Number.isFinite(n) ? n : fallback;
45296
+ };
45063
45297
  VariablePanel.displayName = "VariablePanel";
45064
45298
  }
45065
45299
  });
@@ -45086,14 +45320,21 @@ function StateArchitectBoard({
45086
45320
  }) {
45087
45321
  const { emit } = useEventBus();
45088
45322
  const { t } = hooks.useTranslate();
45089
- const resolved = Array.isArray(entity) ? entity[0] : entity;
45090
- 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);
45091
45332
  const [headerError, setHeaderError] = React85.useState(false);
45092
45333
  const [playState, setPlayState] = React85.useState("editing");
45093
- const [currentState, setCurrentState] = React85.useState(resolved?.initialState ?? "");
45334
+ const [currentState, setCurrentState] = React85.useState(initialState);
45094
45335
  const [selectedState, setSelectedState] = React85.useState(null);
45095
45336
  const [testResults, setTestResults] = React85.useState([]);
45096
- const [variables, setVariables] = React85.useState(resolved?.variables ?? []);
45337
+ const [variables, setVariables] = React85.useState(() => [...entityVariables]);
45097
45338
  const [attempts, setAttempts] = React85.useState(0);
45098
45339
  const timerRef = React85.useRef(null);
45099
45340
  const [addingFrom, setAddingFrom] = React85.useState(null);
@@ -45102,12 +45343,12 @@ function StateArchitectBoard({
45102
45343
  }, []);
45103
45344
  const GRAPH_W = 500;
45104
45345
  const GRAPH_H = 400;
45105
- 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]);
45106
45347
  const handleStateClick = React85.useCallback((state) => {
45107
45348
  if (playState !== "editing") return;
45108
45349
  if (addingFrom) {
45109
45350
  if (addingFrom !== state) {
45110
- const event = resolved?.availableEvents[0] || "EVENT";
45351
+ const event = availableEvents[0] || "EVENT";
45111
45352
  const newTrans = {
45112
45353
  id: `t-${nextTransId++}`,
45113
45354
  from: addingFrom,
@@ -45120,7 +45361,7 @@ function StateArchitectBoard({
45120
45361
  } else {
45121
45362
  setSelectedState(state);
45122
45363
  }
45123
- }, [playState, addingFrom, resolved?.availableEvents]);
45364
+ }, [playState, addingFrom, availableEvents]);
45124
45365
  const handleStartAddTransition = React85.useCallback(() => {
45125
45366
  if (!selectedState) return;
45126
45367
  setAddingFrom(selectedState);
@@ -45129,9 +45370,9 @@ function StateArchitectBoard({
45129
45370
  setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45130
45371
  }, []);
45131
45372
  const machine = React85.useMemo(() => ({
45132
- name: resolved?.entityName ?? "",
45133
- description: resolved?.description ?? "",
45134
- states: resolved?.states ?? [],
45373
+ name: entityName,
45374
+ description: str(resolved?.description),
45375
+ states: entityStates,
45135
45376
  currentState,
45136
45377
  transitions: transitions.map((t2) => ({
45137
45378
  from: t2.from,
@@ -45139,7 +45380,7 @@ function StateArchitectBoard({
45139
45380
  event: t2.event,
45140
45381
  guardHint: t2.guardHint
45141
45382
  }))
45142
- }), [resolved, currentState, transitions]);
45383
+ }), [entityName, resolved, entityStates, currentState, transitions]);
45143
45384
  const handleTest = React85.useCallback(() => {
45144
45385
  if (playState !== "editing") return;
45145
45386
  if (testEvent) emit(`UI:${testEvent}`, {});
@@ -45148,7 +45389,7 @@ function StateArchitectBoard({
45148
45389
  const results = [];
45149
45390
  let testIdx = 0;
45150
45391
  const runNextTest = () => {
45151
- if (testIdx >= (resolved?.testCases.length ?? 0)) {
45392
+ if (testIdx >= testCases.length) {
45152
45393
  const allPassed = results.every((r) => r.passed);
45153
45394
  setPlayState(allPassed ? "success" : "fail");
45154
45395
  setTestResults(results);
@@ -45163,9 +45404,9 @@ function StateArchitectBoard({
45163
45404
  }
45164
45405
  return;
45165
45406
  }
45166
- const testCase = resolved?.testCases[testIdx];
45407
+ const testCase = testCases[testIdx];
45167
45408
  if (!testCase) return;
45168
- let state = resolved.initialState;
45409
+ let state = initialState;
45169
45410
  for (const event of testCase.events) {
45170
45411
  const trans = transitions.find((t2) => t2.from === state && t2.event === event);
45171
45412
  if (trans) {
@@ -45183,53 +45424,57 @@ function StateArchitectBoard({
45183
45424
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45184
45425
  };
45185
45426
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45186
- }, [playState, transitions, resolved, stepDurationMs, testEvent, completeEvent, emit]);
45427
+ }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45187
45428
  const handleTryAgain = React85.useCallback(() => {
45188
45429
  if (timerRef.current) clearTimeout(timerRef.current);
45189
45430
  setPlayState("editing");
45190
- setCurrentState(resolved?.initialState ?? "");
45431
+ setCurrentState(initialState);
45191
45432
  setTestResults([]);
45192
- }, [resolved?.initialState]);
45433
+ }, [initialState]);
45193
45434
  const handleReset = React85.useCallback(() => {
45194
45435
  if (timerRef.current) clearTimeout(timerRef.current);
45195
- setTransitions(resolved?.transitions ?? []);
45436
+ setTransitions(entityTransitions);
45196
45437
  setPlayState("editing");
45197
- setCurrentState(resolved?.initialState ?? "");
45438
+ setCurrentState(initialState);
45198
45439
  setTestResults([]);
45199
- setVariables(resolved?.variables ?? []);
45440
+ setVariables([...entityVariables]);
45200
45441
  setSelectedState(null);
45201
45442
  setAddingFrom(null);
45202
45443
  setAttempts(0);
45203
- }, [resolved]);
45444
+ }, [entityTransitions, initialState, entityVariables]);
45204
45445
  const codeData = React85.useMemo(() => ({
45205
- name: resolved?.entityName ?? "",
45206
- states: resolved?.states ?? [],
45207
- initialState: resolved?.initialState ?? "",
45446
+ name: entityName,
45447
+ states: entityStates,
45448
+ initialState,
45208
45449
  transitions: transitions.map((t2) => ({
45209
45450
  from: t2.from,
45210
45451
  to: t2.to,
45211
45452
  event: t2.event,
45212
45453
  ...t2.guardHint ? { guard: t2.guardHint } : {}
45213
45454
  }))
45214
- }), [resolved, transitions]);
45455
+ }), [entityName, entityStates, initialState, transitions]);
45215
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);
45216
45461
  return /* @__PURE__ */ jsxRuntime.jsxs(
45217
45462
  VStack,
45218
45463
  {
45219
45464
  className: cn("p-4 gap-6", className),
45220
45465
  style: {
45221
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
45466
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
45222
45467
  backgroundSize: "cover",
45223
45468
  backgroundPosition: "center"
45224
45469
  },
45225
45470
  children: [
45226
- 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,
45227
45472
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
45228
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
45229
- /* @__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) }),
45230
45475
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
45231
45476
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
45232
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.hint })
45477
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: hint })
45233
45478
  ] })
45234
45479
  ] }),
45235
45480
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
@@ -45277,14 +45522,14 @@ function StateArchitectBoard({
45277
45522
  ]
45278
45523
  }
45279
45524
  ),
45280
- resolved.states.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
45525
+ entityStates.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
45281
45526
  StateNode2,
45282
45527
  {
45283
45528
  name: state,
45284
45529
  position: positions[state],
45285
45530
  isCurrent: state === currentState,
45286
45531
  isSelected: state === selectedState,
45287
- isInitial: state === resolved.initialState,
45532
+ isInitial: state === initialState,
45288
45533
  onClick: () => handleStateClick(state)
45289
45534
  },
45290
45535
  state
@@ -45331,7 +45576,7 @@ function StateArchitectBoard({
45331
45576
  /* @__PURE__ */ jsxRuntime.jsx(
45332
45577
  VariablePanel,
45333
45578
  {
45334
- entityName: resolved.entityName,
45579
+ entityName,
45335
45580
  variables
45336
45581
  }
45337
45582
  ),
@@ -45346,12 +45591,12 @@ function StateArchitectBoard({
45346
45591
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(CodeView, { data: codeData, label: "View Code" })
45347
45592
  ] })
45348
45593
  ] }),
45349
- 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") }) }),
45350
45595
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
45351
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]) }) }),
45352
- 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: [
45353
45598
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
45354
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
45599
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
45355
45600
  ] }) })
45356
45601
  ] }),
45357
45602
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -45381,6 +45626,7 @@ var init_StateArchitectBoard = __esm({
45381
45626
  init_TransitionArrow();
45382
45627
  init_VariablePanel();
45383
45628
  init_CodeView();
45629
+ init_boardEntity();
45384
45630
  ENCOURAGEMENT_KEYS3 = [
45385
45631
  "puzzle.tryAgain1",
45386
45632
  "puzzle.tryAgain2",
@@ -45417,8 +45663,8 @@ var init_StatsOrganism = __esm({
45417
45663
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45418
45664
  }
45419
45665
  const stats = items.map((item) => ({
45420
- value: item.value,
45421
- label: item.label
45666
+ value: String(item.value ?? ""),
45667
+ label: String(item.label ?? "")
45422
45668
  }));
45423
45669
  return /* @__PURE__ */ jsxRuntime.jsx(
45424
45670
  StatsGrid,
@@ -45464,10 +45710,10 @@ var init_StepFlowOrganism = __esm({
45464
45710
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45465
45711
  }
45466
45712
  const steps = items.map((item) => ({
45467
- number: item.number,
45468
- title: item.title,
45469
- description: item.description,
45470
- 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
45471
45717
  }));
45472
45718
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
45473
45719
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -45640,13 +45886,13 @@ var init_TeamOrganism = __esm({
45640
45886
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((member) => /* @__PURE__ */ jsxRuntime.jsx(
45641
45887
  TeamCard,
45642
45888
  {
45643
- name: member.name,
45644
- nameAr: member.nameAr,
45645
- role: member.role,
45646
- bio: member.bio,
45647
- 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
45648
45894
  },
45649
- member.id
45895
+ String(member.id ?? "")
45650
45896
  )) })
45651
45897
  ] });
45652
45898
  };
@@ -45883,8 +46129,8 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45883
46129
  const [turn, setTurn] = React85.useState(1);
45884
46130
  const [gameResult, setGameResult] = React85.useState(null);
45885
46131
  const checkGameEnd = React85.useCallback((currentUnits) => {
45886
- const pa = currentUnits.filter((u) => u.team === "player" && u.health > 0);
45887
- 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);
45888
46134
  if (pa.length === 0) {
45889
46135
  setGameResult("defeat");
45890
46136
  setPhase("game_over");
@@ -45902,34 +46148,36 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45902
46148
  }
45903
46149
  }, [onGameEnd, gameEndEvent, eventBus]);
45904
46150
  const handleUnitClick = React85.useCallback((unitId) => {
45905
- const unit = units.find((u) => u.id === unitId);
46151
+ const unit = units.find((u) => str(u.id) === unitId);
45906
46152
  if (!unit) return;
45907
46153
  if (unitClickEvent) {
45908
46154
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
45909
46155
  }
45910
46156
  if (phase === "observation" || phase === "selection") {
45911
- if (unit.team === "player") {
46157
+ if (unitTeam(unit) === "player") {
45912
46158
  setSelectedUnitId(unitId);
45913
46159
  setPhase("movement");
45914
46160
  }
45915
46161
  } else if (phase === "action") {
45916
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46162
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45917
46163
  if (!selectedUnit) return;
45918
- if (unit.team === "enemy") {
45919
- const dx = Math.abs(unit.position.x - selectedUnit.position.x);
45920
- 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);
45921
46169
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
45922
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
45923
- 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);
45924
46172
  const updatedUnits = units.map(
45925
- (u) => u.id === unit.id ? { ...u, health: newHealth } : u
46173
+ (u) => str(u.id) === str(unit.id) ? { ...u, health: newHealth } : u
45926
46174
  );
45927
46175
  setUnits(updatedUnits);
45928
46176
  onAttack?.(selectedUnit, unit, damage);
45929
46177
  if (attackEvent) {
45930
46178
  eventBus.emit(`UI:${attackEvent}`, {
45931
- attackerId: selectedUnit.id,
45932
- targetId: unit.id,
46179
+ attackerId: str(selectedUnit.id),
46180
+ targetId: str(unit.id),
45933
46181
  damage
45934
46182
  });
45935
46183
  }
@@ -45946,16 +46194,20 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45946
46194
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
45947
46195
  }
45948
46196
  if (phase === "movement" && selectedUnitId) {
45949
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
46197
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45950
46198
  if (!selectedUnit) return;
45951
- const dx = Math.abs(x - selectedUnit.position.x);
45952
- 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);
45953
46202
  const dist = dx + dy;
45954
- if (dist > 0 && dist <= selectedUnit.movement) {
45955
- 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
+ })) {
45956
46208
  setUnits(
45957
46209
  (prev) => prev.map(
45958
- (u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u
46210
+ (u) => str(u.id) === selectedUnitId ? { ...u, position: { x, y } } : u
45959
46211
  )
45960
46212
  );
45961
46213
  setPhase("action");
@@ -45998,12 +46250,13 @@ var init_useBattleState = __esm({
45998
46250
  "components/game/organisms/hooks/useBattleState.ts"() {
45999
46251
  "use client";
46000
46252
  init_useEventBus();
46253
+ init_boardEntity();
46001
46254
  }
46002
46255
  });
46003
46256
  function UncontrolledBattleBoard({ entity, ...rest }) {
46004
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46257
+ const resolved = boardEntity(entity);
46005
46258
  const battleState = useBattleState(
46006
- resolved?.initialUnits ?? [],
46259
+ rows(resolved?.initialUnits),
46007
46260
  {
46008
46261
  tileClickEvent: rest.tileClickEvent,
46009
46262
  unitClickEvent: rest.unitClickEvent,
@@ -46039,10 +46292,23 @@ function UncontrolledBattleBoard({ entity, ...rest }) {
46039
46292
  var init_UncontrolledBattleBoard = __esm({
46040
46293
  "components/game/organisms/UncontrolledBattleBoard.tsx"() {
46041
46294
  init_BattleBoard();
46295
+ init_boardEntity();
46042
46296
  init_useBattleState();
46043
46297
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
46044
46298
  }
46045
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
+ }
46046
46312
  function defaultIsInRange(from, to, range) {
46047
46313
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
46048
46314
  }
@@ -46073,36 +46339,36 @@ function WorldMapBoard({
46073
46339
  className
46074
46340
  }) {
46075
46341
  const eventBus = useEventBus();
46076
- const resolved = Array.isArray(entity) ? entity[0] : entity;
46077
- const hexes = resolved?.hexes ?? [];
46078
- const heroes = resolved?.heroes ?? [];
46079
- const features = resolved?.features ?? [];
46080
- 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;
46081
46347
  const assetManifest = resolved?.assetManifest;
46082
46348
  const backgroundImage = resolved?.backgroundImage;
46083
46349
  const [hoveredTile, setHoveredTile] = React85.useState(null);
46084
46350
  const selectedHero = React85.useMemo(
46085
- () => heroes.find((h) => h.id === selectedHeroId) ?? null,
46351
+ () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46086
46352
  [heroes, selectedHeroId]
46087
46353
  );
46088
46354
  const tiles = React85.useMemo(
46089
46355
  () => hexes.map((hex) => ({
46090
- x: hex.x,
46091
- y: hex.y,
46092
- terrain: hex.terrain,
46093
- 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)
46094
46360
  })),
46095
46361
  [hexes]
46096
46362
  );
46097
46363
  const baseUnits = React85.useMemo(
46098
46364
  () => heroes.map((hero) => ({
46099
- id: hero.id,
46100
- position: hero.position,
46101
- name: hero.name,
46102
- 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",
46103
46369
  health: 100,
46104
46370
  maxHealth: 100,
46105
- sprite: hero.sprite
46371
+ sprite: hero.sprite == null ? void 0 : str(hero.sprite)
46106
46372
  })),
46107
46373
  [heroes]
46108
46374
  );
@@ -46143,73 +46409,94 @@ function WorldMapBoard({
46143
46409
  const isoUnits = React85.useMemo(() => {
46144
46410
  if (movingPositions.size === 0) return baseUnits;
46145
46411
  return baseUnits.map((u) => {
46146
- const pos = movingPositions.get(u.id);
46412
+ const pos = u.id == null ? void 0 : movingPositions.get(u.id);
46147
46413
  return pos ? { ...u, position: pos } : u;
46148
46414
  });
46149
46415
  }, [baseUnits, movingPositions]);
46150
46416
  const validMoves = React85.useMemo(() => {
46151
- 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);
46152
46421
  const moves = [];
46153
46422
  hexes.forEach((hex) => {
46154
- if (hex.passable === false) return;
46155
- if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
46156
- if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
46157
- if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
46158
- 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 });
46159
46433
  });
46160
46434
  return moves;
46161
46435
  }, [selectedHero, hexes, heroes, isInRange]);
46162
46436
  const attackTargets = React85.useMemo(() => {
46163
- if (!selectedHero || selectedHero.movement <= 0) return [];
46164
- 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));
46165
46442
  }, [selectedHero, heroes, isInRange]);
46166
- const maxY = Math.max(...hexes.map((h) => h.y), 0);
46443
+ const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
46167
46444
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46168
46445
  const tileToScreen = React85.useCallback(
46169
46446
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46170
46447
  [scale, baseOffsetX]
46171
46448
  );
46172
46449
  const hoveredHex = React85.useMemo(
46173
- () => 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,
46174
46451
  [hoveredTile, hexes]
46175
46452
  );
46176
46453
  const hoveredHero = React85.useMemo(
46177
- () => 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,
46178
46458
  [hoveredTile, heroes]
46179
46459
  );
46180
46460
  const handleTileClick = React85.useCallback((x, y) => {
46181
46461
  if (movementAnimRef.current) return;
46182
- 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);
46183
46463
  if (!hex) return;
46184
46464
  if (tileClickEvent) {
46185
46465
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46186
46466
  }
46187
46467
  if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
46188
- startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
46189
- onHeroMove?.(selectedHero.id, x, y);
46468
+ const heroId = str(selectedHero.id);
46469
+ startMoveAnimation(heroId, { ...heroPosition(selectedHero) }, { x, y }, () => {
46470
+ onHeroMove?.(heroId, x, y);
46190
46471
  if (heroMoveEvent) {
46191
- eventBus.emit(`UI:${heroMoveEvent}`, { heroId: selectedHero.id, toX: x, toY: y });
46472
+ eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46192
46473
  }
46193
- if (hex.feature && hex.feature !== "none") {
46194
- onFeatureEnter?.(selectedHero.id, hex);
46474
+ const feature = str(hex.feature);
46475
+ if (feature && feature !== "none") {
46476
+ onFeatureEnter?.(heroId, hex);
46195
46477
  if (featureEnterEvent) {
46196
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId: selectedHero.id, feature: hex.feature, hex });
46478
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
46197
46479
  }
46198
46480
  }
46199
46481
  });
46200
46482
  return;
46201
46483
  }
46202
- 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
+ });
46203
46488
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46204
- onBattleEncounter?.(selectedHero.id, enemy.id);
46489
+ const attackerId = str(selectedHero.id);
46490
+ const defenderId = str(enemy.id);
46491
+ onBattleEncounter?.(attackerId, defenderId);
46205
46492
  if (battleEncounterEvent) {
46206
- eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId: selectedHero.id, defenderId: enemy.id });
46493
+ eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46207
46494
  }
46208
46495
  }
46209
46496
  }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46210
46497
  const handleUnitClick = React85.useCallback((unitId) => {
46211
- const hero = heroes.find((h) => h.id === unitId);
46212
- if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
46498
+ const hero = heroes.find((h) => str(h.id) === unitId);
46499
+ if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
46213
46500
  onHeroSelect?.(unitId);
46214
46501
  if (heroSelectEvent) {
46215
46502
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
@@ -46282,6 +46569,7 @@ var init_WorldMapBoard = __esm({
46282
46569
  init_Stack();
46283
46570
  init_LoadingState();
46284
46571
  init_IsometricCanvas2();
46572
+ init_boardEntity();
46285
46573
  init_isometric();
46286
46574
  WorldMapBoard.displayName = "WorldMapBoard";
46287
46575
  }