@almadar/ui 5.21.12 → 5.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/avl/index.cjs +919 -631
  2. package/dist/avl/index.js +919 -631
  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
@@ -2503,7 +2503,7 @@ var init_SvgGrid = __esm({
2503
2503
  x,
2504
2504
  y,
2505
2505
  cols = 4,
2506
- rows = 3,
2506
+ rows: rows2 = 3,
2507
2507
  spacing = 20,
2508
2508
  nodeRadius = 3,
2509
2509
  color = "var(--color-primary)",
@@ -2512,7 +2512,7 @@ var init_SvgGrid = __esm({
2512
2512
  highlights = []
2513
2513
  }) => {
2514
2514
  const highlightSet = new Set(highlights);
2515
- return /* @__PURE__ */ jsxRuntime.jsx("g", { className, opacity, children: Array.from({ length: rows }).map(
2515
+ return /* @__PURE__ */ jsxRuntime.jsx("g", { className, opacity, children: Array.from({ length: rows2 }).map(
2516
2516
  (_, row) => Array.from({ length: cols }).map((_2, col) => {
2517
2517
  const index = row * cols + col;
2518
2518
  const isHighlighted = highlightSet.has(index);
@@ -3172,7 +3172,7 @@ var init_Input = __esm({
3172
3172
  onClear,
3173
3173
  value,
3174
3174
  options,
3175
- rows = 3,
3175
+ rows: rows2 = 3,
3176
3176
  onChange,
3177
3177
  ...props
3178
3178
  }, ref) => {
@@ -3222,7 +3222,7 @@ var init_Input = __esm({
3222
3222
  ref,
3223
3223
  value,
3224
3224
  onChange,
3225
- rows,
3225
+ rows: rows2,
3226
3226
  className: baseClassName,
3227
3227
  ...props
3228
3228
  }
@@ -5478,66 +5478,6 @@ var init_RangeSlider = __esm({
5478
5478
  RangeSlider.displayName = "RangeSlider";
5479
5479
  }
5480
5480
  });
5481
- function easeOut(t) {
5482
- return t * (2 - t);
5483
- }
5484
- var AnimatedCounter;
5485
- var init_AnimatedCounter = __esm({
5486
- "components/marketing/atoms/AnimatedCounter.tsx"() {
5487
- "use client";
5488
- init_cn();
5489
- init_Typography();
5490
- AnimatedCounter = ({
5491
- value: rawValue,
5492
- duration = 600,
5493
- prefix,
5494
- suffix,
5495
- className
5496
- }) => {
5497
- const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
5498
- const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
5499
- const [displayValue, setDisplayValue] = React84.useState(value);
5500
- const previousValueRef = React84.useRef(value);
5501
- const animationFrameRef = React84.useRef(null);
5502
- React84.useEffect(() => {
5503
- const from = previousValueRef.current;
5504
- const to = value;
5505
- previousValueRef.current = value;
5506
- if (from === to) {
5507
- setDisplayValue(to);
5508
- return;
5509
- }
5510
- const startTime = performance.now();
5511
- const diff = to - from;
5512
- function animate(currentTime) {
5513
- const elapsed = currentTime - startTime;
5514
- const progress = Math.min(elapsed / duration, 1);
5515
- const easedProgress = easeOut(progress);
5516
- setDisplayValue(from + diff * easedProgress);
5517
- if (progress < 1) {
5518
- animationFrameRef.current = requestAnimationFrame(animate);
5519
- } else {
5520
- setDisplayValue(to);
5521
- }
5522
- }
5523
- animationFrameRef.current = requestAnimationFrame(animate);
5524
- return () => {
5525
- if (animationFrameRef.current !== null) {
5526
- cancelAnimationFrame(animationFrameRef.current);
5527
- }
5528
- };
5529
- }, [value, duration]);
5530
- const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5531
- const formattedValue = displayValue.toFixed(decimalPlaces);
5532
- return /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5533
- prefix,
5534
- formattedValue,
5535
- suffix
5536
- ] });
5537
- };
5538
- AnimatedCounter.displayName = "AnimatedCounter";
5539
- }
5540
- });
5541
5481
  function useInfiniteScroll(onLoadMore, options = {}) {
5542
5482
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
5543
5483
  const observerRef = React84.useRef(null);
@@ -8019,15 +7959,15 @@ function HeaderSkeleton({ className }) {
8019
7959
  ] })
8020
7960
  ] });
8021
7961
  }
8022
- function TableSkeleton({ rows = 5, columns = 4, className }) {
7962
+ function TableSkeleton({ rows: rows2 = 5, columns = 4, className }) {
8023
7963
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", className: cn("border border-border rounded-lg overflow-hidden", className), children: [
8024
7964
  /* @__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)) }),
8025
- Array.from({ length: rows }).map((_, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
7965
+ Array.from({ length: rows2 }).map((_, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
8026
7966
  HStack,
8027
7967
  {
8028
7968
  className: cn(
8029
7969
  "px-4 py-3",
8030
- rowIdx < rows - 1 && "border-b border-border"
7970
+ rowIdx < rows2 - 1 && "border-b border-border"
8031
7971
  ),
8032
7972
  children: Array.from({ length: columns }).map((_2, colIdx) => /* @__PURE__ */ jsxRuntime.jsx(SkeletonLine, { className: "flex-1 mx-2" }, colIdx))
8033
7973
  },
@@ -8075,18 +8015,18 @@ function CardSkeleton({ className }) {
8075
8015
  }
8076
8016
  );
8077
8017
  }
8078
- function TextSkeleton({ rows = 3, className }) {
8079
- return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
8018
+ function TextSkeleton({ rows: rows2 = 3, className }) {
8019
+ return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className, children: Array.from({ length: rows2 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
8080
8020
  SkeletonLine,
8081
8021
  {
8082
- className: i === rows - 1 ? "w-2/3" : "w-full"
8022
+ className: i === rows2 - 1 ? "w-2/3" : "w-full"
8083
8023
  },
8084
8024
  i
8085
8025
  )) });
8086
8026
  }
8087
8027
  function Skeleton({
8088
8028
  variant = "text",
8089
- rows,
8029
+ rows: rows2,
8090
8030
  columns,
8091
8031
  fields,
8092
8032
  className
@@ -8096,15 +8036,15 @@ function Skeleton({
8096
8036
  case "header":
8097
8037
  return /* @__PURE__ */ jsxRuntime.jsx(HeaderSkeleton, { className });
8098
8038
  case "table":
8099
- return /* @__PURE__ */ jsxRuntime.jsx(TableSkeleton, { rows, columns, className });
8039
+ return /* @__PURE__ */ jsxRuntime.jsx(TableSkeleton, { rows: rows2, columns, className });
8100
8040
  case "form":
8101
8041
  return /* @__PURE__ */ jsxRuntime.jsx(FormSkeleton, { fields, className });
8102
8042
  case "card":
8103
8043
  return /* @__PURE__ */ jsxRuntime.jsx(CardSkeleton, { className });
8104
8044
  case "text":
8105
- return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows, className });
8045
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows: rows2, className });
8106
8046
  default:
8107
- return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows, className });
8047
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSkeleton, { rows: rows2, className });
8108
8048
  }
8109
8049
  }
8110
8050
  var pulseClass;
@@ -9732,7 +9672,7 @@ var init_MapView = __esm({
9732
9672
  shadowSize: [41, 41]
9733
9673
  });
9734
9674
  L.Marker.prototype.options.icon = defaultIcon;
9735
- const { useEffect: useEffect71, useRef: useRef66, useCallback: useCallback113, useState: useState103 } = React84__namespace.default;
9675
+ const { useEffect: useEffect71, useRef: useRef66, useCallback: useCallback114, useState: useState103 } = React84__namespace.default;
9736
9676
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
9737
9677
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
9738
9678
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -9778,7 +9718,7 @@ var init_MapView = __esm({
9778
9718
  }) {
9779
9719
  const eventBus = useEventBus3();
9780
9720
  const [clickedPosition, setClickedPosition] = useState103(null);
9781
- const handleMapClick = useCallback113((lat, lng) => {
9721
+ const handleMapClick = useCallback114((lat, lng) => {
9782
9722
  if (showClickedPin) {
9783
9723
  setClickedPosition({ lat, lng });
9784
9724
  }
@@ -9787,7 +9727,7 @@ var init_MapView = __esm({
9787
9727
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
9788
9728
  }
9789
9729
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
9790
- const handleMarkerClick = useCallback113((marker) => {
9730
+ const handleMarkerClick = useCallback114((marker) => {
9791
9731
  onMarkerClick?.(marker);
9792
9732
  if (markerClickEvent) {
9793
9733
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -10008,7 +9948,7 @@ function InputPattern({
10008
9948
  function TextareaPattern({
10009
9949
  value = "",
10010
9950
  placeholder,
10011
- rows = 4,
9951
+ rows: rows2 = 4,
10012
9952
  disabled = false,
10013
9953
  fieldError,
10014
9954
  onChange,
@@ -10028,7 +9968,7 @@ function TextareaPattern({
10028
9968
  {
10029
9969
  value: localValue,
10030
9970
  placeholder,
10031
- rows,
9971
+ rows: rows2,
10032
9972
  disabled,
10033
9973
  error: fieldError,
10034
9974
  onChange: handleChange,
@@ -10513,6 +10453,91 @@ var init_ActionPalette = __esm({
10513
10453
  ActionPalette.displayName = "ActionPalette";
10514
10454
  }
10515
10455
  });
10456
+ function parseValue(value) {
10457
+ if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
10458
+ const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
10459
+ if (!match) {
10460
+ return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
10461
+ }
10462
+ const numStr = match[2];
10463
+ const decimalIdx = numStr.indexOf(".");
10464
+ const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
10465
+ return {
10466
+ prefix: match[1],
10467
+ num: parseFloat(numStr),
10468
+ suffix: match[3],
10469
+ decimals
10470
+ };
10471
+ }
10472
+ var AnimatedCounter;
10473
+ var init_AnimatedCounter = __esm({
10474
+ "components/core/molecules/AnimatedCounter.tsx"() {
10475
+ "use client";
10476
+ init_cn();
10477
+ init_Box();
10478
+ init_Typography();
10479
+ AnimatedCounter = ({
10480
+ value,
10481
+ label,
10482
+ duration = 1500,
10483
+ className
10484
+ }) => {
10485
+ const ref = React84.useRef(null);
10486
+ const [displayValue, setDisplayValue] = React84.useState("0");
10487
+ const [hasAnimated, setHasAnimated] = React84.useState(false);
10488
+ const animate = React84.useCallback(() => {
10489
+ const { num: num2, prefix, suffix, decimals } = parseValue(value);
10490
+ if (num2 === 0) {
10491
+ setDisplayValue(String(value));
10492
+ return;
10493
+ }
10494
+ const startTime = performance.now();
10495
+ const tick = (now2) => {
10496
+ const elapsed = now2 - startTime;
10497
+ const progress = Math.min(elapsed / duration, 1);
10498
+ const eased = 1 - Math.pow(1 - progress, 3);
10499
+ const current = eased * num2;
10500
+ setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
10501
+ if (progress < 1) {
10502
+ requestAnimationFrame(tick);
10503
+ } else {
10504
+ setDisplayValue(String(value));
10505
+ }
10506
+ };
10507
+ requestAnimationFrame(tick);
10508
+ }, [value, duration]);
10509
+ React84.useEffect(() => {
10510
+ if (hasAnimated) return;
10511
+ const el = ref.current;
10512
+ if (!el) return;
10513
+ const observer2 = new IntersectionObserver(
10514
+ (entries) => {
10515
+ if (entries[0].isIntersecting) {
10516
+ setHasAnimated(true);
10517
+ animate();
10518
+ observer2.disconnect();
10519
+ }
10520
+ },
10521
+ { threshold: 0.3 }
10522
+ );
10523
+ observer2.observe(el);
10524
+ return () => observer2.disconnect();
10525
+ }, [hasAnimated, animate]);
10526
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
10527
+ /* @__PURE__ */ jsxRuntime.jsx(
10528
+ Typography,
10529
+ {
10530
+ variant: "h2",
10531
+ className: "text-primary font-bold tabular-nums",
10532
+ children: hasAnimated ? displayValue : "0"
10533
+ }
10534
+ ),
10535
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
10536
+ ] });
10537
+ };
10538
+ AnimatedCounter.displayName = "AnimatedCounter";
10539
+ }
10540
+ });
10516
10541
  var AuthLayout;
10517
10542
  var init_AuthLayout = __esm({
10518
10543
  "components/core/templates/AuthLayout.tsx"() {
@@ -11866,6 +11891,39 @@ var init_IsometricCanvas2 = __esm({
11866
11891
  init_IsometricCanvas();
11867
11892
  }
11868
11893
  });
11894
+
11895
+ // components/game/organisms/boardEntity.ts
11896
+ function boardEntity(entity) {
11897
+ if (!entity) return void 0;
11898
+ return Array.isArray(entity) ? entity[0] : entity;
11899
+ }
11900
+ function str(v) {
11901
+ return v == null ? "" : String(v);
11902
+ }
11903
+ function num(v, fallback = 0) {
11904
+ const n = Number(v);
11905
+ return Number.isFinite(n) ? n : fallback;
11906
+ }
11907
+ function rows(v) {
11908
+ return Array.isArray(v) ? v : [];
11909
+ }
11910
+ function vec2(v) {
11911
+ const o = v ?? {};
11912
+ return { x: num(o.x), y: num(o.y) };
11913
+ }
11914
+ function unitPosition(u) {
11915
+ return vec2(u.position);
11916
+ }
11917
+ function unitTeam(u) {
11918
+ return str(u.team);
11919
+ }
11920
+ function unitHealth(u) {
11921
+ return num(u.health);
11922
+ }
11923
+ var init_boardEntity = __esm({
11924
+ "components/game/organisms/boardEntity.ts"() {
11925
+ }
11926
+ });
11869
11927
  function BattleBoard({
11870
11928
  entity,
11871
11929
  scale = 0.45,
@@ -11892,43 +11950,49 @@ function BattleBoard({
11892
11950
  attackEvent,
11893
11951
  className
11894
11952
  }) {
11895
- const tiles = entity.tiles;
11896
- const features = entity.features ?? [];
11897
- const boardWidth = entity.boardWidth ?? 8;
11898
- const boardHeight = entity.boardHeight ?? 6;
11899
- const assetManifest = entity.assetManifest;
11900
- const backgroundImage = entity.backgroundImage;
11901
- const units = entity.units;
11902
- const selectedUnitId = entity.selectedUnitId;
11903
- const currentPhase = entity.phase;
11904
- const currentTurn = entity.turn;
11905
- const gameResult = entity.gameResult;
11953
+ const board = boardEntity(entity) ?? {};
11954
+ const tiles = Array.isArray(board.tiles) ? board.tiles : [];
11955
+ const features = Array.isArray(board.features) ? board.features : [];
11956
+ const boardWidth = num(board.boardWidth, 8);
11957
+ const boardHeight = num(board.boardHeight, 6);
11958
+ const assetManifest = board.assetManifest;
11959
+ const backgroundImage = board.backgroundImage;
11960
+ const units = rows(board.units);
11961
+ const selectedUnitId = board.selectedUnitId ?? null;
11962
+ const currentPhase = str(board.phase) || "observation";
11963
+ const currentTurn = num(board.turn, 1);
11964
+ const gameResult = board.gameResult ?? null;
11906
11965
  const eventBus = useEventBus();
11907
11966
  const { t } = hooks.useTranslate();
11908
11967
  const [hoveredTile, setHoveredTile] = React84.useState(null);
11909
11968
  const [isShaking, setIsShaking] = React84.useState(false);
11910
11969
  const selectedUnit = React84.useMemo(
11911
- () => units.find((u) => u.id === selectedUnitId) ?? null,
11970
+ () => units.find((u) => str(u.id) === selectedUnitId) ?? null,
11912
11971
  [units, selectedUnitId]
11913
11972
  );
11914
11973
  const hoveredUnit = React84.useMemo(() => {
11915
11974
  if (!hoveredTile) return null;
11916
- return units.find(
11917
- (u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
11918
- ) ?? null;
11975
+ return units.find((u) => {
11976
+ const p2 = unitPosition(u);
11977
+ return p2.x === hoveredTile.x && p2.y === hoveredTile.y && unitHealth(u) > 0;
11978
+ }) ?? null;
11919
11979
  }, [hoveredTile, units]);
11920
- const playerUnits = React84.useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
11921
- const enemyUnits = React84.useMemo(() => units.filter((u) => u.team === "enemy" && u.health > 0), [units]);
11980
+ const playerUnits = React84.useMemo(() => units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0), [units]);
11981
+ const enemyUnits = React84.useMemo(() => units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0), [units]);
11922
11982
  const validMoves = React84.useMemo(() => {
11923
11983
  if (!selectedUnit || currentPhase !== "movement") return [];
11924
11984
  const moves = [];
11925
- const range = selectedUnit.movement;
11985
+ const range = num(selectedUnit.movement);
11986
+ const origin = unitPosition(selectedUnit);
11926
11987
  for (let dy = -range; dy <= range; dy++) {
11927
11988
  for (let dx = -range; dx <= range; dx++) {
11928
- const nx = selectedUnit.position.x + dx;
11929
- const ny = selectedUnit.position.y + dy;
11989
+ const nx = origin.x + dx;
11990
+ const ny = origin.y + dy;
11930
11991
  const dist = Math.abs(dx) + Math.abs(dy);
11931
- 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)) {
11992
+ if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => {
11993
+ const p2 = unitPosition(u);
11994
+ return p2.x === nx && p2.y === ny && unitHealth(u) > 0;
11995
+ })) {
11932
11996
  moves.push({ x: nx, y: ny });
11933
11997
  }
11934
11998
  }
@@ -11937,11 +12001,14 @@ function BattleBoard({
11937
12001
  }, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
11938
12002
  const attackTargets = React84.useMemo(() => {
11939
12003
  if (!selectedUnit || currentPhase !== "action") return [];
11940
- return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
11941
- const dx = Math.abs(u.position.x - selectedUnit.position.x);
11942
- const dy = Math.abs(u.position.y - selectedUnit.position.y);
12004
+ const sp = unitPosition(selectedUnit);
12005
+ const sTeam = unitTeam(selectedUnit);
12006
+ return units.filter((u) => unitTeam(u) !== sTeam && unitHealth(u) > 0).filter((u) => {
12007
+ const p2 = unitPosition(u);
12008
+ const dx = Math.abs(p2.x - sp.x);
12009
+ const dy = Math.abs(p2.y - sp.y);
11943
12010
  return dx <= 1 && dy <= 1 && dx + dy > 0;
11944
- }).map((u) => u.position);
12011
+ }).map((u) => unitPosition(u));
11945
12012
  }, [selectedUnit, currentPhase, units]);
11946
12013
  const MOVE_SPEED_MS_PER_TILE = 300;
11947
12014
  const movementAnimRef = React84.useRef(null);
@@ -11981,23 +12048,25 @@ function BattleBoard({
11981
12048
  return () => clearInterval(interval);
11982
12049
  }, []);
11983
12050
  const isoUnits = React84.useMemo(() => {
11984
- return units.filter((u) => u.health > 0).map((unit) => {
11985
- const pos = movingPositions.get(unit.id) ?? unit.position;
12051
+ return units.filter((u) => unitHealth(u) > 0).map((unit) => {
12052
+ const id = str(unit.id);
12053
+ const pos = movingPositions.get(id) ?? unitPosition(unit);
12054
+ const unitTraits = Array.isArray(unit.traits) ? unit.traits : void 0;
11986
12055
  return {
11987
- id: unit.id,
12056
+ id,
11988
12057
  position: pos,
11989
- name: unit.name,
11990
- team: unit.team,
11991
- health: unit.health,
11992
- maxHealth: unit.maxHealth,
11993
- unitType: unit.unitType,
11994
- heroId: unit.heroId,
11995
- sprite: unit.sprite,
11996
- traits: unit.traits?.map((t2) => ({
11997
- name: t2.name,
11998
- currentState: t2.currentState,
11999
- states: t2.states,
12000
- cooldown: t2.cooldown ?? 0
12058
+ name: str(unit.name),
12059
+ team: unitTeam(unit),
12060
+ health: unitHealth(unit),
12061
+ maxHealth: num(unit.maxHealth),
12062
+ unitType: unit.unitType == null ? void 0 : str(unit.unitType),
12063
+ heroId: unit.heroId == null ? void 0 : str(unit.heroId),
12064
+ sprite: unit.sprite == null ? void 0 : str(unit.sprite),
12065
+ traits: unitTraits?.map((tr) => ({
12066
+ name: tr.name,
12067
+ currentState: tr.currentState,
12068
+ states: tr.states,
12069
+ cooldown: tr.cooldown ?? 0
12001
12070
  }))
12002
12071
  };
12003
12072
  });
@@ -12009,8 +12078,8 @@ function BattleBoard({
12009
12078
  [scale, baseOffsetX]
12010
12079
  );
12011
12080
  const checkGameEnd = React84.useCallback(() => {
12012
- const pa = units.filter((u) => u.team === "player" && u.health > 0);
12013
- const ea = units.filter((u) => u.team === "enemy" && u.health > 0);
12081
+ const pa = units.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
12082
+ const ea = units.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
12014
12083
  if (pa.length === 0) {
12015
12084
  onGameEnd?.("defeat");
12016
12085
  if (gameEndEvent) {
@@ -12024,21 +12093,22 @@ function BattleBoard({
12024
12093
  }
12025
12094
  }, [units, onGameEnd, gameEndEvent, eventBus]);
12026
12095
  const handleUnitClick = React84.useCallback((unitId) => {
12027
- const unit = units.find((u) => u.id === unitId);
12096
+ const unit = units.find((u) => str(u.id) === unitId);
12028
12097
  if (!unit) return;
12029
12098
  if (unitClickEvent) {
12030
12099
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
12031
12100
  }
12032
12101
  if (currentPhase === "action" && selectedUnit) {
12033
- if (unit.team === "enemy" && attackTargets.some((t2) => t2.x === unit.position.x && t2.y === unit.position.y)) {
12034
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
12102
+ const up = unitPosition(unit);
12103
+ if (unitTeam(unit) === "enemy" && attackTargets.some((t2) => t2.x === up.x && t2.y === up.y)) {
12104
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
12035
12105
  setIsShaking(true);
12036
12106
  setTimeout(() => setIsShaking(false), 300);
12037
12107
  onAttack?.(selectedUnit, unit, damage);
12038
12108
  if (attackEvent) {
12039
12109
  eventBus.emit(`UI:${attackEvent}`, {
12040
- attackerId: selectedUnit.id,
12041
- targetId: unit.id,
12110
+ attackerId: str(selectedUnit.id),
12111
+ targetId: str(unit.id),
12042
12112
  damage
12043
12113
  });
12044
12114
  }
@@ -12053,9 +12123,9 @@ function BattleBoard({
12053
12123
  if (currentPhase === "movement" && selectedUnit) {
12054
12124
  if (movementAnimRef.current) return;
12055
12125
  if (validMoves.some((m) => m.x === x && m.y === y)) {
12056
- const from = { ...selectedUnit.position };
12126
+ const from = { ...unitPosition(selectedUnit) };
12057
12127
  const to = { x, y };
12058
- startMoveAnimation(selectedUnit.id, from, to, () => {
12128
+ startMoveAnimation(str(selectedUnit.id), from, to, () => {
12059
12129
  onUnitMove?.(selectedUnit, to);
12060
12130
  });
12061
12131
  }
@@ -12213,6 +12283,7 @@ var init_BattleBoard = __esm({
12213
12283
  init_Typography();
12214
12284
  init_Stack();
12215
12285
  init_IsometricCanvas2();
12286
+ init_boardEntity();
12216
12287
  init_isometric();
12217
12288
  BattleBoard.displayName = "BattleBoard";
12218
12289
  }
@@ -13435,24 +13506,24 @@ var init_CodeBlock = __esm({
13435
13506
  return;
13436
13507
  }
13437
13508
  lineEls.forEach((el) => {
13438
- const num = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13439
- if (hiddenLines.has(num)) {
13509
+ const num2 = parseInt(el.getAttribute("data-line") ?? "-1", 10);
13510
+ if (hiddenLines.has(num2)) {
13440
13511
  el.style.display = "none";
13441
13512
  return;
13442
13513
  }
13443
13514
  el.style.display = "";
13444
13515
  el.style.position = "relative";
13445
13516
  el.style.paddingLeft = "1.2em";
13446
- const region = foldStartMap.get(num);
13517
+ const region = foldStartMap.get(num2);
13447
13518
  if (!region) return;
13448
- const isCollapsed = collapsed.has(num);
13519
+ const isCollapsed = collapsed.has(num2);
13449
13520
  const toggle = document.createElement("span");
13450
13521
  toggle.className = "fold-toggle";
13451
13522
  toggle.textContent = isCollapsed ? "\u25B6" : "\u25BC";
13452
13523
  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%";
13453
13524
  toggle.addEventListener("click", (e) => {
13454
13525
  e.stopPropagation();
13455
- toggleFoldRef.current(num);
13526
+ toggleFoldRef.current(num2);
13456
13527
  });
13457
13528
  el.insertBefore(toggle, el.firstChild);
13458
13529
  if (isCollapsed) {
@@ -15705,10 +15776,13 @@ var init_BookChapterView = __esm({
15705
15776
  init_cn();
15706
15777
  BookChapterView = ({
15707
15778
  chapter,
15779
+ orbitalSchema,
15708
15780
  direction,
15709
15781
  className
15710
15782
  }) => {
15711
15783
  const { t: _t } = hooks.useTranslate();
15784
+ const title = String(chapter.title ?? "");
15785
+ const content = String(chapter.content ?? "");
15712
15786
  return /* @__PURE__ */ jsxRuntime.jsxs(
15713
15787
  VStack,
15714
15788
  {
@@ -15716,16 +15790,16 @@ var init_BookChapterView = __esm({
15716
15790
  className: cn("px-6 py-8 max-w-4xl mx-auto w-full", className),
15717
15791
  style: { direction },
15718
15792
  children: [
15719
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: chapter.title }),
15793
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold", children: title }),
15720
15794
  /* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
15721
- !!chapter.orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
15795
+ !!orbitalSchema && /* @__PURE__ */ jsxRuntime.jsx(ScaledDiagram, { children: /* @__PURE__ */ jsxRuntime.jsx(
15722
15796
  JazariStateMachine,
15723
15797
  {
15724
- schema: chapter.orbitalSchema,
15798
+ schema: orbitalSchema,
15725
15799
  direction
15726
15800
  }
15727
15801
  ) }),
15728
- /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content: chapter.content, direction })
15802
+ /* @__PURE__ */ jsxRuntime.jsx(ContentRenderer, { content, direction })
15729
15803
  ]
15730
15804
  }
15731
15805
  );
@@ -15823,7 +15897,7 @@ var init_BookNavBar = __esm({
15823
15897
  BookNavBar = ({
15824
15898
  currentPage,
15825
15899
  totalPages,
15826
- chapterTitle,
15900
+ chapterTitle: chapterTitle2,
15827
15901
  direction,
15828
15902
  className
15829
15903
  }) => {
@@ -15864,12 +15938,12 @@ var init_BookNavBar = __esm({
15864
15938
  )
15865
15939
  ] }),
15866
15940
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex-1 mx-4 max-w-md", children: [
15867
- chapterTitle && /* @__PURE__ */ jsxRuntime.jsx(
15941
+ chapterTitle2 && /* @__PURE__ */ jsxRuntime.jsx(
15868
15942
  Typography,
15869
15943
  {
15870
15944
  variant: "caption",
15871
15945
  className: "text-center block truncate text-muted-foreground",
15872
- children: chapterTitle
15946
+ children: chapterTitle2
15873
15947
  }
15874
15948
  ),
15875
15949
  /* @__PURE__ */ jsxRuntime.jsx(ProgressBar, { value: progress, size: "sm", variant: "primary" })
@@ -15936,31 +16010,35 @@ var init_BookTableOfContents = __esm({
15936
16010
  style: { direction },
15937
16011
  children: [
15938
16012
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h1", className: "text-3xl font-bold text-center mb-4", children: t("book.tableOfContents") }),
15939
- parts.map((part, partIdx) => /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
15940
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", children: [
15941
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
15942
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", className: "font-semibold", children: part.title })
15943
- ] }),
15944
- /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: part.chapters.map((chapter) => {
15945
- const isCurrent = chapter.id === currentChapterId;
15946
- return /* @__PURE__ */ jsxRuntime.jsx(
15947
- Button,
15948
- {
15949
- variant: "ghost",
15950
- size: "sm",
15951
- action: "BOOK_NAVIGATE",
15952
- actionPayload: { chapterId: chapter.id },
15953
- className: cn(
15954
- "justify-start text-left w-full",
15955
- direction === "rtl" && "text-right",
15956
- isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
15957
- ),
15958
- children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: chapter.title }) })
15959
- },
15960
- chapter.id
15961
- );
15962
- }) })
15963
- ] }, partIdx))
16013
+ parts.map((part, partIdx) => {
16014
+ const chapters = Array.isArray(part.chapters) ? part.chapters : [];
16015
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
16016
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", children: [
16017
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", children: t("book.partNumber", { number: String(partIdx + 1) }) }),
16018
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", className: "font-semibold", children: String(part.title ?? "") })
16019
+ ] }),
16020
+ /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "xs", className: direction === "rtl" ? "pr-6" : "pl-6", children: chapters.map((chapter) => {
16021
+ const id = chapter.id == null ? "" : String(chapter.id);
16022
+ const isCurrent = id === currentChapterId;
16023
+ return /* @__PURE__ */ jsxRuntime.jsx(
16024
+ Button,
16025
+ {
16026
+ variant: "ghost",
16027
+ size: "sm",
16028
+ action: "BOOK_NAVIGATE",
16029
+ actionPayload: { chapterId: id },
16030
+ className: cn(
16031
+ "justify-start text-left w-full",
16032
+ direction === "rtl" && "text-right",
16033
+ isCurrent && "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400"
16034
+ ),
16035
+ children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "truncate", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: String(chapter.title ?? "") }) })
16036
+ },
16037
+ id
16038
+ );
16039
+ }) })
16040
+ ] }, partIdx);
16041
+ })
15964
16042
  ]
15965
16043
  }
15966
16044
  );
@@ -16082,27 +16160,41 @@ function resolveFieldMap(fieldMap) {
16082
16160
  function get(obj, key) {
16083
16161
  return obj[key];
16084
16162
  }
16163
+ function asStr(v) {
16164
+ return v == null ? "" : String(v);
16165
+ }
16085
16166
  function mapBookData(raw, fields = IDENTITY_BOOK_FIELDS) {
16086
16167
  const rawParts = get(raw, fields.parts) ?? [];
16087
- return {
16088
- title: get(raw, fields.title) ?? "",
16089
- subtitle: get(raw, fields.subtitle),
16090
- author: get(raw, fields.author),
16091
- coverImageUrl: get(raw, fields.coverImageUrl),
16092
- direction: get(raw, fields.direction) ?? void 0,
16093
- parts: rawParts.map((part) => {
16094
- const rawChapters = get(part, fields.chapters) ?? [];
16095
- return {
16096
- title: get(part, fields.partTitle) ?? "",
16097
- chapters: rawChapters.map((ch) => ({
16098
- id: get(ch, fields.chapterId) ?? "",
16099
- title: get(ch, fields.chapterTitle) ?? "",
16100
- content: get(ch, fields.chapterContent) ?? "",
16101
- orbitalSchema: get(ch, fields.chapterOrbitalSchema)
16102
- }))
16103
- };
16104
- })
16168
+ const direction = get(raw, fields.direction) ?? "ltr";
16169
+ const cover = {
16170
+ title: asStr(get(raw, fields.title)),
16171
+ subtitle: asStr(get(raw, fields.subtitle)),
16172
+ author: asStr(get(raw, fields.author)),
16173
+ coverImageUrl: asStr(get(raw, fields.coverImageUrl)),
16174
+ direction
16105
16175
  };
16176
+ const schemaByChapterId = {};
16177
+ const chapters = [];
16178
+ const parts = rawParts.map((part) => {
16179
+ const rawChapters = get(part, fields.chapters) ?? [];
16180
+ const chapterRows = rawChapters.map((ch) => {
16181
+ const id = asStr(get(ch, fields.chapterId));
16182
+ const schema = get(ch, fields.chapterOrbitalSchema);
16183
+ if (schema) schemaByChapterId[id] = schema;
16184
+ const row = {
16185
+ id,
16186
+ title: asStr(get(ch, fields.chapterTitle)),
16187
+ content: asStr(get(ch, fields.chapterContent))
16188
+ };
16189
+ chapters.push(row);
16190
+ return row;
16191
+ });
16192
+ return {
16193
+ title: asStr(get(part, fields.partTitle)),
16194
+ chapters: chapterRows
16195
+ };
16196
+ });
16197
+ return { cover, direction, parts, chapters, schemaByChapterId };
16106
16198
  }
16107
16199
  var IDENTITY_BOOK_FIELDS, AR_BOOK_FIELDS, FIELD_MAP_REGISTRY;
16108
16200
  var init_types2 = __esm({
@@ -16140,10 +16232,7 @@ var init_types2 = __esm({
16140
16232
  };
16141
16233
  }
16142
16234
  });
16143
- function flattenChapters(book) {
16144
- return book.parts.flatMap((part) => part.chapters);
16145
- }
16146
- var PRINT_STYLES, BookViewer;
16235
+ var chapterId, chapterTitle, PRINT_STYLES, BookViewer;
16147
16236
  var init_BookViewer = __esm({
16148
16237
  "components/marketing/organisms/book/BookViewer.tsx"() {
16149
16238
  init_Box();
@@ -16156,6 +16245,8 @@ var init_BookViewer = __esm({
16156
16245
  init_BookNavBar();
16157
16246
  init_EmptyState();
16158
16247
  init_types2();
16248
+ chapterId = (ch) => ch?.id == null ? void 0 : String(ch.id);
16249
+ chapterTitle = (ch) => ch?.title == null ? void 0 : String(ch.title);
16159
16250
  PRINT_STYLES = `
16160
16251
  @media print {
16161
16252
  .book-viewer-page {
@@ -16184,14 +16275,14 @@ var init_BookViewer = __esm({
16184
16275
  return mapBookData(raw, resolvedFieldMap);
16185
16276
  }, [entity, resolvedFieldMap]);
16186
16277
  const direction = book?.direction ?? "ltr";
16187
- const chapters = React84.useMemo(() => book ? flattenChapters(book) : [], [book]);
16278
+ const chapters = React84.useMemo(() => book ? book.chapters : [], [book]);
16188
16279
  const totalPages = 2 + chapters.length;
16189
16280
  const navigateTo = React84.useCallback(
16190
16281
  (page) => {
16191
16282
  const clamped = Math.max(0, Math.min(page, totalPages - 1));
16192
16283
  setCurrentPage(clamped);
16193
- const chapterId = clamped >= 2 ? chapters[clamped - 2]?.id : void 0;
16194
- eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId });
16284
+ const id = clamped >= 2 ? chapterId(chapters[clamped - 2]) : void 0;
16285
+ eventBus.emit("UI:BOOK_PAGE_CHANGE", { pageIndex: clamped, chapterId: id });
16195
16286
  },
16196
16287
  [totalPages, chapters, eventBus]
16197
16288
  );
@@ -16203,8 +16294,8 @@ var init_BookViewer = __esm({
16203
16294
  eventBus.on("UI:BOOK_PAGE_NEXT", () => navigateTo(currentPage + 1)),
16204
16295
  eventBus.on("UI:BOOK_PRINT", () => window.print()),
16205
16296
  eventBus.on("UI:BOOK_NAVIGATE", (event) => {
16206
- const chapterId = event.payload?.chapterId;
16207
- const idx = chapters.findIndex((ch) => ch.id === chapterId);
16297
+ const targetId = event.payload?.chapterId;
16298
+ const idx = chapters.findIndex((ch) => chapterId(ch) === targetId);
16208
16299
  if (idx >= 0) navigateTo(idx + 2);
16209
16300
  })
16210
16301
  ];
@@ -16221,9 +16312,11 @@ var init_BookViewer = __esm({
16221
16312
  style.remove();
16222
16313
  };
16223
16314
  }, []);
16224
- const currentChapterId = currentPage >= 2 ? chapters[currentPage - 2]?.id : void 0;
16225
- const currentChapterTitle = currentPage >= 2 ? chapters[currentPage - 2]?.title : void 0;
16315
+ const currentChapterId = currentPage >= 2 ? chapterId(chapters[currentPage - 2]) : void 0;
16316
+ const currentChapterTitle = currentPage >= 2 ? chapterTitle(chapters[currentPage - 2]) : void 0;
16226
16317
  if (!book) return /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { message: t("book.noData") });
16318
+ const cover = book.cover;
16319
+ const coverTitle = String(cover.title ?? "");
16227
16320
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("relative h-full overflow-hidden bg-background", className), children: [
16228
16321
  /* @__PURE__ */ jsxRuntime.jsxs(
16229
16322
  Box,
@@ -16235,10 +16328,10 @@ var init_BookViewer = __esm({
16235
16328
  /* @__PURE__ */ jsxRuntime.jsx(
16236
16329
  BookCoverPage,
16237
16330
  {
16238
- title: book.title,
16239
- subtitle: book.subtitle,
16240
- author: book.author,
16241
- coverImageUrl: book.coverImageUrl,
16331
+ title: coverTitle,
16332
+ subtitle: String(cover.subtitle ?? "") || void 0,
16333
+ author: String(cover.author ?? "") || void 0,
16334
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16242
16335
  direction
16243
16336
  }
16244
16337
  ),
@@ -16249,23 +16342,27 @@ var init_BookViewer = __esm({
16249
16342
  direction
16250
16343
  }
16251
16344
  ),
16252
- chapters.map((chapter) => /* @__PURE__ */ jsxRuntime.jsx(
16253
- BookChapterView,
16254
- {
16255
- chapter,
16256
- direction
16257
- },
16258
- chapter.id
16259
- ))
16345
+ chapters.map((chapter) => {
16346
+ const id = chapterId(chapter);
16347
+ return /* @__PURE__ */ jsxRuntime.jsx(
16348
+ BookChapterView,
16349
+ {
16350
+ chapter,
16351
+ orbitalSchema: id ? book.schemaByChapterId[id] : void 0,
16352
+ direction
16353
+ },
16354
+ id
16355
+ );
16356
+ })
16260
16357
  ] }),
16261
16358
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "print:hidden", children: [
16262
16359
  currentPage === 0 && /* @__PURE__ */ jsxRuntime.jsx(
16263
16360
  BookCoverPage,
16264
16361
  {
16265
- title: book.title,
16266
- subtitle: book.subtitle,
16267
- author: book.author,
16268
- coverImageUrl: book.coverImageUrl,
16362
+ title: coverTitle,
16363
+ subtitle: String(cover.subtitle ?? "") || void 0,
16364
+ author: String(cover.author ?? "") || void 0,
16365
+ coverImageUrl: String(cover.coverImageUrl ?? "") || void 0,
16269
16366
  direction
16270
16367
  }
16271
16368
  ),
@@ -16281,6 +16378,7 @@ var init_BookViewer = __esm({
16281
16378
  BookChapterView,
16282
16379
  {
16283
16380
  chapter: chapters[currentPage - 2],
16381
+ orbitalSchema: currentChapterId ? book.schemaByChapterId[currentChapterId] : void 0,
16284
16382
  direction
16285
16383
  }
16286
16384
  )
@@ -16293,7 +16391,7 @@ var init_BookViewer = __esm({
16293
16391
  {
16294
16392
  currentPage,
16295
16393
  totalPages,
16296
- chapterTitle: currentPage === 0 ? book.title : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16394
+ chapterTitle: currentPage === 0 ? coverTitle : currentPage === 1 ? t("book.tableOfContents") : currentChapterTitle,
16297
16395
  direction
16298
16396
  }
16299
16397
  )
@@ -16391,7 +16489,7 @@ var init_Grid = __esm({
16391
16489
  };
16392
16490
  Grid = ({
16393
16491
  cols = 1,
16394
- rows,
16492
+ rows: rows2,
16395
16493
  gap = "md",
16396
16494
  rowGap,
16397
16495
  colGap,
@@ -16403,7 +16501,7 @@ var init_Grid = __esm({
16403
16501
  children,
16404
16502
  as: Component = "div"
16405
16503
  }) => {
16406
- const mergedStyle = rows ? { gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`, ...style } : style;
16504
+ const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
16407
16505
  return React84__namespace.default.createElement(
16408
16506
  Component,
16409
16507
  {
@@ -17119,14 +17217,14 @@ function BuilderBoard({
17119
17217
  }) {
17120
17218
  const { emit } = useEventBus();
17121
17219
  const { t } = hooks.useTranslate();
17122
- const resolved = Array.isArray(entity) ? entity[0] : entity;
17220
+ const resolved = boardEntity(entity);
17123
17221
  const [placements, setPlacements] = React84.useState({});
17124
17222
  const [headerError, setHeaderError] = React84.useState(false);
17125
17223
  const [submitted, setSubmitted] = React84.useState(false);
17126
17224
  const [attempts, setAttempts] = React84.useState(0);
17127
17225
  const [showHint, setShowHint] = React84.useState(false);
17128
- const components = resolved?.components ?? [];
17129
- const slots = resolved?.slots ?? [];
17226
+ const components = Array.isArray(resolved?.components) ? resolved.components : [];
17227
+ const slots = Array.isArray(resolved?.slots) ? resolved.slots : [];
17130
17228
  const usedComponentIds = new Set(Object.values(placements));
17131
17229
  const availableComponents = components.filter((c) => !usedComponentIds.has(c.id));
17132
17230
  const [selectedComponent, setSelectedComponent] = React84.useState(null);
@@ -17160,7 +17258,7 @@ function BuilderBoard({
17160
17258
  }, [slots, placements, attempts, completeEvent, emit]);
17161
17259
  const handleReset = () => {
17162
17260
  setSubmitted(false);
17163
- if (attempts >= 2 && resolved?.hint) {
17261
+ if (attempts >= 2 && str(resolved?.hint)) {
17164
17262
  setShowHint(true);
17165
17263
  }
17166
17264
  };
@@ -17173,20 +17271,24 @@ function BuilderBoard({
17173
17271
  };
17174
17272
  const getComponentById = (id) => components.find((c) => c.id === id);
17175
17273
  if (!resolved) return null;
17274
+ const theme = resolved.theme ?? void 0;
17275
+ const themeBackground = theme?.background;
17276
+ const headerImage = str(resolved.headerImage);
17277
+ const hint = str(resolved.hint);
17176
17278
  return /* @__PURE__ */ jsxRuntime.jsx(
17177
17279
  Box,
17178
17280
  {
17179
17281
  className,
17180
17282
  style: {
17181
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
17283
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
17182
17284
  backgroundSize: "cover",
17183
17285
  backgroundPosition: "center"
17184
17286
  },
17185
17287
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
17186
- 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,
17288
+ 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,
17187
17289
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17188
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
17189
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
17290
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
17291
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
17190
17292
  ] }) }),
17191
17293
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17192
17294
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("builder.components") }),
@@ -17246,9 +17348,9 @@ function BuilderBoard({
17246
17348
  ] }) }),
17247
17349
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
17248
17350
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
17249
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("builder.success") : resolved.failMessage ?? t("builder.incorrect") })
17351
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("builder.success") : str(resolved.failMessage) || t("builder.incorrect") })
17250
17352
  ] }) }),
17251
- 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 }) }),
17353
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
17252
17354
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
17253
17355
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
17254
17356
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Wrench, size: "sm" }),
@@ -17267,6 +17369,7 @@ var init_BuilderBoard = __esm({
17267
17369
  "components/game/organisms/puzzles/builder/BuilderBoard.tsx"() {
17268
17370
  init_atoms2();
17269
17371
  init_useEventBus();
17372
+ init_boardEntity();
17270
17373
  BuilderBoard.displayName = "BuilderBoard";
17271
17374
  }
17272
17375
  });
@@ -17604,21 +17707,24 @@ function CalendarGrid({
17604
17707
  eventBus.emit(`UI:${longPressEvent}`, { date: day.toISOString(), time, ...longPressPayload });
17605
17708
  }, 500);
17606
17709
  }, [longPressEvent, longPressPayload, eventBus]);
17607
- const renderEvent = (event) => /* @__PURE__ */ jsxRuntime.jsx(
17608
- Box,
17609
- {
17610
- rounded: "md",
17611
- padding: "xs",
17612
- border: true,
17613
- className: cn(
17614
- "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
17615
- event.color ? event.color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
17616
- ),
17617
- onClick: (e) => handleEventClick(event, e),
17618
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
17619
- },
17620
- event.id
17621
- );
17710
+ const renderEvent = (event) => {
17711
+ const color = event.color;
17712
+ return /* @__PURE__ */ jsxRuntime.jsx(
17713
+ Box,
17714
+ {
17715
+ rounded: "md",
17716
+ padding: "xs",
17717
+ border: true,
17718
+ className: cn(
17719
+ "cursor-pointer hover:shadow-sm transition-shadow text-xs truncate",
17720
+ color ? color : "bg-blue-500/15 border-blue-500/30 text-blue-600"
17721
+ ),
17722
+ onClick: (e) => handleEventClick(event, e),
17723
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "truncate font-medium", children: event.title })
17724
+ },
17725
+ event.id
17726
+ );
17727
+ };
17622
17728
  return /* @__PURE__ */ jsxRuntime.jsxs(
17623
17729
  Box,
17624
17730
  {
@@ -19282,7 +19388,6 @@ var init_CardGrid = __esm({
19282
19388
  alignItems = "stretch",
19283
19389
  className,
19284
19390
  children,
19285
- // EntityDisplayProps
19286
19391
  entity,
19287
19392
  isLoading = false,
19288
19393
  error = null,
@@ -19754,14 +19859,14 @@ var init_CaseStudyOrganism = __esm({
19754
19859
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((study) => /* @__PURE__ */ jsxRuntime.jsx(
19755
19860
  CaseStudyCard,
19756
19861
  {
19757
- title: study.title,
19758
- description: study.description,
19759
- category: study.category,
19760
- categoryColor: study.categoryColor,
19761
- href: study.href,
19762
- linkLabel: study.linkLabel
19862
+ title: String(study.title ?? ""),
19863
+ description: String(study.description ?? ""),
19864
+ category: String(study.category ?? ""),
19865
+ categoryColor: study.categoryColor != null ? String(study.categoryColor) : void 0,
19866
+ href: String(study.href ?? ""),
19867
+ linkLabel: study.linkLabel != null ? String(study.linkLabel) : void 0
19763
19868
  },
19764
- study.id
19869
+ String(study.id ?? "")
19765
19870
  )) })
19766
19871
  ] });
19767
19872
  };
@@ -19784,10 +19889,10 @@ function CastleBoard({
19784
19889
  className
19785
19890
  }) {
19786
19891
  const eventBus = useEventBus();
19787
- const resolved = Array.isArray(entity) ? entity[0] : entity;
19788
- const tiles = resolved?.tiles ?? [];
19789
- const features = resolved?.features ?? [];
19790
- const units = resolved?.units ?? [];
19892
+ const resolved = boardEntity(entity);
19893
+ const tiles = Array.isArray(resolved?.tiles) ? resolved.tiles : [];
19894
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
19895
+ const units = Array.isArray(resolved?.units) ? resolved.units : [];
19791
19896
  const assetManifest = resolved?.assetManifest;
19792
19897
  const backgroundImage = resolved?.backgroundImage;
19793
19898
  const [hoveredTile, setHoveredTile] = React84.useState(null);
@@ -19815,7 +19920,7 @@ function CastleBoard({
19815
19920
  onFeatureClick?.(feature);
19816
19921
  if (featureClickEvent) {
19817
19922
  eventBus.emit(`UI:${featureClickEvent}`, {
19818
- featureId: feature.id,
19923
+ featureId: feature.id ?? "",
19819
19924
  featureType: feature.type,
19820
19925
  x: feature.x,
19821
19926
  y: feature.y
@@ -19883,6 +19988,7 @@ var init_CastleBoard = __esm({
19883
19988
  init_cn();
19884
19989
  init_useEventBus();
19885
19990
  init_IsometricCanvas2();
19991
+ init_boardEntity();
19886
19992
  init_isometric();
19887
19993
  CastleBoard.displayName = "CastleBoard";
19888
19994
  }
@@ -20693,14 +20799,14 @@ function ClassifierBoard({
20693
20799
  }) {
20694
20800
  const { emit } = useEventBus();
20695
20801
  const { t } = hooks.useTranslate();
20696
- const resolved = Array.isArray(entity) ? entity[0] : entity;
20802
+ const resolved = boardEntity(entity);
20697
20803
  const [assignments, setAssignments] = React84.useState({});
20698
20804
  const [headerError, setHeaderError] = React84.useState(false);
20699
20805
  const [submitted, setSubmitted] = React84.useState(false);
20700
20806
  const [attempts, setAttempts] = React84.useState(0);
20701
20807
  const [showHint, setShowHint] = React84.useState(false);
20702
- const items = resolved?.items ?? [];
20703
- const categories = resolved?.categories ?? [];
20808
+ const items = Array.isArray(resolved?.items) ? resolved.items : [];
20809
+ const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
20704
20810
  const unassignedItems = items.filter((item) => !assignments[item.id]);
20705
20811
  const allAssigned = Object.keys(assignments).length === items.length;
20706
20812
  const results = submitted ? items.map((item) => ({
@@ -20732,7 +20838,7 @@ function ClassifierBoard({
20732
20838
  }, [items, assignments, attempts, completeEvent, emit]);
20733
20839
  const handleReset = () => {
20734
20840
  setSubmitted(false);
20735
- if (attempts >= 2 && resolved?.hint) {
20841
+ if (attempts >= 2 && str(resolved?.hint)) {
20736
20842
  setShowHint(true);
20737
20843
  }
20738
20844
  };
@@ -20743,20 +20849,25 @@ function ClassifierBoard({
20743
20849
  setShowHint(false);
20744
20850
  };
20745
20851
  if (!resolved) return null;
20852
+ const theme = resolved.theme ?? void 0;
20853
+ const themeBackground = theme?.background;
20854
+ const headerImage = str(resolved.headerImage);
20855
+ const hint = str(resolved.hint);
20856
+ const failMessage = str(resolved.failMessage);
20746
20857
  return /* @__PURE__ */ jsxRuntime.jsx(
20747
20858
  Box,
20748
20859
  {
20749
20860
  className,
20750
20861
  style: {
20751
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
20862
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
20752
20863
  backgroundSize: "cover",
20753
20864
  backgroundPosition: "center"
20754
20865
  },
20755
20866
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
20756
- 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,
20867
+ 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,
20757
20868
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
20758
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
20759
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
20869
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
20870
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
20760
20871
  ] }) }),
20761
20872
  unassignedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
20762
20873
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("classifier.itemsToSort") }),
@@ -20808,10 +20919,10 @@ function ClassifierBoard({
20808
20919
  }) }),
20809
20920
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
20810
20921
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: allCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "lg", className: allCorrect ? "text-success" : "text-error" }),
20811
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
20812
- !allCorrect && resolved.failMessage && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "text-muted-foreground", children: resolved.failMessage })
20922
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("classifier.allCorrect") : `${correctCount}/${items.length} ${t("classifier.correct")}` }),
20923
+ !allCorrect && failMessage && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "text-muted-foreground", children: failMessage })
20813
20924
  ] }) }),
20814
- 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 }) }),
20925
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
20815
20926
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
20816
20927
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20817
20928
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -20830,6 +20941,7 @@ var init_ClassifierBoard = __esm({
20830
20941
  "components/game/organisms/puzzles/classifier/ClassifierBoard.tsx"() {
20831
20942
  init_atoms2();
20832
20943
  init_useEventBus();
20944
+ init_boardEntity();
20833
20945
  ClassifierBoard.displayName = "ClassifierBoard";
20834
20946
  }
20835
20947
  });
@@ -27200,7 +27312,7 @@ function InventoryPanel({
27200
27312
  const slotArray = Array.from({ length: safeSlots }, (_, index) => {
27201
27313
  return safeItems[index] ?? null;
27202
27314
  });
27203
- const rows = Math.ceil(safeSlots / safeColumns);
27315
+ const rows2 = Math.ceil(safeSlots / safeColumns);
27204
27316
  const handleSlotClick = React84.useCallback((index) => {
27205
27317
  if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
27206
27318
  onSelectSlot?.(index);
@@ -27269,7 +27381,7 @@ function InventoryPanel({
27269
27381
  className: "grid gap-1 bg-[var(--color-card)] p-2 rounded-container border border-border",
27270
27382
  style: {
27271
27383
  gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
27272
- gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
27384
+ gridTemplateRows: `repeat(${rows2}, ${slotSize}px)`
27273
27385
  },
27274
27386
  children: slotArray.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
27275
27387
  "button",
@@ -31233,11 +31345,11 @@ function LatticeSVG({
31233
31345
  }) {
31234
31346
  const paths = [];
31235
31347
  const cols = 5;
31236
- const rows = Math.ceil(h / (w / cols));
31348
+ const rows2 = Math.ceil(h / (w / cols));
31237
31349
  const cellW = w / cols;
31238
31350
  const cellH = cellW;
31239
31351
  const bulge = cellW * 0.3;
31240
- for (let row = 0; row < rows; row++) {
31352
+ for (let row = 0; row < rows2; row++) {
31241
31353
  for (let col = 0; col < cols; col++) {
31242
31354
  const cx = col * cellW + cellW / 2;
31243
31355
  const cy = row * cellH + cellH / 2;
@@ -31649,7 +31761,7 @@ var init_MatrixQuestion = __esm({
31649
31761
  };
31650
31762
  MatrixQuestion = ({
31651
31763
  title,
31652
- rows,
31764
+ rows: rows2,
31653
31765
  columns = DEFAULT_MATRIX_COLUMNS,
31654
31766
  values,
31655
31767
  onChange,
@@ -31659,7 +31771,7 @@ var init_MatrixQuestion = __esm({
31659
31771
  className
31660
31772
  }) => {
31661
31773
  const styles = sizeStyles13[size];
31662
- const safeRows = rows ?? [];
31774
+ const safeRows = rows2 ?? [];
31663
31775
  const safeValues = values ?? {};
31664
31776
  const eventBus = useEventBus();
31665
31777
  const handleChange = React84.useCallback(
@@ -32287,7 +32399,8 @@ var init_PositionedCanvas = __esm({
32287
32399
  dragRef.current = null;
32288
32400
  setDraggingId(null);
32289
32401
  if (!wasDrag) {
32290
- const next = selectedId === item.id ? null : item.id;
32402
+ const itemId = item.id;
32403
+ const next = selectedId === itemId ? null : itemId;
32291
32404
  onSelect?.(next);
32292
32405
  if (selectEvent) {
32293
32406
  eventBus.emit(`UI:${selectEvent}`, { id: next });
@@ -32322,15 +32435,22 @@ var init_PositionedCanvas = __esm({
32322
32435
  style: { width, height },
32323
32436
  onClick: handleContainerClick,
32324
32437
  children: items.map((item) => {
32438
+ const itemId = item.id;
32439
+ const label = item.label;
32440
+ const x = item.x;
32441
+ const y = item.y;
32442
+ const capacity = item.capacity;
32443
+ const partySize = item.partySize;
32444
+ const serverName = item.serverName;
32325
32445
  const status = item.status ?? "empty";
32326
32446
  const shape = item.shape ?? "round";
32327
- const isSelected = selectedId === item.id;
32328
- const isDragging = draggingId === item.id;
32447
+ const isSelected = selectedId === itemId;
32448
+ const isDragging = draggingId === itemId;
32329
32449
  const statusBadge = STATUS_BADGE[status];
32330
32450
  return /* @__PURE__ */ jsxRuntime.jsxs(
32331
32451
  Box,
32332
32452
  {
32333
- "data-testid": `item-node-${item.id}`,
32453
+ "data-testid": `item-node-${itemId}`,
32334
32454
  "data-status": status,
32335
32455
  className: cn(
32336
32456
  "absolute flex flex-col items-center justify-center gap-1 border-2 select-none",
@@ -32341,7 +32461,7 @@ var init_PositionedCanvas = __esm({
32341
32461
  isSelected && "outline outline-2 outline-offset-2 outline-primary shadow-md",
32342
32462
  isDragging && "shadow-lg z-10"
32343
32463
  ),
32344
- style: { left: item.x, top: item.y, touchAction: "none" },
32464
+ style: { left: x, top: y, touchAction: "none" },
32345
32465
  onPointerDown: (e) => handlePointerDown(e, item),
32346
32466
  onPointerMove: handlePointerMove,
32347
32467
  onPointerUp: (e) => handlePointerUp(e, item),
@@ -32349,10 +32469,10 @@ var init_PositionedCanvas = __esm({
32349
32469
  children: [
32350
32470
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1", children: [
32351
32471
  getStatusIcon(status),
32352
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: item.label })
32472
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "semibold", children: label })
32353
32473
  ] }),
32354
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: item.partySize !== void 0 && status === "seated" ? `${item.partySize}/${item.capacity}` : `Cap ${item.capacity}` }),
32355
- status === "seated" && item.serverName && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: item.serverName }),
32474
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: partySize !== void 0 && status === "seated" ? `${partySize}/${capacity}` : `Cap ${capacity}` }),
32475
+ status === "seated" && serverName && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "truncate max-w-[80%]", children: serverName }),
32356
32476
  isSelected && /* @__PURE__ */ jsxRuntime.jsx(
32357
32477
  Badge,
32358
32478
  {
@@ -32364,7 +32484,7 @@ var init_PositionedCanvas = __esm({
32364
32484
  )
32365
32485
  ]
32366
32486
  },
32367
- item.id
32487
+ itemId
32368
32488
  );
32369
32489
  })
32370
32490
  }
@@ -33136,9 +33256,10 @@ var init_RichBlockEditor = __esm({
33136
33256
  });
33137
33257
  function collectInitiallyCollapsed(nodes, acc) {
33138
33258
  for (const n of nodes) {
33259
+ const replies = n.replies;
33139
33260
  if (n.collapsed) acc.add(n.id);
33140
- if (n.replies && n.replies.length > 0) {
33141
- collectInitiallyCollapsed(n.replies, acc);
33261
+ if (replies && replies.length > 0) {
33262
+ collectInitiallyCollapsed(replies, acc);
33142
33263
  }
33143
33264
  }
33144
33265
  }
@@ -33168,44 +33289,52 @@ var init_ReplyTree = __esm({
33168
33289
  }) => {
33169
33290
  const eventBus = useEventBus();
33170
33291
  const { t } = hooks.useTranslate();
33171
- const hasReplies = !!node.replies && node.replies.length > 0;
33172
- const isCollapsed = collapsedSet.has(node.id);
33292
+ const nodeId = node.id;
33293
+ const authorName = node.authorName;
33294
+ const authorAvatarUrl = node.authorAvatarUrl;
33295
+ const content = node.content;
33296
+ const postedAt = node.postedAt;
33297
+ const voteCount = node.voteCount;
33298
+ const userVote = node.userVote;
33299
+ const replies = node.replies;
33300
+ const hasReplies = !!replies && replies.length > 0;
33301
+ const isCollapsed = collapsedSet.has(nodeId);
33173
33302
  const atMaxDepth = depth >= maxDepth;
33174
33303
  const [replyOpen, setReplyOpen] = React84.useState(false);
33175
33304
  const [draft, setDraft] = React84.useState("");
33176
33305
  const handleVote = React84.useCallback(
33177
33306
  (next) => {
33178
- onVote?.(node.id, next);
33179
- if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId: node.id, vote: next });
33307
+ onVote?.(nodeId, next);
33308
+ if (voteEvent) eventBus.emit(`UI:${voteEvent}`, { nodeId, vote: next });
33180
33309
  },
33181
- [node.id, onVote, voteEvent, eventBus]
33310
+ [nodeId, onVote, voteEvent, eventBus]
33182
33311
  );
33183
33312
  const handleReply = React84.useCallback(() => {
33184
- onReply?.(node.id);
33313
+ onReply?.(nodeId);
33185
33314
  setReplyOpen((open) => !open);
33186
- }, [node.id, onReply]);
33315
+ }, [nodeId, onReply]);
33187
33316
  const handleSubmitReply = React84.useCallback(() => {
33188
- const content = draft.trim();
33189
- if (!content) return;
33190
- if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: node.id, content });
33317
+ const text = draft.trim();
33318
+ if (!text) return;
33319
+ if (replyEvent) eventBus.emit(`UI:${replyEvent}`, { parentNodeId: nodeId, content: text });
33191
33320
  setDraft("");
33192
33321
  setReplyOpen(false);
33193
- }, [node.id, draft, replyEvent, eventBus]);
33322
+ }, [nodeId, draft, replyEvent, eventBus]);
33194
33323
  const handleCancelReply = React84.useCallback(() => {
33195
33324
  setDraft("");
33196
33325
  setReplyOpen(false);
33197
33326
  }, []);
33198
33327
  const handleFlag = React84.useCallback(() => {
33199
- onFlag?.(node.id);
33200
- if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId: node.id });
33201
- }, [node.id, onFlag, flagEvent, eventBus]);
33328
+ onFlag?.(nodeId);
33329
+ if (flagEvent) eventBus.emit(`UI:${flagEvent}`, { nodeId });
33330
+ }, [nodeId, onFlag, flagEvent, eventBus]);
33202
33331
  const handleContinue = React84.useCallback(() => {
33203
- onContinueThread?.(node.id);
33204
- if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId: node.id });
33205
- }, [node.id, onContinueThread, continueThreadEvent, eventBus]);
33332
+ onContinueThread?.(nodeId);
33333
+ if (continueThreadEvent) eventBus.emit(`UI:${continueThreadEvent}`, { nodeId });
33334
+ }, [nodeId, onContinueThread, continueThreadEvent, eventBus]);
33206
33335
  const handleToggle = React84.useCallback(() => {
33207
- toggleCollapse(node.id);
33208
- }, [node.id, toggleCollapse]);
33336
+ toggleCollapse(nodeId);
33337
+ }, [nodeId, toggleCollapse]);
33209
33338
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-stretch min-w-0", children: [
33210
33339
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-col items-center flex-shrink-0 w-6", children: [
33211
33340
  hasReplies ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -33237,25 +33366,25 @@ var init_ReplyTree = __esm({
33237
33366
  /* @__PURE__ */ jsxRuntime.jsx(
33238
33367
  Avatar,
33239
33368
  {
33240
- src: node.authorAvatarUrl,
33241
- name: node.authorName,
33369
+ src: authorAvatarUrl,
33370
+ name: authorName,
33242
33371
  size: "sm"
33243
33372
  }
33244
33373
  ),
33245
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: node.authorName }),
33246
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: node.postedAt })
33374
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: authorName }),
33375
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: postedAt })
33247
33376
  ] }),
33248
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: node.content }),
33377
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "whitespace-pre-wrap break-words", children: content }),
33249
33378
  showActions && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
33250
33379
  /* @__PURE__ */ jsxRuntime.jsx(
33251
33380
  VoteStack,
33252
33381
  {
33253
- count: node.voteCount ?? 0,
33254
- userVote: node.userVote ?? null,
33382
+ count: voteCount ?? 0,
33383
+ userVote: userVote ?? null,
33255
33384
  onVote: handleVote,
33256
33385
  size: "sm",
33257
33386
  variant: "horizontal",
33258
- label: t("replyTree.voteOnReplyBy", { author: node.authorName })
33387
+ label: t("replyTree.voteOnReplyBy", { author: authorName })
33259
33388
  }
33260
33389
  ),
33261
33390
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -33265,7 +33394,7 @@ var init_ReplyTree = __esm({
33265
33394
  size: "sm",
33266
33395
  leftIcon: "message-square",
33267
33396
  onClick: handleReply,
33268
- "aria-label": t("replyTree.replyTo", { author: node.authorName }),
33397
+ "aria-label": t("replyTree.replyTo", { author: authorName }),
33269
33398
  children: t("replyTree.reply")
33270
33399
  }
33271
33400
  ),
@@ -33276,7 +33405,7 @@ var init_ReplyTree = __esm({
33276
33405
  size: "sm",
33277
33406
  leftIcon: "flag",
33278
33407
  onClick: handleFlag,
33279
- "aria-label": t("replyTree.flagReplyBy", { author: node.authorName }),
33408
+ "aria-label": t("replyTree.flagReplyBy", { author: authorName }),
33280
33409
  children: t("replyTree.flag")
33281
33410
  }
33282
33411
  )
@@ -33288,9 +33417,9 @@ var init_ReplyTree = __esm({
33288
33417
  inputType: "textarea",
33289
33418
  rows: 2,
33290
33419
  value: draft,
33291
- placeholder: t("replyTree.replyToPlaceholder", { author: node.authorName }),
33420
+ placeholder: t("replyTree.replyToPlaceholder", { author: authorName }),
33292
33421
  onChange: (e) => setDraft(e.target.value),
33293
- "aria-label": t("replyTree.replyTo", { author: node.authorName })
33422
+ "aria-label": t("replyTree.replyTo", { author: authorName })
33294
33423
  }
33295
33424
  ),
33296
33425
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-row gap-2 items-center", children: [
@@ -33321,7 +33450,7 @@ var init_ReplyTree = __esm({
33321
33450
  ),
33322
33451
  children: t("replyTree.continueThread")
33323
33452
  }
33324
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-2 mt-1", children: node.replies.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
33453
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-2 mt-1", children: replies.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
33325
33454
  ReplyTreeNode,
33326
33455
  {
33327
33456
  node: child,
@@ -35641,8 +35770,8 @@ var init_WizardContainer = __esm({
35641
35770
  return void 0;
35642
35771
  if (typeof controlledStep === "number") return controlledStep;
35643
35772
  if (typeof controlledStep === "string") return parseInt(controlledStep, 10);
35644
- const num = Number(controlledStep);
35645
- return isNaN(num) ? void 0 : num;
35773
+ const num2 = Number(controlledStep);
35774
+ return isNaN(num2) ? void 0 : num2;
35646
35775
  })();
35647
35776
  const currentStep = normalizedControlledStep !== void 0 ? normalizedControlledStep : internalStep;
35648
35777
  const totalSteps = steps.length;
@@ -37347,7 +37476,7 @@ function DebuggerBoard({
37347
37476
  }) {
37348
37477
  const { emit } = useEventBus();
37349
37478
  const { t } = hooks.useTranslate();
37350
- const resolved = Array.isArray(entity) ? entity[0] : entity;
37479
+ const resolved = boardEntity(entity);
37351
37480
  const [flaggedLines, setFlaggedLines] = React84.useState(/* @__PURE__ */ new Set());
37352
37481
  const [headerError, setHeaderError] = React84.useState(false);
37353
37482
  const [submitted, setSubmitted] = React84.useState(false);
@@ -37365,7 +37494,7 @@ function DebuggerBoard({
37365
37494
  return next;
37366
37495
  });
37367
37496
  };
37368
- const lines = resolved?.lines ?? [];
37497
+ const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
37369
37498
  const bugLines = lines.filter((l) => l.isBug);
37370
37499
  const correctFlags = lines.filter((l) => l.isBug && flaggedLines.has(l.id));
37371
37500
  const falseFlags = lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
@@ -37380,7 +37509,7 @@ function DebuggerBoard({
37380
37509
  }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
37381
37510
  const handleReset = () => {
37382
37511
  setSubmitted(false);
37383
- if (attempts >= 2 && resolved?.hint) {
37512
+ if (attempts >= 2 && str(resolved?.hint)) {
37384
37513
  setShowHint(true);
37385
37514
  }
37386
37515
  };
@@ -37391,24 +37520,28 @@ function DebuggerBoard({
37391
37520
  setShowHint(false);
37392
37521
  };
37393
37522
  if (!resolved) return null;
37523
+ const theme = resolved.theme ?? void 0;
37524
+ const themeBackground = theme?.background;
37525
+ const headerImage = str(resolved.headerImage);
37526
+ const hint = str(resolved.hint);
37394
37527
  return /* @__PURE__ */ jsxRuntime.jsx(
37395
37528
  Box,
37396
37529
  {
37397
37530
  className,
37398
37531
  style: {
37399
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
37532
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
37400
37533
  backgroundSize: "cover",
37401
37534
  backgroundPosition: "center"
37402
37535
  },
37403
37536
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
37404
- 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,
37537
+ 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,
37405
37538
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37406
37539
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
37407
37540
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Bug, size: "sm" }),
37408
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title })
37541
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
37409
37542
  ] }),
37410
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description }),
37411
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(resolved.bugCount) }) })
37543
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
37544
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
37412
37545
  ] }) }),
37413
37546
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
37414
37547
  const isFlagged = flaggedLines.has(line.id);
@@ -37440,7 +37573,7 @@ function DebuggerBoard({
37440
37573
  );
37441
37574
  }) }) }),
37442
37575
  submitted && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
37443
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? resolved.successMessage ?? t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
37576
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? str(resolved.successMessage) || t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
37444
37577
  bugLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "start", children: [
37445
37578
  /* @__PURE__ */ jsxRuntime.jsx(
37446
37579
  Icon,
@@ -37456,7 +37589,7 @@ function DebuggerBoard({
37456
37589
  ] })
37457
37590
  ] }, line.id))
37458
37591
  ] }) }),
37459
- 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 }) }),
37592
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
37460
37593
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
37461
37594
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
37462
37595
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
@@ -37475,6 +37608,7 @@ var init_DebuggerBoard = __esm({
37475
37608
  "components/game/organisms/puzzles/debugger/DebuggerBoard.tsx"() {
37476
37609
  init_atoms2();
37477
37610
  init_useEventBus();
37611
+ init_boardEntity();
37478
37612
  DebuggerBoard.displayName = "DebuggerBoard";
37479
37613
  }
37480
37614
  });
@@ -37512,7 +37646,7 @@ function getBadgeVariant(fieldName, value) {
37512
37646
  return "default";
37513
37647
  }
37514
37648
  function formatFieldLabel(fieldName) {
37515
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
37649
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase());
37516
37650
  }
37517
37651
  function formatFieldValue(value, fieldName) {
37518
37652
  if (typeof value === "number") {
@@ -37531,26 +37665,26 @@ function formatFieldValue(value, fieldName) {
37531
37665
  }
37532
37666
  function renderRichFieldValue(value, fieldName, fieldType) {
37533
37667
  if (value === void 0 || value === null) return "\u2014";
37534
- const str = String(value);
37668
+ const str2 = String(value);
37535
37669
  switch (fieldType) {
37536
37670
  case "image":
37537
37671
  case "url": {
37538
- if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
37672
+ if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
37539
37673
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
37540
37674
  "img",
37541
37675
  {
37542
- src: str,
37676
+ src: str2,
37543
37677
  alt: formatFieldLabel(fieldName),
37544
37678
  className: "max-w-full max-h-64 rounded-md object-contain",
37545
37679
  loading: "lazy"
37546
37680
  }
37547
37681
  ) });
37548
37682
  }
37549
- return str;
37683
+ return str2;
37550
37684
  }
37551
37685
  case "markdown":
37552
37686
  case "richtext":
37553
- return /* @__PURE__ */ jsxRuntime.jsx(React84.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "break-words", children: str }), children: /* @__PURE__ */ jsxRuntime.jsx(
37687
+ return /* @__PURE__ */ jsxRuntime.jsx(React84.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "break-words", children: str2 }), children: /* @__PURE__ */ jsxRuntime.jsx(
37554
37688
  Box,
37555
37689
  {
37556
37690
  className: "prose prose-sm max-w-none",
@@ -37568,11 +37702,11 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37568
37702
  "--tw-prose-th-borders": "var(--color-border)",
37569
37703
  "--tw-prose-td-borders": "var(--color-border)"
37570
37704
  },
37571
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str })
37705
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactMarkdown2, { children: str2 })
37572
37706
  }
37573
37707
  ) });
37574
37708
  case "code":
37575
- 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 }) }) });
37709
+ 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 }) }) });
37576
37710
  case "html":
37577
37711
  return /* @__PURE__ */ jsxRuntime.jsx(
37578
37712
  Box,
@@ -37592,12 +37726,12 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37592
37726
  "--tw-prose-th-borders": "var(--color-border)",
37593
37727
  "--tw-prose-td-borders": "var(--color-border)"
37594
37728
  },
37595
- children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str })
37729
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str2 })
37596
37730
  }
37597
37731
  );
37598
37732
  case "date":
37599
37733
  case "datetime": {
37600
- const d = new Date(str);
37734
+ const d = new Date(str2);
37601
37735
  if (!isNaN(d.getTime())) {
37602
37736
  return d.toLocaleDateString(void 0, {
37603
37737
  year: "numeric",
@@ -37606,7 +37740,7 @@ function renderRichFieldValue(value, fieldName, fieldType) {
37606
37740
  ...fieldType === "datetime" ? { hour: "2-digit", minute: "2-digit" } : {}
37607
37741
  });
37608
37742
  }
37609
- return str;
37743
+ return str2;
37610
37744
  }
37611
37745
  default:
37612
37746
  return formatFieldValue(value, fieldName);
@@ -38284,6 +38418,40 @@ var init_RuleEditor = __esm({
38284
38418
  RuleEditor.displayName = "RuleEditor";
38285
38419
  }
38286
38420
  });
38421
+
38422
+ // components/game/organisms/puzzles/event-handler/puzzleObject.ts
38423
+ function objId(o) {
38424
+ return o.id == null ? "" : String(o.id);
38425
+ }
38426
+ function objName(o) {
38427
+ return o.name == null ? "" : String(o.name);
38428
+ }
38429
+ function objIcon(o) {
38430
+ return o.icon == null ? "" : String(o.icon);
38431
+ }
38432
+ function objStates(o) {
38433
+ return Array.isArray(o.states) ? o.states : [];
38434
+ }
38435
+ function objCurrentState(o) {
38436
+ return o.currentState == null ? "" : String(o.currentState);
38437
+ }
38438
+ function objAvailableEvents(o) {
38439
+ return Array.isArray(o.availableEvents) ? o.availableEvents : [];
38440
+ }
38441
+ function objAvailableActions(o) {
38442
+ return Array.isArray(o.availableActions) ? o.availableActions : [];
38443
+ }
38444
+ function objRules(o) {
38445
+ return Array.isArray(o.rules) ? o.rules : [];
38446
+ }
38447
+ function objMaxRules(o) {
38448
+ const n = Number(o.maxRules);
38449
+ return Number.isFinite(n) && n > 0 ? n : 3;
38450
+ }
38451
+ var init_puzzleObject = __esm({
38452
+ "components/game/organisms/puzzles/event-handler/puzzleObject.ts"() {
38453
+ }
38454
+ });
38287
38455
  function ObjectRulePanel({
38288
38456
  object,
38289
38457
  onRulesChange,
@@ -38291,55 +38459,63 @@ function ObjectRulePanel({
38291
38459
  className
38292
38460
  }) {
38293
38461
  const { t } = hooks.useTranslate();
38294
- const maxRules = object.maxRules || 3;
38295
- const canAdd = object.rules.length < maxRules;
38462
+ const id = objId(object);
38463
+ const name = objName(object);
38464
+ const icon = objIcon(object);
38465
+ const states = objStates(object);
38466
+ const currentState = objCurrentState(object);
38467
+ const availableEvents = objAvailableEvents(object);
38468
+ const availableActions = objAvailableActions(object);
38469
+ const rules = objRules(object);
38470
+ const maxRules = objMaxRules(object);
38471
+ const canAdd = rules.length < maxRules;
38296
38472
  const handleRuleChange = React84.useCallback((index, updatedRule) => {
38297
- const newRules = [...object.rules];
38473
+ const newRules = [...rules];
38298
38474
  newRules[index] = updatedRule;
38299
- onRulesChange(object.id, newRules);
38300
- }, [object.id, object.rules, onRulesChange]);
38475
+ onRulesChange(id, newRules);
38476
+ }, [id, rules, onRulesChange]);
38301
38477
  const handleRuleRemove = React84.useCallback((index) => {
38302
- const newRules = object.rules.filter((_, i) => i !== index);
38303
- onRulesChange(object.id, newRules);
38304
- }, [object.id, object.rules, onRulesChange]);
38478
+ const newRules = rules.filter((_, i) => i !== index);
38479
+ onRulesChange(id, newRules);
38480
+ }, [id, rules, onRulesChange]);
38305
38481
  const handleAddRule = React84.useCallback(() => {
38306
38482
  if (!canAdd || disabled) return;
38307
- const firstEvent = object.availableEvents[0]?.value || "";
38308
- const firstAction = object.availableActions[0]?.value || "";
38483
+ const firstEvent = availableEvents[0]?.value || "";
38484
+ const firstAction = availableActions[0]?.value || "";
38309
38485
  const newRule = {
38310
38486
  id: `rule-${nextRuleId++}`,
38311
38487
  whenEvent: firstEvent,
38312
38488
  thenAction: firstAction
38313
38489
  };
38314
- onRulesChange(object.id, [...object.rules, newRule]);
38315
- }, [canAdd, disabled, object, onRulesChange]);
38490
+ onRulesChange(id, [...rules, newRule]);
38491
+ }, [canAdd, disabled, id, rules, availableEvents, availableActions, onRulesChange]);
38316
38492
  const machine = {
38317
- name: object.name,
38318
- states: object.states,
38319
- currentState: object.currentState,
38320
- transitions: object.rules.map((r) => ({
38321
- from: object.currentState,
38322
- to: object.states.find((s) => s !== object.currentState) || object.currentState,
38493
+ name,
38494
+ states,
38495
+ currentState,
38496
+ transitions: rules.map((r) => ({
38497
+ from: currentState,
38498
+ to: states.find((s) => s !== currentState) || currentState,
38323
38499
  event: r.whenEvent
38324
38500
  }))
38325
38501
  };
38326
38502
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
38327
38503
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center", gap: "sm", children: [
38328
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: object.icon }),
38504
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: icon }),
38329
38505
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
38330
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
38331
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + object.currentState })
38506
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: name }),
38507
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + currentState })
38332
38508
  ] })
38333
38509
  ] }),
38334
38510
  /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
38335
38511
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38336
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
38337
- object.rules.map((rule, i) => /* @__PURE__ */ jsxRuntime.jsx(
38512
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: rules.length, max: maxRules }) + ":" }),
38513
+ rules.map((rule, i) => /* @__PURE__ */ jsxRuntime.jsx(
38338
38514
  RuleEditor,
38339
38515
  {
38340
38516
  rule,
38341
- availableEvents: object.availableEvents,
38342
- availableActions: object.availableActions,
38517
+ availableEvents,
38518
+ availableActions,
38343
38519
  onChange: (r) => handleRuleChange(i, r),
38344
38520
  onRemove: () => handleRuleRemove(i),
38345
38521
  disabled
@@ -38357,6 +38533,7 @@ var init_ObjectRulePanel = __esm({
38357
38533
  init_cn();
38358
38534
  init_TraitStateViewer();
38359
38535
  init_RuleEditor();
38536
+ init_puzzleObject();
38360
38537
  nextRuleId = 1;
38361
38538
  ObjectRulePanel.displayName = "ObjectRulePanel";
38362
38539
  }
@@ -38423,11 +38600,11 @@ function EventHandlerBoard({
38423
38600
  }) {
38424
38601
  const { emit } = useEventBus();
38425
38602
  const { t } = hooks.useTranslate();
38426
- const resolved = Array.isArray(entity) ? entity[0] : entity;
38427
- const entityObjects = resolved?.objects ?? [];
38428
- const [objects, setObjects] = React84.useState(entityObjects);
38603
+ const resolved = boardEntity(entity);
38604
+ const entityObjects = rows(resolved?.objects);
38605
+ const [objects, setObjects] = React84.useState(() => [...entityObjects]);
38429
38606
  const [selectedObjectId, setSelectedObjectId] = React84.useState(
38430
- entityObjects[0]?.id || null
38607
+ entityObjects[0] ? objId(entityObjects[0]) : null
38431
38608
  );
38432
38609
  const [headerError, setHeaderError] = React84.useState(false);
38433
38610
  const [playState, setPlayState] = React84.useState("editing");
@@ -38438,10 +38615,10 @@ function EventHandlerBoard({
38438
38615
  React84.useEffect(() => () => {
38439
38616
  if (timerRef.current) clearTimeout(timerRef.current);
38440
38617
  }, []);
38441
- const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
38618
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
38442
38619
  const handleRulesChange = React84.useCallback((objectId, rules) => {
38443
38620
  setObjects((prev) => prev.map(
38444
- (o) => o.id === objectId ? { ...o, rules } : o
38621
+ (o) => objId(o) === objectId ? { ...o, rules } : o
38445
38622
  ));
38446
38623
  }, []);
38447
38624
  const addLogEntry = React84.useCallback((icon, message, status = "done") => {
@@ -38455,11 +38632,12 @@ function EventHandlerBoard({
38455
38632
  setEventLog([]);
38456
38633
  const allRules = [];
38457
38634
  objects.forEach((obj) => {
38458
- obj.rules.forEach((rule) => {
38635
+ objRules(obj).forEach((rule) => {
38459
38636
  allRules.push({ object: obj, rule });
38460
38637
  });
38461
38638
  });
38462
- const triggers = resolved?.triggerEvents || [];
38639
+ const triggers = Array.isArray(resolved?.triggerEvents) ? resolved.triggerEvents : [];
38640
+ const goalEvent = str(resolved?.goalEvent);
38463
38641
  const eventQueue = [...triggers];
38464
38642
  const firedEvents = /* @__PURE__ */ new Set();
38465
38643
  let stepIdx = 0;
@@ -38488,14 +38666,14 @@ function EventHandlerBoard({
38488
38666
  addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
38489
38667
  } else {
38490
38668
  matching.forEach(({ object, rule }) => {
38491
- addLogEntry(object.icon, t("eventHandler.heardEvent", { object: object.name, event: currentEvent, action: rule.thenAction }), "done");
38669
+ addLogEntry(objIcon(object), t("eventHandler.heardEvent", { object: objName(object), event: currentEvent, action: rule.thenAction }), "done");
38492
38670
  eventQueue.push(rule.thenAction);
38493
- if (rule.thenAction === resolved?.goalEvent) {
38671
+ if (rule.thenAction === goalEvent) {
38494
38672
  goalReached = true;
38495
38673
  }
38496
38674
  });
38497
38675
  }
38498
- if (currentEvent === resolved?.goalEvent) {
38676
+ if (currentEvent === goalEvent) {
38499
38677
  goalReached = true;
38500
38678
  }
38501
38679
  stepIdx++;
@@ -38513,65 +38691,75 @@ function EventHandlerBoard({
38513
38691
  }, []);
38514
38692
  const handleReset = React84.useCallback(() => {
38515
38693
  if (timerRef.current) clearTimeout(timerRef.current);
38516
- setObjects(resolved?.objects ?? []);
38694
+ const resetObjects = rows(resolved?.objects);
38695
+ setObjects([...resetObjects]);
38517
38696
  setPlayState("editing");
38518
38697
  setEventLog([]);
38519
- setSelectedObjectId((resolved?.objects ?? [])[0]?.id || null);
38698
+ setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
38520
38699
  setAttempts(0);
38521
38700
  }, [resolved?.objects]);
38522
38701
  if (!resolved) return null;
38523
38702
  const objectViewers = objects.map((obj) => {
38703
+ const states = objStates(obj);
38704
+ const currentState = objCurrentState(obj);
38524
38705
  const machine = {
38525
- name: obj.name,
38526
- states: obj.states,
38527
- currentState: obj.currentState,
38528
- transitions: obj.rules.map((r) => ({
38529
- from: obj.currentState,
38530
- to: obj.states.find((s) => s !== obj.currentState) || obj.currentState,
38706
+ name: objName(obj),
38707
+ states,
38708
+ currentState,
38709
+ transitions: objRules(obj).map((r) => ({
38710
+ from: currentState,
38711
+ to: states.find((s) => s !== currentState) || currentState,
38531
38712
  event: r.whenEvent
38532
38713
  }))
38533
38714
  };
38534
38715
  return { obj, machine };
38535
38716
  });
38536
- const showHint = attempts >= 3 && resolved.hint;
38717
+ const hint = str(resolved.hint);
38718
+ const showHint = attempts >= 3 && hint;
38719
+ const theme = resolved.theme ?? void 0;
38720
+ const themeBackground = theme?.background;
38721
+ const headerImage = str(resolved.headerImage);
38537
38722
  const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
38538
38723
  return /* @__PURE__ */ jsxRuntime.jsxs(
38539
38724
  VStack,
38540
38725
  {
38541
38726
  className: cn("p-4 gap-6", className),
38542
38727
  style: {
38543
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
38728
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
38544
38729
  backgroundSize: "cover",
38545
38730
  backgroundPosition: "center"
38546
38731
  },
38547
38732
  children: [
38548
- 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,
38733
+ 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,
38549
38734
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
38550
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
38551
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
38735
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
38736
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
38552
38737
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
38553
38738
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
38554
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.goalCondition })
38739
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: str(resolved.goalCondition) })
38555
38740
  ] })
38556
38741
  ] }),
38557
38742
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38558
38743
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
38559
- /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsxRuntime.jsx(
38560
- Box,
38561
- {
38562
- className: cn(
38563
- "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
38564
- selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
38565
- ),
38566
- onClick: () => setSelectedObjectId(obj.id),
38567
- children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
38568
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: obj.icon }),
38569
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
38570
- /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
38571
- ] })
38572
- },
38573
- obj.id
38574
- )) })
38744
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => {
38745
+ const oid = objId(obj);
38746
+ return /* @__PURE__ */ jsxRuntime.jsx(
38747
+ Box,
38748
+ {
38749
+ className: cn(
38750
+ "p-3 rounded-container border-2 cursor-pointer transition-all hover:scale-105",
38751
+ selectedObjectId === oid ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
38752
+ ),
38753
+ onClick: () => setSelectedObjectId(oid),
38754
+ children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
38755
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", children: objIcon(obj) }),
38756
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: objName(obj) }),
38757
+ /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
38758
+ ] })
38759
+ },
38760
+ oid
38761
+ );
38762
+ }) })
38575
38763
  ] }),
38576
38764
  selectedObject && /* @__PURE__ */ jsxRuntime.jsx(
38577
38765
  ObjectRulePanel,
@@ -38582,12 +38770,12 @@ function EventHandlerBoard({
38582
38770
  }
38583
38771
  ),
38584
38772
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
38585
- 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") }) }),
38773
+ 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") }) }),
38586
38774
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38587
38775
  /* @__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) }) }),
38588
38776
  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: [
38589
38777
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
38590
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
38778
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
38591
38779
  ] }) })
38592
38780
  ] }),
38593
38781
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -38615,6 +38803,8 @@ var init_EventHandlerBoard = __esm({
38615
38803
  init_TraitStateViewer();
38616
38804
  init_ObjectRulePanel();
38617
38805
  init_EventLog();
38806
+ init_puzzleObject();
38807
+ init_boardEntity();
38618
38808
  ENCOURAGEMENT_KEYS = [
38619
38809
  "puzzle.tryAgain1",
38620
38810
  "puzzle.tryAgain2",
@@ -38703,7 +38893,10 @@ var init_FeatureGridOrganism = __esm({
38703
38893
  );
38704
38894
  React84.useCallback(
38705
38895
  (feature) => {
38706
- eventBus.emit("UI:FEATURE_CLICK", { id: feature.id, href: feature.href ?? "" });
38896
+ eventBus.emit("UI:FEATURE_CLICK", {
38897
+ id: String(feature.id ?? ""),
38898
+ href: String(feature.href ?? "")
38899
+ });
38707
38900
  },
38708
38901
  [eventBus]
38709
38902
  );
@@ -38713,14 +38906,17 @@ var init_FeatureGridOrganism = __esm({
38713
38906
  if (error) {
38714
38907
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
38715
38908
  }
38716
- const featureCards = items.map((feature) => ({
38717
- icon: feature.icon,
38718
- title: feature.title,
38719
- description: feature.description,
38720
- href: feature.href,
38721
- linkLabel: feature.linkLabel,
38722
- variant: feature.href ? "interactive" : "bordered"
38723
- }));
38909
+ const featureCards = items.map((feature) => {
38910
+ const href = feature.href != null ? String(feature.href) : void 0;
38911
+ return {
38912
+ icon: feature.icon != null ? String(feature.icon) : void 0,
38913
+ title: String(feature.title ?? ""),
38914
+ description: String(feature.description ?? ""),
38915
+ href,
38916
+ linkLabel: feature.linkLabel != null ? String(feature.linkLabel) : void 0,
38917
+ variant: href ? "interactive" : "bordered"
38918
+ };
38919
+ });
38724
38920
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
38725
38921
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
38726
38922
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
@@ -40038,22 +40234,24 @@ var init_HeroOrganism = __esm({
40038
40234
  () => Array.isArray(entity) ? entity[0] : entity && typeof entity === "object" ? entity : void 0,
40039
40235
  [entity]
40040
40236
  );
40237
+ const primaryAction = resolved?.primaryAction;
40238
+ const secondaryAction = resolved?.secondaryAction;
40041
40239
  const handlePrimaryClick = React84.useCallback(() => {
40042
- if (resolved?.primaryAction) {
40240
+ if (primaryAction) {
40043
40241
  eventBus.emit("UI:CTA_PRIMARY", {
40044
- label: resolved.primaryAction.label,
40045
- href: resolved.primaryAction.href
40242
+ label: String(primaryAction.label ?? ""),
40243
+ href: String(primaryAction.href ?? "")
40046
40244
  });
40047
40245
  }
40048
- }, [eventBus, resolved]);
40246
+ }, [eventBus, primaryAction]);
40049
40247
  const handleSecondaryClick = React84.useCallback(() => {
40050
- if (resolved?.secondaryAction) {
40248
+ if (secondaryAction) {
40051
40249
  eventBus.emit("UI:CTA_SECONDARY", {
40052
- label: resolved.secondaryAction.label,
40053
- href: resolved.secondaryAction.href
40250
+ label: String(secondaryAction.label ?? ""),
40251
+ href: String(secondaryAction.href ?? "")
40054
40252
  });
40055
40253
  }
40056
- }, [eventBus, resolved]);
40254
+ }, [eventBus, secondaryAction]);
40057
40255
  if (isLoading) {
40058
40256
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
40059
40257
  }
@@ -40063,17 +40261,19 @@ var init_HeroOrganism = __esm({
40063
40261
  if (!resolved) {
40064
40262
  return null;
40065
40263
  }
40264
+ const imageRaw = resolved.image;
40265
+ const image = imageRaw ? { src: String(imageRaw.src ?? ""), alt: String(imageRaw.alt ?? "") } : void 0;
40066
40266
  return /* @__PURE__ */ jsxRuntime.jsxs(
40067
40267
  HeroSection,
40068
40268
  {
40069
- tag: resolved.tag,
40070
- title: resolved.title,
40071
- titleAccent: resolved.titleAccent,
40072
- subtitle: resolved.subtitle,
40073
- primaryAction: resolved.primaryAction ? { label: resolved.primaryAction.label, href: resolved.primaryAction.href } : void 0,
40074
- secondaryAction: resolved.secondaryAction ? { label: resolved.secondaryAction.label, href: resolved.secondaryAction.href } : void 0,
40075
- installCommand: resolved.installCommand,
40076
- image: resolved.image,
40269
+ tag: resolved.tag != null ? String(resolved.tag) : void 0,
40270
+ title: String(resolved.title ?? ""),
40271
+ titleAccent: resolved.titleAccent != null ? String(resolved.titleAccent) : void 0,
40272
+ subtitle: String(resolved.subtitle ?? ""),
40273
+ primaryAction: primaryAction ? { label: String(primaryAction.label ?? ""), href: String(primaryAction.href ?? "") } : void 0,
40274
+ secondaryAction: secondaryAction ? { label: String(secondaryAction.label ?? ""), href: String(secondaryAction.href ?? "") } : void 0,
40275
+ installCommand: resolved.installCommand != null ? String(resolved.installCommand) : void 0,
40276
+ image,
40077
40277
  imagePosition: resolved.imagePosition,
40078
40278
  background: resolved.background,
40079
40279
  className: cn(className),
@@ -40082,8 +40282,8 @@ var init_HeroOrganism = __esm({
40082
40282
  /* @__PURE__ */ jsxRuntime.jsx(
40083
40283
  _HeroClickInterceptor,
40084
40284
  {
40085
- hasPrimary: !!resolved.primaryAction,
40086
- hasSecondary: !!resolved.secondaryAction,
40285
+ hasPrimary: !!primaryAction,
40286
+ hasSecondary: !!secondaryAction,
40087
40287
  onPrimaryClick: handlePrimaryClick,
40088
40288
  onSecondaryClick: handleSecondaryClick
40089
40289
  }
@@ -40312,7 +40512,7 @@ function formatValue3(value, fieldName) {
40312
40512
  return String(value);
40313
40513
  }
40314
40514
  function formatFieldLabel2(fieldName) {
40315
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
40515
+ return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase()).replace(/Id$/, "").trim();
40316
40516
  }
40317
40517
  var STATUS_STYLES2, StatusBadge, ProgressIndicator, List3;
40318
40518
  var init_List = __esm({
@@ -41159,20 +41359,22 @@ function NegotiatorBoard({
41159
41359
  }) {
41160
41360
  const { emit } = useEventBus();
41161
41361
  const { t } = hooks.useTranslate();
41162
- const resolved = Array.isArray(entity) ? entity[0] : entity;
41362
+ const resolved = boardEntity(entity);
41163
41363
  const [history, setHistory] = React84.useState([]);
41164
41364
  const [headerError, setHeaderError] = React84.useState(false);
41165
41365
  const [showHint, setShowHint] = React84.useState(false);
41366
+ const totalRounds = num(resolved?.totalRounds);
41367
+ const targetScore = num(resolved?.targetScore);
41166
41368
  const currentRound = history.length;
41167
- const isComplete = currentRound >= (resolved?.totalRounds ?? 0);
41369
+ const isComplete = currentRound >= totalRounds;
41168
41370
  const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
41169
41371
  const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
41170
- const won = isComplete && playerTotal >= (resolved?.targetScore ?? 0);
41171
- const actions = resolved?.actions ?? [];
41172
- const payoffMatrix = resolved?.payoffMatrix ?? [];
41372
+ const won = isComplete && playerTotal >= targetScore;
41373
+ const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
41374
+ const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
41173
41375
  const handleAction = React84.useCallback((actionId) => {
41174
41376
  if (isComplete) return;
41175
- const opponentAction = getOpponentAction(resolved?.opponentStrategy ?? "random", actions, history);
41377
+ const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
41176
41378
  const payoff = payoffMatrix.find(
41177
41379
  (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
41178
41380
  );
@@ -41185,42 +41387,46 @@ function NegotiatorBoard({
41185
41387
  };
41186
41388
  const newHistory = [...history, result];
41187
41389
  setHistory(newHistory);
41188
- if (newHistory.length >= (resolved?.totalRounds ?? 0)) {
41390
+ if (newHistory.length >= totalRounds) {
41189
41391
  const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
41190
- if (total >= (resolved?.targetScore ?? 0)) {
41392
+ if (total >= targetScore) {
41191
41393
  emit(`UI:${completeEvent}`, { success: true, score: total });
41192
41394
  }
41193
- if (newHistory.length >= 3 && resolved?.hint) {
41395
+ if (newHistory.length >= 3 && str(resolved?.hint)) {
41194
41396
  setShowHint(true);
41195
41397
  }
41196
41398
  }
41197
- }, [isComplete, resolved, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41399
+ }, [isComplete, resolved, totalRounds, targetScore, actions, payoffMatrix, history, currentRound, completeEvent, emit]);
41198
41400
  const handleReset = () => {
41199
41401
  setHistory([]);
41200
41402
  setShowHint(false);
41201
41403
  };
41202
41404
  const getActionLabel = (id) => actions.find((a) => a.id === id)?.label ?? id;
41203
41405
  if (!resolved) return null;
41406
+ const theme = resolved.theme ?? void 0;
41407
+ const themeBackground = theme?.background;
41408
+ const headerImage = str(resolved.headerImage);
41409
+ const hint = str(resolved.hint);
41204
41410
  return /* @__PURE__ */ jsxRuntime.jsx(
41205
41411
  Box,
41206
41412
  {
41207
41413
  className,
41208
41414
  style: {
41209
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
41415
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
41210
41416
  backgroundSize: "cover",
41211
41417
  backgroundPosition: "center"
41212
41418
  },
41213
41419
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
41214
- 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,
41420
+ 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,
41215
41421
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
41216
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
41217
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description }),
41422
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
41423
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
41218
41424
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", children: [
41219
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(resolved.totalRounds) }) }),
41425
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(totalRounds) }) }),
41220
41426
  /* @__PURE__ */ jsxRuntime.jsxs(Badge, { size: "sm", children: [
41221
41427
  t("negotiator.target"),
41222
41428
  ": ",
41223
- resolved.targetScore
41429
+ targetScore
41224
41430
  ] })
41225
41431
  ] })
41226
41432
  ] }) }),
@@ -41269,16 +41475,16 @@ function NegotiatorBoard({
41269
41475
  ] }) }),
41270
41476
  isComplete && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
41271
41477
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.CheckCircle, size: "lg", className: won ? "text-success" : "text-error" }),
41272
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: won ? resolved.successMessage ?? t("negotiator.success") : resolved.failMessage ?? t("negotiator.failed") }),
41478
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "bold", children: won ? str(resolved.successMessage) || t("negotiator.success") : str(resolved.failMessage) || t("negotiator.failed") }),
41273
41479
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
41274
41480
  t("negotiator.finalScore"),
41275
41481
  ": ",
41276
41482
  playerTotal,
41277
41483
  "/",
41278
- resolved.targetScore
41484
+ targetScore
41279
41485
  ] })
41280
41486
  ] }) }),
41281
- 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 }) }),
41487
+ 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 }) }),
41282
41488
  isComplete && !won && /* @__PURE__ */ jsxRuntime.jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
41283
41489
  ] })
41284
41490
  }
@@ -41288,6 +41494,7 @@ var init_NegotiatorBoard = __esm({
41288
41494
  "components/game/organisms/puzzles/negotiator/NegotiatorBoard.tsx"() {
41289
41495
  init_atoms2();
41290
41496
  init_useEventBus();
41497
+ init_boardEntity();
41291
41498
  NegotiatorBoard.displayName = "NegotiatorBoard";
41292
41499
  }
41293
41500
  });
@@ -41323,13 +41530,13 @@ var init_PricingOrganism = __esm({
41323
41530
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
41324
41531
  }
41325
41532
  const plans = items.map((plan) => ({
41326
- name: plan.name,
41327
- price: plan.price,
41328
- description: plan.description,
41329
- features: plan.features,
41330
- action: { label: plan.actionLabel, href: plan.actionHref },
41331
- highlighted: plan.highlighted,
41332
- badge: plan.badge
41533
+ name: String(plan.name ?? ""),
41534
+ price: String(plan.price ?? ""),
41535
+ description: plan.description != null ? String(plan.description) : void 0,
41536
+ features: (plan.features ?? []).map((f3) => String(f3)),
41537
+ action: { label: String(plan.actionLabel ?? ""), href: String(plan.actionHref ?? "") },
41538
+ highlighted: Boolean(plan.highlighted),
41539
+ badge: plan.badge != null ? String(plan.badge) : void 0
41333
41540
  }));
41334
41541
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
41335
41542
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -43419,16 +43626,20 @@ function SequencerBoard({
43419
43626
  }) {
43420
43627
  const { emit } = useEventBus();
43421
43628
  const { t } = hooks.useTranslate();
43422
- const resolved = Array.isArray(entity) ? entity[0] : entity;
43629
+ const resolved = boardEntity(entity);
43630
+ const maxSlots = num(resolved?.maxSlots);
43631
+ const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
43632
+ const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
43633
+ const allowDuplicates = resolved?.allowDuplicates !== false;
43423
43634
  const [headerError, setHeaderError] = React84.useState(false);
43424
43635
  const [slots, setSlots] = React84.useState(
43425
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0)
43636
+ () => Array.from({ length: maxSlots }, () => void 0)
43426
43637
  );
43427
43638
  const [playState, setPlayState] = React84.useState("idle");
43428
43639
  const [currentStep, setCurrentStep] = React84.useState(-1);
43429
43640
  const [attempts, setAttempts] = React84.useState(0);
43430
43641
  const [slotFeedback, setSlotFeedback] = React84.useState(
43431
- () => Array.from({ length: resolved?.maxSlots ?? 0 }, () => null)
43642
+ () => Array.from({ length: maxSlots }, () => null)
43432
43643
  );
43433
43644
  const timerRef = React84.useRef(null);
43434
43645
  React84.useEffect(() => () => {
@@ -43462,17 +43673,17 @@ function SequencerBoard({
43462
43673
  }, [emit]);
43463
43674
  const handleReset = React84.useCallback(() => {
43464
43675
  if (timerRef.current) clearTimeout(timerRef.current);
43465
- setSlots(Array.from({ length: resolved?.maxSlots ?? 0 }, () => void 0));
43676
+ setSlots(Array.from({ length: maxSlots }, () => void 0));
43466
43677
  setPlayState("idle");
43467
43678
  setCurrentStep(-1);
43468
43679
  setAttempts(0);
43469
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
43470
- }, [resolved?.maxSlots]);
43680
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
43681
+ }, [maxSlots]);
43471
43682
  const filledSlots = slots.filter((s) => !!s);
43472
43683
  const canPlay = filledSlots.length > 0 && playState === "idle";
43473
43684
  const handlePlay = React84.useCallback(() => {
43474
43685
  if (!canPlay) return;
43475
- setSlotFeedback(Array.from({ length: resolved?.maxSlots ?? 0 }, () => null));
43686
+ setSlotFeedback(Array.from({ length: maxSlots }, () => null));
43476
43687
  emit("UI:PLAY_SOUND", { key: "confirm" });
43477
43688
  const sequence = slots.map((s) => s?.id || "");
43478
43689
  if (playEvent) {
@@ -43483,10 +43694,10 @@ function SequencerBoard({
43483
43694
  let step = 0;
43484
43695
  const advance = () => {
43485
43696
  step++;
43486
- if (step >= (resolved?.maxSlots ?? 0)) {
43697
+ if (step >= maxSlots) {
43487
43698
  const playerSeq = slots.map((s) => s?.id);
43488
43699
  const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
43489
- const success = (resolved?.solutions ?? []).some(
43700
+ const success = solutions.some(
43490
43701
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
43491
43702
  );
43492
43703
  if (success) {
@@ -43498,7 +43709,7 @@ function SequencerBoard({
43498
43709
  }
43499
43710
  } else {
43500
43711
  setAttempts((prev) => prev + 1);
43501
- const feedback = computeSlotFeedback(playerSeq, resolved?.solutions ?? []);
43712
+ const feedback = computeSlotFeedback(playerSeq, solutions);
43502
43713
  setSlotFeedback(feedback);
43503
43714
  setPlayState("idle");
43504
43715
  setCurrentStep(-1);
@@ -43516,10 +43727,10 @@ function SequencerBoard({
43516
43727
  }
43517
43728
  };
43518
43729
  timerRef.current = setTimeout(advance, stepDurationMs);
43519
- }, [canPlay, slots, resolved?.maxSlots, resolved?.solutions, stepDurationMs, playEvent, completeEvent, emit]);
43730
+ }, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, emit]);
43520
43731
  const machine = {
43521
- name: resolved?.title ?? "",
43522
- description: resolved?.description ?? "",
43732
+ name: str(resolved?.title),
43733
+ description: str(resolved?.description),
43523
43734
  states: slots.map((s, i) => stepLabel(s, i)),
43524
43735
  currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
43525
43736
  transitions: slots.slice(0, -1).map((s, i) => ({
@@ -43528,37 +43739,41 @@ function SequencerBoard({
43528
43739
  event: "NEXT"
43529
43740
  }))
43530
43741
  };
43531
- const usedIds = resolved?.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
43532
- const showHint = attempts >= 3 && !!resolved?.hint;
43742
+ const usedIds = !allowDuplicates ? slots.filter(Boolean).map((s) => s?.id || "") : [];
43743
+ const hint = str(resolved?.hint);
43744
+ const showHint = attempts >= 3 && !!hint;
43533
43745
  const hasFeedback = slotFeedback.some((f3) => f3 !== null);
43534
43746
  const correctCount = slotFeedback.filter((f3) => f3 === "correct").length;
43535
43747
  const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
43536
43748
  if (!resolved) return null;
43749
+ const theme = resolved.theme ?? void 0;
43750
+ const themeBackground = theme?.background;
43751
+ const headerImage = str(resolved.headerImage);
43537
43752
  return /* @__PURE__ */ jsxRuntime.jsxs(
43538
43753
  VStack,
43539
43754
  {
43540
43755
  className: cn("p-4 gap-6", className),
43541
43756
  style: {
43542
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
43757
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
43543
43758
  backgroundSize: "cover",
43544
43759
  backgroundPosition: "center"
43545
43760
  },
43546
43761
  children: [
43547
- 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,
43762
+ 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,
43548
43763
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43549
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
43550
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description })
43764
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
43765
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) })
43551
43766
  ] }),
43552
43767
  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: [
43553
43768
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
43554
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
43769
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
43555
43770
  ] }) }),
43556
43771
  filledSlots.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
43557
43772
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
43558
43773
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
43559
43774
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
43560
43775
  hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
43561
- `${correctCount}/${resolved.maxSlots} `,
43776
+ `${correctCount}/${maxSlots} `,
43562
43777
  "\u2705"
43563
43778
  ] })
43564
43779
  ] }),
@@ -43566,7 +43781,7 @@ function SequencerBoard({
43566
43781
  SequenceBar,
43567
43782
  {
43568
43783
  slots,
43569
- maxSlots: resolved.maxSlots,
43784
+ maxSlots,
43570
43785
  onSlotDrop: handleSlotDrop,
43571
43786
  onSlotRemove: handleSlotRemove,
43572
43787
  playing: playState === "playing",
@@ -43580,15 +43795,15 @@ function SequencerBoard({
43580
43795
  playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
43581
43796
  ActionPalette,
43582
43797
  {
43583
- actions: resolved.availableActions,
43798
+ actions: availableActions,
43584
43799
  usedActionIds: usedIds,
43585
- allowDuplicates: resolved.allowDuplicates !== false,
43800
+ allowDuplicates,
43586
43801
  categoryColors,
43587
43802
  label: t("sequencer.dragActions")
43588
43803
  }
43589
43804
  ),
43590
43805
  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) }) }),
43591
- 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") }) }),
43806
+ 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") }) }),
43592
43807
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
43593
43808
  /* @__PURE__ */ jsxRuntime.jsx(
43594
43809
  Button,
@@ -43612,6 +43827,7 @@ var init_SequencerBoard = __esm({
43612
43827
  init_cn();
43613
43828
  init_useEventBus();
43614
43829
  init_TraitStateViewer();
43830
+ init_boardEntity();
43615
43831
  init_SequenceBar();
43616
43832
  init_ActionPalette();
43617
43833
  ENCOURAGEMENT_KEYS2 = [
@@ -43661,18 +43877,21 @@ var init_ShowcaseOrganism = __esm({
43661
43877
  heading && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", align: "center", children: heading }),
43662
43878
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", color: "muted", align: "center", className: "max-w-2xl", children: subtitle })
43663
43879
  ] }),
43664
- /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
43665
- ShowcaseCard,
43666
- {
43667
- title: item.title,
43668
- description: item.description,
43669
- image: item.image,
43670
- href: item.href,
43671
- badge: item.badge,
43672
- accentColor: item.accentColor
43673
- },
43674
- item.id
43675
- )) })
43880
+ /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: columns, gap: "lg", children: items.map((item) => {
43881
+ const imageRaw = item.image;
43882
+ return /* @__PURE__ */ jsxRuntime.jsx(
43883
+ ShowcaseCard,
43884
+ {
43885
+ title: String(item.title ?? ""),
43886
+ description: item.description != null ? String(item.description) : void 0,
43887
+ image: { src: String(imageRaw?.src ?? ""), alt: String(imageRaw?.alt ?? "") },
43888
+ href: item.href != null ? String(item.href) : void 0,
43889
+ badge: item.badge != null ? String(item.badge) : void 0,
43890
+ accentColor: item.accentColor != null ? String(item.accentColor) : void 0
43891
+ },
43892
+ String(item.id ?? "")
43893
+ );
43894
+ }) })
43676
43895
  ] });
43677
43896
  };
43678
43897
  ShowcaseOrganism.displayName = "ShowcaseOrganism";
@@ -44040,8 +44259,8 @@ function SimulatorBoard({
44040
44259
  }) {
44041
44260
  const { emit } = useEventBus();
44042
44261
  const { t } = hooks.useTranslate();
44043
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44044
- const parameters = resolved?.parameters ?? [];
44262
+ const resolved = boardEntity(entity);
44263
+ const parameters = Array.isArray(resolved?.parameters) ? resolved.parameters : [];
44045
44264
  const [values, setValues] = React84.useState(() => {
44046
44265
  const init = {};
44047
44266
  for (const p2 of parameters) {
@@ -44055,15 +44274,15 @@ function SimulatorBoard({
44055
44274
  const [showHint, setShowHint] = React84.useState(false);
44056
44275
  const computeOutput = React84.useCallback((params) => {
44057
44276
  try {
44058
- const fn = new Function("params", `return (${resolved?.computeExpression})`);
44277
+ const fn = new Function("params", `return (${str(resolved?.computeExpression)})`);
44059
44278
  return fn(params);
44060
44279
  } catch {
44061
44280
  return 0;
44062
44281
  }
44063
44282
  }, [resolved?.computeExpression]);
44064
44283
  const output = React84.useMemo(() => computeOutput(values) ?? 0, [computeOutput, values]);
44065
- const targetValue = resolved?.targetValue ?? 0;
44066
- const targetTolerance = resolved?.targetTolerance ?? 0;
44284
+ const targetValue = num(resolved?.targetValue);
44285
+ const targetTolerance = num(resolved?.targetTolerance);
44067
44286
  const isCorrect = Math.abs(output - targetValue) <= targetTolerance;
44068
44287
  const handleParameterChange = (id, value) => {
44069
44288
  if (submitted) return;
@@ -44078,7 +44297,7 @@ function SimulatorBoard({
44078
44297
  };
44079
44298
  const handleReset = () => {
44080
44299
  setSubmitted(false);
44081
- if (attempts >= 2 && resolved?.hint) {
44300
+ if (attempts >= 2 && str(resolved?.hint)) {
44082
44301
  setShowHint(true);
44083
44302
  }
44084
44303
  };
@@ -44093,20 +44312,26 @@ function SimulatorBoard({
44093
44312
  setShowHint(false);
44094
44313
  };
44095
44314
  if (!resolved) return null;
44315
+ const theme = resolved.theme ?? void 0;
44316
+ const themeBackground = theme?.background;
44317
+ const headerImage = str(resolved.headerImage);
44318
+ const hint = str(resolved.hint);
44319
+ const outputLabel = str(resolved.outputLabel);
44320
+ const outputUnit = str(resolved.outputUnit);
44096
44321
  return /* @__PURE__ */ jsxRuntime.jsx(
44097
44322
  Box,
44098
44323
  {
44099
44324
  className,
44100
44325
  style: {
44101
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
44326
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
44102
44327
  backgroundSize: "cover",
44103
44328
  backgroundPosition: "center"
44104
44329
  },
44105
44330
  children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: "p-4", children: [
44106
- 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,
44331
+ 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,
44107
44332
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
44108
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: resolved.title }),
44109
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: resolved.description })
44333
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) }),
44334
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) })
44110
44335
  ] }) }),
44111
44336
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "md", children: [
44112
44337
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: t("simulator.parameters") }),
@@ -44147,28 +44372,28 @@ function SimulatorBoard({
44147
44372
  ] }, param.id))
44148
44373
  ] }) }),
44149
44374
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", children: [
44150
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: resolved.outputLabel }),
44375
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-muted-foreground", children: outputLabel }),
44151
44376
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", weight: "bold", children: [
44152
44377
  output.toFixed(2),
44153
44378
  " ",
44154
- resolved.outputUnit
44379
+ outputUnit
44155
44380
  ] }),
44156
44381
  submitted && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
44157
44382
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: isCorrect ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "sm", className: isCorrect ? "text-success" : "text-error" }),
44158
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: isCorrect ? "text-success" : "text-error", children: isCorrect ? resolved.successMessage ?? t("simulator.correct") : resolved.failMessage ?? t("simulator.incorrect") })
44383
+ /* @__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") })
44159
44384
  ] }),
44160
44385
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44161
44386
  t("simulator.target"),
44162
44387
  ": ",
44163
44388
  targetValue,
44164
44389
  " ",
44165
- resolved.outputUnit,
44390
+ outputUnit,
44166
44391
  " (\xB1",
44167
44392
  targetTolerance,
44168
44393
  ")"
44169
44394
  ] })
44170
44395
  ] }) }),
44171
- 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 }) }),
44396
+ showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
44172
44397
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
44173
44398
  !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
44174
44399
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Play, size: "sm" }),
@@ -44187,6 +44412,7 @@ var init_SimulatorBoard = __esm({
44187
44412
  "components/game/organisms/puzzles/simulator/SimulatorBoard.tsx"() {
44188
44413
  init_atoms2();
44189
44414
  init_useEventBus();
44415
+ init_boardEntity();
44190
44416
  SimulatorBoard.displayName = "SimulatorBoard";
44191
44417
  }
44192
44418
  });
@@ -44612,22 +44838,25 @@ function VariablePanel({
44612
44838
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
44613
44839
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
44614
44840
  variables.map((v) => {
44615
- const max = v.max ?? 100;
44616
- const min = v.min ?? 0;
44617
- const pct = Math.round((v.value - min) / (max - min) * 100);
44841
+ const name = v.name == null ? "" : String(v.name);
44842
+ const value = numField(v.value);
44843
+ const max = numField(v.max, 100);
44844
+ const min = numField(v.min, 0);
44845
+ const unit = v.unit == null ? "" : String(v.unit);
44846
+ const pct = Math.round((value - min) / (max - min) * 100);
44618
44847
  const isHigh = pct > 80;
44619
44848
  const isLow = pct < 20;
44620
44849
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "none", children: [
44621
44850
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
44622
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: v.name }),
44851
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: name }),
44623
44852
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: cn(
44624
44853
  isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
44625
44854
  ), children: [
44626
- v.value,
44627
- v.unit || "",
44855
+ value,
44856
+ unit,
44628
44857
  " / ",
44629
44858
  max,
44630
- v.unit || ""
44859
+ unit
44631
44860
  ] })
44632
44861
  ] }),
44633
44862
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -44638,14 +44867,19 @@ function VariablePanel({
44638
44867
  size: "sm"
44639
44868
  }
44640
44869
  )
44641
- ] }, v.name);
44870
+ ] }, name);
44642
44871
  })
44643
44872
  ] });
44644
44873
  }
44874
+ var numField;
44645
44875
  var init_VariablePanel = __esm({
44646
44876
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
44647
44877
  init_atoms2();
44648
44878
  init_cn();
44879
+ numField = (v, fallback = 0) => {
44880
+ const n = Number(v);
44881
+ return Number.isFinite(n) ? n : fallback;
44882
+ };
44649
44883
  VariablePanel.displayName = "VariablePanel";
44650
44884
  }
44651
44885
  });
@@ -44672,14 +44906,21 @@ function StateArchitectBoard({
44672
44906
  }) {
44673
44907
  const { emit } = useEventBus();
44674
44908
  const { t } = hooks.useTranslate();
44675
- const resolved = Array.isArray(entity) ? entity[0] : entity;
44676
- const [transitions, setTransitions] = React84.useState(resolved?.transitions ?? []);
44909
+ const resolved = boardEntity(entity);
44910
+ const entityStates = Array.isArray(resolved?.states) ? resolved.states : [];
44911
+ const initialState = str(resolved?.initialState);
44912
+ const entityName = str(resolved?.entityName);
44913
+ const availableEvents = Array.isArray(resolved?.availableEvents) ? resolved.availableEvents : [];
44914
+ const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
44915
+ const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
44916
+ const entityVariables = rows(resolved?.variables);
44917
+ const [transitions, setTransitions] = React84.useState(entityTransitions);
44677
44918
  const [headerError, setHeaderError] = React84.useState(false);
44678
44919
  const [playState, setPlayState] = React84.useState("editing");
44679
- const [currentState, setCurrentState] = React84.useState(resolved?.initialState ?? "");
44920
+ const [currentState, setCurrentState] = React84.useState(initialState);
44680
44921
  const [selectedState, setSelectedState] = React84.useState(null);
44681
44922
  const [testResults, setTestResults] = React84.useState([]);
44682
- const [variables, setVariables] = React84.useState(resolved?.variables ?? []);
44923
+ const [variables, setVariables] = React84.useState(() => [...entityVariables]);
44683
44924
  const [attempts, setAttempts] = React84.useState(0);
44684
44925
  const timerRef = React84.useRef(null);
44685
44926
  const [addingFrom, setAddingFrom] = React84.useState(null);
@@ -44688,12 +44929,12 @@ function StateArchitectBoard({
44688
44929
  }, []);
44689
44930
  const GRAPH_W = 500;
44690
44931
  const GRAPH_H = 400;
44691
- const positions = React84.useMemo(() => layoutStates(resolved?.states ?? [], GRAPH_W, GRAPH_H), [resolved?.states]);
44932
+ const positions = React84.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
44692
44933
  const handleStateClick = React84.useCallback((state) => {
44693
44934
  if (playState !== "editing") return;
44694
44935
  if (addingFrom) {
44695
44936
  if (addingFrom !== state) {
44696
- const event = resolved?.availableEvents[0] || "EVENT";
44937
+ const event = availableEvents[0] || "EVENT";
44697
44938
  const newTrans = {
44698
44939
  id: `t-${nextTransId++}`,
44699
44940
  from: addingFrom,
@@ -44706,7 +44947,7 @@ function StateArchitectBoard({
44706
44947
  } else {
44707
44948
  setSelectedState(state);
44708
44949
  }
44709
- }, [playState, addingFrom, resolved?.availableEvents]);
44950
+ }, [playState, addingFrom, availableEvents]);
44710
44951
  const handleStartAddTransition = React84.useCallback(() => {
44711
44952
  if (!selectedState) return;
44712
44953
  setAddingFrom(selectedState);
@@ -44715,9 +44956,9 @@ function StateArchitectBoard({
44715
44956
  setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
44716
44957
  }, []);
44717
44958
  const machine = React84.useMemo(() => ({
44718
- name: resolved?.entityName ?? "",
44719
- description: resolved?.description ?? "",
44720
- states: resolved?.states ?? [],
44959
+ name: entityName,
44960
+ description: str(resolved?.description),
44961
+ states: entityStates,
44721
44962
  currentState,
44722
44963
  transitions: transitions.map((t2) => ({
44723
44964
  from: t2.from,
@@ -44725,7 +44966,7 @@ function StateArchitectBoard({
44725
44966
  event: t2.event,
44726
44967
  guardHint: t2.guardHint
44727
44968
  }))
44728
- }), [resolved, currentState, transitions]);
44969
+ }), [entityName, resolved, entityStates, currentState, transitions]);
44729
44970
  const handleTest = React84.useCallback(() => {
44730
44971
  if (playState !== "editing") return;
44731
44972
  if (testEvent) emit(`UI:${testEvent}`, {});
@@ -44734,7 +44975,7 @@ function StateArchitectBoard({
44734
44975
  const results = [];
44735
44976
  let testIdx = 0;
44736
44977
  const runNextTest = () => {
44737
- if (testIdx >= (resolved?.testCases.length ?? 0)) {
44978
+ if (testIdx >= testCases.length) {
44738
44979
  const allPassed = results.every((r) => r.passed);
44739
44980
  setPlayState(allPassed ? "success" : "fail");
44740
44981
  setTestResults(results);
@@ -44749,9 +44990,9 @@ function StateArchitectBoard({
44749
44990
  }
44750
44991
  return;
44751
44992
  }
44752
- const testCase = resolved?.testCases[testIdx];
44993
+ const testCase = testCases[testIdx];
44753
44994
  if (!testCase) return;
44754
- let state = resolved.initialState;
44995
+ let state = initialState;
44755
44996
  for (const event of testCase.events) {
44756
44997
  const trans = transitions.find((t2) => t2.from === state && t2.event === event);
44757
44998
  if (trans) {
@@ -44769,53 +45010,57 @@ function StateArchitectBoard({
44769
45010
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
44770
45011
  };
44771
45012
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
44772
- }, [playState, transitions, resolved, stepDurationMs, testEvent, completeEvent, emit]);
45013
+ }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
44773
45014
  const handleTryAgain = React84.useCallback(() => {
44774
45015
  if (timerRef.current) clearTimeout(timerRef.current);
44775
45016
  setPlayState("editing");
44776
- setCurrentState(resolved?.initialState ?? "");
45017
+ setCurrentState(initialState);
44777
45018
  setTestResults([]);
44778
- }, [resolved?.initialState]);
45019
+ }, [initialState]);
44779
45020
  const handleReset = React84.useCallback(() => {
44780
45021
  if (timerRef.current) clearTimeout(timerRef.current);
44781
- setTransitions(resolved?.transitions ?? []);
45022
+ setTransitions(entityTransitions);
44782
45023
  setPlayState("editing");
44783
- setCurrentState(resolved?.initialState ?? "");
45024
+ setCurrentState(initialState);
44784
45025
  setTestResults([]);
44785
- setVariables(resolved?.variables ?? []);
45026
+ setVariables([...entityVariables]);
44786
45027
  setSelectedState(null);
44787
45028
  setAddingFrom(null);
44788
45029
  setAttempts(0);
44789
- }, [resolved]);
45030
+ }, [entityTransitions, initialState, entityVariables]);
44790
45031
  const codeData = React84.useMemo(() => ({
44791
- name: resolved?.entityName ?? "",
44792
- states: resolved?.states ?? [],
44793
- initialState: resolved?.initialState ?? "",
45032
+ name: entityName,
45033
+ states: entityStates,
45034
+ initialState,
44794
45035
  transitions: transitions.map((t2) => ({
44795
45036
  from: t2.from,
44796
45037
  to: t2.to,
44797
45038
  event: t2.event,
44798
45039
  ...t2.guardHint ? { guard: t2.guardHint } : {}
44799
45040
  }))
44800
- }), [resolved, transitions]);
45041
+ }), [entityName, entityStates, initialState, transitions]);
44801
45042
  if (!resolved) return null;
45043
+ const theme = resolved.theme ?? void 0;
45044
+ const themeBackground = theme?.background;
45045
+ const headerImage = str(resolved.headerImage);
45046
+ const hint = str(resolved.hint);
44802
45047
  return /* @__PURE__ */ jsxRuntime.jsxs(
44803
45048
  VStack,
44804
45049
  {
44805
45050
  className: cn("p-4 gap-6", className),
44806
45051
  style: {
44807
- backgroundImage: resolved.theme?.background ? `url(${resolved.theme.background})` : void 0,
45052
+ backgroundImage: themeBackground ? `url(${themeBackground})` : void 0,
44808
45053
  backgroundSize: "cover",
44809
45054
  backgroundPosition: "center"
44810
45055
  },
44811
45056
  children: [
44812
- 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,
45057
+ 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,
44813
45058
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
44814
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: resolved.title }),
44815
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: resolved.description }),
45059
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", className: "text-foreground", children: str(resolved.title) }),
45060
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: str(resolved.description) }),
44816
45061
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
44817
45062
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
44818
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: resolved.hint })
45063
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-foreground", children: hint })
44819
45064
  ] })
44820
45065
  ] }),
44821
45066
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
@@ -44863,14 +45108,14 @@ function StateArchitectBoard({
44863
45108
  ]
44864
45109
  }
44865
45110
  ),
44866
- resolved.states.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
45111
+ entityStates.map((state) => /* @__PURE__ */ jsxRuntime.jsx(
44867
45112
  StateNode2,
44868
45113
  {
44869
45114
  name: state,
44870
45115
  position: positions[state],
44871
45116
  isCurrent: state === currentState,
44872
45117
  isSelected: state === selectedState,
44873
- isInitial: state === resolved.initialState,
45118
+ isInitial: state === initialState,
44874
45119
  onClick: () => handleStateClick(state)
44875
45120
  },
44876
45121
  state
@@ -44917,7 +45162,7 @@ function StateArchitectBoard({
44917
45162
  /* @__PURE__ */ jsxRuntime.jsx(
44918
45163
  VariablePanel,
44919
45164
  {
44920
- entityName: resolved.entityName,
45165
+ entityName,
44921
45166
  variables
44922
45167
  }
44923
45168
  ),
@@ -44932,12 +45177,12 @@ function StateArchitectBoard({
44932
45177
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(CodeView, { data: codeData, label: "View Code" })
44933
45178
  ] })
44934
45179
  ] }),
44935
- 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") }) }),
45180
+ 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") }) }),
44936
45181
  playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
44937
45182
  /* @__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]) }) }),
44938
- 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: [
45183
+ 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: [
44939
45184
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
44940
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: resolved.hint })
45185
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
44941
45186
  ] }) })
44942
45187
  ] }),
44943
45188
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
@@ -44967,6 +45212,7 @@ var init_StateArchitectBoard = __esm({
44967
45212
  init_TransitionArrow();
44968
45213
  init_VariablePanel();
44969
45214
  init_CodeView();
45215
+ init_boardEntity();
44970
45216
  ENCOURAGEMENT_KEYS3 = [
44971
45217
  "puzzle.tryAgain1",
44972
45218
  "puzzle.tryAgain2",
@@ -45003,8 +45249,8 @@ var init_StatsOrganism = __esm({
45003
45249
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45004
45250
  }
45005
45251
  const stats = items.map((item) => ({
45006
- value: item.value,
45007
- label: item.label
45252
+ value: String(item.value ?? ""),
45253
+ label: String(item.label ?? "")
45008
45254
  }));
45009
45255
  return /* @__PURE__ */ jsxRuntime.jsx(
45010
45256
  StatsGrid,
@@ -45050,10 +45296,10 @@ var init_StepFlowOrganism = __esm({
45050
45296
  return /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error.message, className });
45051
45297
  }
45052
45298
  const steps = items.map((item) => ({
45053
- number: item.number,
45054
- title: item.title,
45055
- description: item.description,
45056
- icon: item.icon
45299
+ number: item.number != null ? Number(item.number) : void 0,
45300
+ title: String(item.title ?? ""),
45301
+ description: String(item.description ?? ""),
45302
+ icon: item.icon != null ? String(item.icon) : void 0
45057
45303
  }));
45058
45304
  return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "lg", className: cn("w-full", className), children: [
45059
45305
  (heading || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", align: "center", className: "w-full", children: [
@@ -45226,13 +45472,13 @@ var init_TeamOrganism = __esm({
45226
45472
  /* @__PURE__ */ jsxRuntime.jsx(SimpleGrid, { cols: cols > 0 ? cols : 1, gap: "lg", children: items.map((member) => /* @__PURE__ */ jsxRuntime.jsx(
45227
45473
  TeamCard,
45228
45474
  {
45229
- name: member.name,
45230
- nameAr: member.nameAr,
45231
- role: member.role,
45232
- bio: member.bio,
45233
- avatar: member.avatar
45475
+ name: String(member.name ?? ""),
45476
+ nameAr: member.nameAr != null ? String(member.nameAr) : void 0,
45477
+ role: String(member.role ?? ""),
45478
+ bio: String(member.bio ?? ""),
45479
+ avatar: member.avatar != null ? String(member.avatar) : void 0
45234
45480
  },
45235
- member.id
45481
+ String(member.id ?? "")
45236
45482
  )) })
45237
45483
  ] });
45238
45484
  };
@@ -45469,8 +45715,8 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45469
45715
  const [turn, setTurn] = React84.useState(1);
45470
45716
  const [gameResult, setGameResult] = React84.useState(null);
45471
45717
  const checkGameEnd = React84.useCallback((currentUnits) => {
45472
- const pa = currentUnits.filter((u) => u.team === "player" && u.health > 0);
45473
- const ea = currentUnits.filter((u) => u.team === "enemy" && u.health > 0);
45718
+ const pa = currentUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
45719
+ const ea = currentUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
45474
45720
  if (pa.length === 0) {
45475
45721
  setGameResult("defeat");
45476
45722
  setPhase("game_over");
@@ -45488,34 +45734,36 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45488
45734
  }
45489
45735
  }, [onGameEnd, gameEndEvent, eventBus]);
45490
45736
  const handleUnitClick = React84.useCallback((unitId) => {
45491
- const unit = units.find((u) => u.id === unitId);
45737
+ const unit = units.find((u) => str(u.id) === unitId);
45492
45738
  if (!unit) return;
45493
45739
  if (unitClickEvent) {
45494
45740
  eventBus.emit(`UI:${unitClickEvent}`, { unitId });
45495
45741
  }
45496
45742
  if (phase === "observation" || phase === "selection") {
45497
- if (unit.team === "player") {
45743
+ if (unitTeam(unit) === "player") {
45498
45744
  setSelectedUnitId(unitId);
45499
45745
  setPhase("movement");
45500
45746
  }
45501
45747
  } else if (phase === "action") {
45502
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
45748
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45503
45749
  if (!selectedUnit) return;
45504
- if (unit.team === "enemy") {
45505
- const dx = Math.abs(unit.position.x - selectedUnit.position.x);
45506
- const dy = Math.abs(unit.position.y - selectedUnit.position.y);
45750
+ if (unitTeam(unit) === "enemy") {
45751
+ const up = unitPosition(unit);
45752
+ const sp = unitPosition(selectedUnit);
45753
+ const dx = Math.abs(up.x - sp.x);
45754
+ const dy = Math.abs(up.y - sp.y);
45507
45755
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
45508
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
45509
- const newHealth = Math.max(0, unit.health - damage);
45756
+ const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, num(selectedUnit.attack) - num(unit.defense));
45757
+ const newHealth = Math.max(0, unitHealth(unit) - damage);
45510
45758
  const updatedUnits = units.map(
45511
- (u) => u.id === unit.id ? { ...u, health: newHealth } : u
45759
+ (u) => str(u.id) === str(unit.id) ? { ...u, health: newHealth } : u
45512
45760
  );
45513
45761
  setUnits(updatedUnits);
45514
45762
  onAttack?.(selectedUnit, unit, damage);
45515
45763
  if (attackEvent) {
45516
45764
  eventBus.emit(`UI:${attackEvent}`, {
45517
- attackerId: selectedUnit.id,
45518
- targetId: unit.id,
45765
+ attackerId: str(selectedUnit.id),
45766
+ targetId: str(unit.id),
45519
45767
  damage
45520
45768
  });
45521
45769
  }
@@ -45532,16 +45780,20 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
45532
45780
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
45533
45781
  }
45534
45782
  if (phase === "movement" && selectedUnitId) {
45535
- const selectedUnit = units.find((u) => u.id === selectedUnitId);
45783
+ const selectedUnit = units.find((u) => str(u.id) === selectedUnitId);
45536
45784
  if (!selectedUnit) return;
45537
- const dx = Math.abs(x - selectedUnit.position.x);
45538
- const dy = Math.abs(y - selectedUnit.position.y);
45785
+ const sp = unitPosition(selectedUnit);
45786
+ const dx = Math.abs(x - sp.x);
45787
+ const dy = Math.abs(y - sp.y);
45539
45788
  const dist = dx + dy;
45540
- if (dist > 0 && dist <= selectedUnit.movement) {
45541
- if (!units.some((u) => u.position.x === x && u.position.y === y && u.health > 0)) {
45789
+ if (dist > 0 && dist <= num(selectedUnit.movement)) {
45790
+ if (!units.some((u) => {
45791
+ const p2 = unitPosition(u);
45792
+ return p2.x === x && p2.y === y && unitHealth(u) > 0;
45793
+ })) {
45542
45794
  setUnits(
45543
45795
  (prev) => prev.map(
45544
- (u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u
45796
+ (u) => str(u.id) === selectedUnitId ? { ...u, position: { x, y } } : u
45545
45797
  )
45546
45798
  );
45547
45799
  setPhase("action");
@@ -45584,12 +45836,13 @@ var init_useBattleState = __esm({
45584
45836
  "components/game/organisms/hooks/useBattleState.ts"() {
45585
45837
  "use client";
45586
45838
  init_useEventBus();
45839
+ init_boardEntity();
45587
45840
  }
45588
45841
  });
45589
45842
  function UncontrolledBattleBoard({ entity, ...rest }) {
45590
- const resolved = Array.isArray(entity) ? entity[0] : entity;
45843
+ const resolved = boardEntity(entity);
45591
45844
  const battleState = useBattleState(
45592
- resolved?.initialUnits ?? [],
45845
+ rows(resolved?.initialUnits),
45593
45846
  {
45594
45847
  tileClickEvent: rest.tileClickEvent,
45595
45848
  unitClickEvent: rest.unitClickEvent,
@@ -45625,10 +45878,23 @@ function UncontrolledBattleBoard({ entity, ...rest }) {
45625
45878
  var init_UncontrolledBattleBoard = __esm({
45626
45879
  "components/game/organisms/UncontrolledBattleBoard.tsx"() {
45627
45880
  init_BattleBoard();
45881
+ init_boardEntity();
45628
45882
  init_useBattleState();
45629
45883
  UncontrolledBattleBoard.displayName = "UncontrolledBattleBoard";
45630
45884
  }
45631
45885
  });
45886
+ function heroPosition(h) {
45887
+ return vec2(h.position);
45888
+ }
45889
+ function heroOwner(h) {
45890
+ return str(h.owner);
45891
+ }
45892
+ function heroMovement(h) {
45893
+ return num(h.movement);
45894
+ }
45895
+ function hexPassable(h) {
45896
+ return h.passable !== false;
45897
+ }
45632
45898
  function defaultIsInRange(from, to, range) {
45633
45899
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
45634
45900
  }
@@ -45659,36 +45925,36 @@ function WorldMapBoard({
45659
45925
  className
45660
45926
  }) {
45661
45927
  const eventBus = useEventBus();
45662
- const resolved = Array.isArray(entity) ? entity[0] : entity;
45663
- const hexes = resolved?.hexes ?? [];
45664
- const heroes = resolved?.heroes ?? [];
45665
- const features = resolved?.features ?? [];
45666
- const selectedHeroId = resolved?.selectedHeroId;
45928
+ const resolved = boardEntity(entity);
45929
+ const hexes = rows(resolved?.hexes);
45930
+ const heroes = rows(resolved?.heroes);
45931
+ const features = Array.isArray(resolved?.features) ? resolved.features : [];
45932
+ const selectedHeroId = resolved?.selectedHeroId ?? null;
45667
45933
  const assetManifest = resolved?.assetManifest;
45668
45934
  const backgroundImage = resolved?.backgroundImage;
45669
45935
  const [hoveredTile, setHoveredTile] = React84.useState(null);
45670
45936
  const selectedHero = React84.useMemo(
45671
- () => heroes.find((h) => h.id === selectedHeroId) ?? null,
45937
+ () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
45672
45938
  [heroes, selectedHeroId]
45673
45939
  );
45674
45940
  const tiles = React84.useMemo(
45675
45941
  () => hexes.map((hex) => ({
45676
- x: hex.x,
45677
- y: hex.y,
45678
- terrain: hex.terrain,
45679
- terrainSprite: hex.terrainSprite
45942
+ x: num(hex.x),
45943
+ y: num(hex.y),
45944
+ terrain: str(hex.terrain),
45945
+ terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
45680
45946
  })),
45681
45947
  [hexes]
45682
45948
  );
45683
45949
  const baseUnits = React84.useMemo(
45684
45950
  () => heroes.map((hero) => ({
45685
- id: hero.id,
45686
- position: hero.position,
45687
- name: hero.name,
45688
- team: hero.owner === "enemy" ? "enemy" : "player",
45951
+ id: str(hero.id),
45952
+ position: heroPosition(hero),
45953
+ name: str(hero.name),
45954
+ team: heroOwner(hero) === "enemy" ? "enemy" : "player",
45689
45955
  health: 100,
45690
45956
  maxHealth: 100,
45691
- sprite: hero.sprite
45957
+ sprite: hero.sprite == null ? void 0 : str(hero.sprite)
45692
45958
  })),
45693
45959
  [heroes]
45694
45960
  );
@@ -45729,73 +45995,94 @@ function WorldMapBoard({
45729
45995
  const isoUnits = React84.useMemo(() => {
45730
45996
  if (movingPositions.size === 0) return baseUnits;
45731
45997
  return baseUnits.map((u) => {
45732
- const pos = movingPositions.get(u.id);
45998
+ const pos = u.id == null ? void 0 : movingPositions.get(u.id);
45733
45999
  return pos ? { ...u, position: pos } : u;
45734
46000
  });
45735
46001
  }, [baseUnits, movingPositions]);
45736
46002
  const validMoves = React84.useMemo(() => {
45737
- if (!selectedHero || selectedHero.movement <= 0) return [];
46003
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46004
+ const sp = heroPosition(selectedHero);
46005
+ const sOwner = heroOwner(selectedHero);
46006
+ const range = heroMovement(selectedHero);
45738
46007
  const moves = [];
45739
46008
  hexes.forEach((hex) => {
45740
- if (hex.passable === false) return;
45741
- if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
45742
- if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
45743
- if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
45744
- moves.push({ x: hex.x, y: hex.y });
46009
+ const hx = num(hex.x);
46010
+ const hy = num(hex.y);
46011
+ if (!hexPassable(hex)) return;
46012
+ if (hx === sp.x && hy === sp.y) return;
46013
+ if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46014
+ if (heroes.some((h) => {
46015
+ const hp = heroPosition(h);
46016
+ return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
46017
+ })) return;
46018
+ moves.push({ x: hx, y: hy });
45745
46019
  });
45746
46020
  return moves;
45747
46021
  }, [selectedHero, hexes, heroes, isInRange]);
45748
46022
  const attackTargets = React84.useMemo(() => {
45749
- if (!selectedHero || selectedHero.movement <= 0) return [];
45750
- return heroes.filter((h) => h.owner !== selectedHero.owner).filter((h) => isInRange(selectedHero.position, h.position, selectedHero.movement)).map((h) => h.position);
46023
+ if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46024
+ const sp = heroPosition(selectedHero);
46025
+ const sOwner = heroOwner(selectedHero);
46026
+ const range = heroMovement(selectedHero);
46027
+ return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
45751
46028
  }, [selectedHero, heroes, isInRange]);
45752
- const maxY = Math.max(...hexes.map((h) => h.y), 0);
46029
+ const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
45753
46030
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
45754
46031
  const tileToScreen = React84.useCallback(
45755
46032
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
45756
46033
  [scale, baseOffsetX]
45757
46034
  );
45758
46035
  const hoveredHex = React84.useMemo(
45759
- () => hoveredTile ? hexes.find((h) => h.x === hoveredTile.x && h.y === hoveredTile.y) ?? null : null,
46036
+ () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
45760
46037
  [hoveredTile, hexes]
45761
46038
  );
45762
46039
  const hoveredHero = React84.useMemo(
45763
- () => hoveredTile ? heroes.find((h) => h.position.x === hoveredTile.x && h.position.y === hoveredTile.y) ?? null : null,
46040
+ () => hoveredTile ? heroes.find((h) => {
46041
+ const hp = heroPosition(h);
46042
+ return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
46043
+ }) ?? null : null,
45764
46044
  [hoveredTile, heroes]
45765
46045
  );
45766
46046
  const handleTileClick = React84.useCallback((x, y) => {
45767
46047
  if (movementAnimRef.current) return;
45768
- const hex = hexes.find((h) => h.x === x && h.y === y);
46048
+ const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
45769
46049
  if (!hex) return;
45770
46050
  if (tileClickEvent) {
45771
46051
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
45772
46052
  }
45773
46053
  if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
45774
- startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
45775
- onHeroMove?.(selectedHero.id, x, y);
46054
+ const heroId = str(selectedHero.id);
46055
+ startMoveAnimation(heroId, { ...heroPosition(selectedHero) }, { x, y }, () => {
46056
+ onHeroMove?.(heroId, x, y);
45776
46057
  if (heroMoveEvent) {
45777
- eventBus.emit(`UI:${heroMoveEvent}`, { heroId: selectedHero.id, toX: x, toY: y });
46058
+ eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
45778
46059
  }
45779
- if (hex.feature && hex.feature !== "none") {
45780
- onFeatureEnter?.(selectedHero.id, hex);
46060
+ const feature = str(hex.feature);
46061
+ if (feature && feature !== "none") {
46062
+ onFeatureEnter?.(heroId, hex);
45781
46063
  if (featureEnterEvent) {
45782
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId: selectedHero.id, feature: hex.feature, hex });
46064
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
45783
46065
  }
45784
46066
  }
45785
46067
  });
45786
46068
  return;
45787
46069
  }
45788
- const enemy = heroes.find((h) => h.position.x === x && h.position.y === y && h.owner === "enemy");
46070
+ const enemy = heroes.find((h) => {
46071
+ const hp = heroPosition(h);
46072
+ return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
46073
+ });
45789
46074
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
45790
- onBattleEncounter?.(selectedHero.id, enemy.id);
46075
+ const attackerId = str(selectedHero.id);
46076
+ const defenderId = str(enemy.id);
46077
+ onBattleEncounter?.(attackerId, defenderId);
45791
46078
  if (battleEncounterEvent) {
45792
- eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId: selectedHero.id, defenderId: enemy.id });
46079
+ eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
45793
46080
  }
45794
46081
  }
45795
46082
  }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
45796
46083
  const handleUnitClick = React84.useCallback((unitId) => {
45797
- const hero = heroes.find((h) => h.id === unitId);
45798
- if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
46084
+ const hero = heroes.find((h) => str(h.id) === unitId);
46085
+ if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
45799
46086
  onHeroSelect?.(unitId);
45800
46087
  if (heroSelectEvent) {
45801
46088
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
@@ -45868,6 +46155,7 @@ var init_WorldMapBoard = __esm({
45868
46155
  init_Stack();
45869
46156
  init_LoadingState();
45870
46157
  init_IsometricCanvas2();
46158
+ init_boardEntity();
45871
46159
  init_isometric();
45872
46160
  WorldMapBoard.displayName = "WorldMapBoard";
45873
46161
  }
@@ -49096,11 +49384,11 @@ function buildMockData(schema) {
49096
49384
  result[entityName] = entity.instances;
49097
49385
  continue;
49098
49386
  }
49099
- const rows = Array.from(
49387
+ const rows2 = Array.from(
49100
49388
  { length: 10 },
49101
49389
  (_, i) => generateEntityRow(entity, i + 1)
49102
49390
  );
49103
- result[entityName] = rows;
49391
+ result[entityName] = rows2;
49104
49392
  }
49105
49393
  for (const orbital of schema.orbitals) {
49106
49394
  for (const traitRef of orbital.traits ?? []) {