@almadar/ui 2.1.0 → 2.1.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.
@@ -1,10 +1,9 @@
1
- import { useAuthContext } from '../chunk-6WHMUKED.js';
2
- export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-6WHMUKED.js';
1
+ import { useAuthContext } from '../chunk-BKC4XU44.js';
2
+ export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-BKC4XU44.js';
3
3
  import { DEFAULT_CONFIG, renderStateMachineToDomData, parseContentSegments } from '../chunk-N6DJVKZ6.js';
4
4
  import '../chunk-XSEDIUM6.js';
5
- export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
6
- import { VStack, HStack, Typography, Button, Icon, Box, Card, Avatar, Badge, SearchInput, Checkbox, Menu as Menu$1, Pagination, LoadingState, EmptyState, Modal, ErrorState, QuizBlock, CodeBlock, ScaledDiagram, MarkdownContent, Divider, ProgressBar, Stack, Drawer, Toast, Tabs, Input, ThemeToggle, HealthBar, ScoreDisplay, StateIndicator, Container, EntityDisplayEvents } from '../chunk-FZJ73RDM.js';
7
- export { Accordion, Card2 as ActionCard, Alert, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Center, Checkbox, CodeBlock, ConditionalWrapper, Container, ControlButton, DataTable, DetailPanel, Divider, Drawer, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FilterGroup, Flex, FloatingActionButton, Form, FormField, FormSectionHeader, Grid, HStack, Heading, HealthBar, Icon, Input, InputGroup, Label, LawReferenceTooltip, LoadingState, MarkdownContent, MasterDetail, Menu, Modal, Overlay, PageHeader, Pagination, Popover, ProgressBar, QuizBlock, Radio, RelationSelect, RepeatableFormSection, ScaledDiagram, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, Spacer, Spinner, Sprite, Stack, StatCard, StateIndicator, Switch, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, Toast, Tooltip, Typography, UISlotComponent, UISlotRenderer, VStack, ViolationAlert, WizardNavigation, WizardProgress, drawSprite } from '../chunk-FZJ73RDM.js';
5
+ import { VStack, HStack, Typography, Button, Icon, Box, Card, Avatar, Badge, SearchInput, Checkbox, Menu as Menu$1, Pagination, LoadingState, EmptyState, Modal, ErrorState, QuizBlock, CodeBlock, ScaledDiagram, MarkdownContent, Divider, ProgressBar, Stack, Select, Drawer, Toast, Tabs, Input, ThemeToggle, HealthBar, ScoreDisplay, StateIndicator, Container, EntityDisplayEvents } from '../chunk-LB3HXNAR.js';
6
+ export { Accordion, Card2 as ActionCard, Alert, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Center, Checkbox, CodeBlock, ConditionalWrapper, Container, ControlButton, DataTable, DetailPanel, Divider, Drawer, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FilterGroup, Flex, FloatingActionButton, Form, FormField, FormSectionHeader, Grid, HStack, Heading, HealthBar, Icon, Input, InputGroup, Label, LawReferenceTooltip, LoadingState, MarkdownContent, MasterDetail, Menu, Modal, Overlay, PageHeader, Pagination, Popover, ProgressBar, QuizBlock, Radio, RelationSelect, RepeatableFormSection, ScaledDiagram, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, Spacer, Spinner, Sprite, Stack, StatCard, StateIndicator, Switch, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, Toast, Tooltip, Typography, UISlotComponent, UISlotRenderer, VStack, ViolationAlert, WizardNavigation, WizardProgress, drawSprite } from '../chunk-LB3HXNAR.js';
8
7
  import '../chunk-BTXQJGFB.js';
9
8
  import { useTranslate } from '../chunk-PE2H3NAW.js';
10
9
  export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useQuerySingleton, useTranslate } from '../chunk-PE2H3NAW.js';
@@ -13,14 +12,14 @@ export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.j
13
12
  export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-7NEWMNNU.js';
14
13
  import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
15
14
  export { cn } from '../chunk-KKCVDUK7.js';
15
+ export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
16
16
  import { __publicField } from '../chunk-PKBMQBKP.js';
17
- import * as React25 from 'react';
18
- import React25__default, { createContext, useState, useMemo, useCallback, useEffect, useRef, useContext } from 'react';
19
- import { ChevronDown, X, Menu, ChevronRight, ChevronLeft, ArrowUp, ArrowDown, MoreVertical, Package, Check, AlertTriangle, Trash2, List as List$1, Printer, AlertCircle, Circle, Clock, CheckCircle2, Image as Image$1, Upload, ZoomIn, Eraser, FileText, ZoomOut, Download, RotateCcw, Code, WrapText, Copy, Settings, Search, Bell, LogOut, Pause, Play, Calendar, Pencil, Eye, MoreHorizontal, Minus, Plus } from 'lucide-react';
17
+ import * as React42 from 'react';
18
+ import React42__default, { createContext, useState, useMemo, useCallback, useEffect, useRef, useContext } from 'react';
19
+ import { ChevronDown, X, Menu, ChevronRight, ChevronLeft, ArrowUp, ArrowDown, MoreVertical, Package, Check, AlertTriangle, Trash2, List as List$1, Printer, CheckCircle, XCircle, Play, RotateCcw, Send, Wrench, Bug, ArrowRight, Pause, SkipForward, Zap, Sword, Move, Heart, Shield, AlertCircle, Circle, Clock, CheckCircle2, Image as Image$1, Upload, ZoomIn, Eraser, FileText, ZoomOut, Download, Code, WrapText, Copy, Settings, Search, Bell, LogOut, Calendar, Pencil, Eye, MoreHorizontal, Minus, Plus } from 'lucide-react';
20
20
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
21
- import { useNavigate, useLocation, Link, Outlet } from 'react-router-dom';
22
21
  import { createPortal } from 'react-dom';
23
- import { Button as Button$1, cn as cn$1 } from '@almadar/ui';
22
+ import { useLocation, Link, Outlet } from 'react-router-dom';
24
23
 
25
24
  var FormSection = ({
26
25
  title,
@@ -32,7 +31,7 @@ var FormSection = ({
32
31
  columns = 1,
33
32
  className
34
33
  }) => {
35
- const [collapsed, setCollapsed] = React25__default.useState(defaultCollapsed);
34
+ const [collapsed, setCollapsed] = React42__default.useState(defaultCollapsed);
36
35
  const gridClass = {
37
36
  1: "grid-cols-1",
38
37
  2: "grid-cols-1 md:grid-cols-2",
@@ -407,7 +406,7 @@ var Section = ({
407
406
  };
408
407
  Section.displayName = "Section";
409
408
  var SidebarNavItem = ({ item, collapsed }) => {
410
- const Icon2 = item.icon;
409
+ const Icon3 = item.icon;
411
410
  const isActive = item.active ?? item.isActive;
412
411
  return /* @__PURE__ */ jsxs(
413
412
  Button,
@@ -429,8 +428,8 @@ var SidebarNavItem = ({ item, collapsed }) => {
429
428
  ),
430
429
  title: collapsed ? item.label : void 0,
431
430
  children: [
432
- Icon2 && /* @__PURE__ */ jsx(
433
- Icon2,
431
+ Icon3 && /* @__PURE__ */ jsx(
432
+ Icon3,
434
433
  {
435
434
  size: 20,
436
435
  className: cn(
@@ -1008,7 +1007,6 @@ var List = ({
1008
1007
  fieldNames,
1009
1008
  entityType
1010
1009
  }) => {
1011
- const navigate = useNavigate();
1012
1010
  const eventBus = useEventBus();
1013
1011
  const { t } = useTranslate();
1014
1012
  const resolvedEmptyMessage = emptyMessage ?? t("empty.noData");
@@ -1020,7 +1018,7 @@ var List = ({
1020
1018
  if (d && typeof d === "object" && "id" in d) return [d];
1021
1019
  return [];
1022
1020
  }, [data]);
1023
- const getItemActions = React25__default.useCallback(
1021
+ const getItemActions = React42__default.useCallback(
1024
1022
  (item) => {
1025
1023
  if (!itemActions) return [];
1026
1024
  if (typeof itemActions === "function") {
@@ -1035,7 +1033,7 @@ var List = ({
1035
1033
  /\{\{(\w+)\}\}/g,
1036
1034
  (_, key) => String(item[key] || item.id || "")
1037
1035
  );
1038
- navigate(url);
1036
+ eventBus.emit("UI:NAVIGATE", { url, row: item, entity: entityName });
1039
1037
  return;
1040
1038
  }
1041
1039
  if (action.event) {
@@ -1047,7 +1045,7 @@ var List = ({
1047
1045
  }
1048
1046
  }));
1049
1047
  },
1050
- [itemActions, navigate, eventBus, entityName]
1048
+ [itemActions, eventBus, entityName]
1051
1049
  );
1052
1050
  const normalizedItemActions = itemActions ? getItemActions : void 0;
1053
1051
  if (isLoading) {
@@ -1523,7 +1521,7 @@ var WizardContainer = ({
1523
1521
  const isCompleted = index < currentStep;
1524
1522
  const stepKey = step.id ?? step.tabId ?? `step-${index}`;
1525
1523
  const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
1526
- return /* @__PURE__ */ jsxs(React25__default.Fragment, { children: [
1524
+ return /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
1527
1525
  /* @__PURE__ */ jsx(
1528
1526
  Button,
1529
1527
  {
@@ -2747,7 +2745,7 @@ var StateMachineView = ({
2747
2745
  style: { top: title ? 30 : 0 },
2748
2746
  children: [
2749
2747
  entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
2750
- states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React25__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
2748
+ states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React42__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
2751
2749
  StateNode,
2752
2750
  {
2753
2751
  state,
@@ -6130,12 +6128,12 @@ function GameAudioToggle({
6130
6128
  setMuted(!muted);
6131
6129
  }, [muted, setMuted]);
6132
6130
  return /* @__PURE__ */ jsx(
6133
- Button$1,
6131
+ Button,
6134
6132
  {
6135
6133
  variant: "ghost",
6136
6134
  size,
6137
6135
  onClick: handleToggle,
6138
- className: cn$1("text-lg leading-none px-2", className),
6136
+ className: cn("text-lg leading-none px-2", className),
6139
6137
  "aria-pressed": muted,
6140
6138
  children: muted ? "\u{1F507}" : "\u{1F50A}"
6141
6139
  }
@@ -6743,15 +6741,6 @@ var variantMap2 = {
6743
6741
  secondary: "bg-gray-700 hover:bg-gray-600 text-white border-gray-500",
6744
6742
  ghost: "bg-transparent hover:bg-white/10 text-white border-white/20"
6745
6743
  };
6746
- function useSafeNavigate() {
6747
- try {
6748
- const navigate = useNavigate();
6749
- return navigate;
6750
- } catch {
6751
- return () => {
6752
- };
6753
- }
6754
- }
6755
6744
  function GameMenu({
6756
6745
  title,
6757
6746
  subtitle,
@@ -6764,14 +6753,13 @@ function GameMenu({
6764
6753
  className
6765
6754
  }) {
6766
6755
  const resolvedOptions = options ?? menuItems ?? [];
6767
- const navigate = useSafeNavigate();
6768
6756
  let eventBusFromHook = null;
6769
6757
  try {
6770
6758
  eventBusFromHook = useEventBus();
6771
6759
  } catch {
6772
6760
  }
6773
6761
  const eventBus = eventBusProp || eventBusFromHook;
6774
- const handleOptionClick = React25.useCallback(
6762
+ const handleOptionClick = React42.useCallback(
6775
6763
  (option) => {
6776
6764
  if (option.event && eventBus) {
6777
6765
  eventBus.emit(`UI:${option.event}`, { option });
@@ -6779,11 +6767,11 @@ function GameMenu({
6779
6767
  if (onSelect) {
6780
6768
  onSelect(option);
6781
6769
  }
6782
- if (option.navigatesTo) {
6783
- navigate(option.navigatesTo);
6770
+ if (option.navigatesTo && eventBus) {
6771
+ eventBus.emit("UI:NAVIGATE", { url: option.navigatesTo, option });
6784
6772
  }
6785
6773
  },
6786
- [eventBus, onSelect, navigate]
6774
+ [eventBus, onSelect]
6787
6775
  );
6788
6776
  return /* @__PURE__ */ jsxs(
6789
6777
  "div",
@@ -6894,7 +6882,7 @@ function GameOverScreen({
6894
6882
  } catch {
6895
6883
  }
6896
6884
  const eventBus = eventBusProp || eventBusFromHook;
6897
- const handleActionClick = React25.useCallback(
6885
+ const handleActionClick = React42.useCallback(
6898
6886
  (action) => {
6899
6887
  if (action.event && eventBus) {
6900
6888
  eventBus.emit(`UI:${action.event}`, { action });
@@ -7319,7 +7307,7 @@ function BattleBoard({
7319
7307
  onAttack,
7320
7308
  onGameEnd,
7321
7309
  onUnitMove,
7322
- calculateDamage,
7310
+ calculateDamage: calculateDamage2,
7323
7311
  onDrawEffects,
7324
7312
  hasActiveEffects: hasActiveEffects2 = false,
7325
7313
  effectSpriteUrls = [],
@@ -7471,7 +7459,7 @@ function BattleBoard({
7471
7459
  }
7472
7460
  if (currentPhase === "action" && selectedUnit) {
7473
7461
  if (unit.team === "enemy" && attackTargets.some((t) => t.x === unit.position.x && t.y === unit.position.y)) {
7474
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
7462
+ const damage = calculateDamage2 ? calculateDamage2(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
7475
7463
  setIsShaking(true);
7476
7464
  setTimeout(() => setIsShaking(false), 300);
7477
7465
  onAttack?.(selectedUnit, unit, damage);
@@ -7485,7 +7473,7 @@ function BattleBoard({
7485
7473
  setTimeout(checkGameEnd, 100);
7486
7474
  }
7487
7475
  }
7488
- }, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage, unitClickEvent, attackEvent, eventBus]);
7476
+ }, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
7489
7477
  const handleTileClick = useCallback((x, y) => {
7490
7478
  if (tileClickEvent) {
7491
7479
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
@@ -7650,7 +7638,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7650
7638
  attackEvent,
7651
7639
  cancelEvent
7652
7640
  } = eventConfig;
7653
- const { onAttack, onGameEnd, onUnitMove, calculateDamage } = callbacks;
7641
+ const { onAttack, onGameEnd, onUnitMove, calculateDamage: calculateDamage2 } = callbacks;
7654
7642
  const [units, setUnits] = useState(initialUnits);
7655
7643
  const [selectedUnitId, setSelectedUnitId] = useState(null);
7656
7644
  const [phase, setPhase] = useState("observation");
@@ -7693,7 +7681,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7693
7681
  const dx = Math.abs(unit.position.x - selectedUnit.position.x);
7694
7682
  const dy = Math.abs(unit.position.y - selectedUnit.position.y);
7695
7683
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
7696
- const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
7684
+ const damage = calculateDamage2 ? calculateDamage2(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
7697
7685
  const newHealth = Math.max(0, unit.health - damage);
7698
7686
  const updatedUnits = units.map(
7699
7687
  (u) => u.id === unit.id ? { ...u, health: newHealth } : u
@@ -7714,7 +7702,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7714
7702
  }
7715
7703
  }
7716
7704
  }
7717
- }, [units, selectedUnitId, phase, checkGameEnd, onAttack, calculateDamage, unitClickEvent, attackEvent, eventBus]);
7705
+ }, [units, selectedUnitId, phase, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
7718
7706
  const handleTileClick = useCallback((x, y) => {
7719
7707
  if (tileClickEvent) {
7720
7708
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
@@ -8155,7 +8143,7 @@ function LinearView({
8155
8143
  /* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
8156
8144
  const isDone = i < currentIdx;
8157
8145
  const isCurrent = i === currentIdx;
8158
- return /* @__PURE__ */ jsxs(React25__default.Fragment, { children: [
8146
+ return /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
8159
8147
  i > 0 && /* @__PURE__ */ jsx(
8160
8148
  Typography,
8161
8149
  {
@@ -8573,7 +8561,7 @@ var FEATURE_TYPES = [
8573
8561
  "castle"
8574
8562
  ];
8575
8563
  function CollapsibleSection({ title, expanded, onToggle, children, className }) {
8576
- const Icon2 = expanded ? ChevronDown : ChevronRight;
8564
+ const Icon3 = expanded ? ChevronDown : ChevronRight;
8577
8565
  return /* @__PURE__ */ jsxs(VStack, { gap: "xs", className, children: [
8578
8566
  /* @__PURE__ */ jsx(
8579
8567
  Button,
@@ -8583,7 +8571,7 @@ function CollapsibleSection({ title, expanded, onToggle, children, className })
8583
8571
  onClick: onToggle,
8584
8572
  className: "w-full justify-start text-left",
8585
8573
  children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
8586
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
8574
+ /* @__PURE__ */ jsx(Icon3, { size: 14 }),
8587
8575
  /* @__PURE__ */ jsx(Typography, { variant: "label", weight: "semibold", children: title })
8588
8576
  ] })
8589
8577
  }
@@ -8715,174 +8703,2451 @@ function EditorToolbar({ mode, onModeChange, className }) {
8715
8703
  )) });
8716
8704
  }
8717
8705
  EditorToolbar.displayName = "EditorToolbar";
8718
- function extractTitle(children) {
8719
- if (!React25__default.isValidElement(children)) return void 0;
8720
- const props = children.props;
8721
- if (typeof props.title === "string") {
8722
- return props.title;
8723
- }
8724
- return void 0;
8725
- }
8726
- var ModalSlot = ({
8727
- children,
8728
- title: overrideTitle,
8706
+ var DRAG_MIME2 = "application/x-almadar-slot-item";
8707
+ var SIZE_CONFIG3 = {
8708
+ sm: { px: "px-2 py-1", icon: "text-lg", text: "text-xs" },
8709
+ md: { px: "px-3 py-2", icon: "text-2xl", text: "text-sm" },
8710
+ lg: { px: "px-4 py-3", icon: "text-3xl", text: "text-base" }
8711
+ };
8712
+ function ActionTile({
8713
+ action,
8729
8714
  size = "md",
8715
+ disabled = false,
8716
+ categoryColors,
8730
8717
  className
8731
- }) => {
8732
- const eventBus = useEventBus();
8733
- const isOpen = Boolean(children);
8734
- const title = overrideTitle || extractTitle(children);
8735
- const handleClose = () => {
8736
- eventBus.emit("UI:CLOSE");
8737
- eventBus.emit("UI:CANCEL");
8738
- };
8739
- if (!isOpen) return null;
8740
- return /* @__PURE__ */ jsx(
8741
- Modal,
8718
+ }) {
8719
+ const config = SIZE_CONFIG3[size];
8720
+ const catColor = categoryColors?.[action.category];
8721
+ const handleDragStart = useCallback((e) => {
8722
+ if (disabled) {
8723
+ e.preventDefault();
8724
+ return;
8725
+ }
8726
+ e.dataTransfer.setData(DRAG_MIME2, JSON.stringify(action));
8727
+ e.dataTransfer.effectAllowed = "copy";
8728
+ }, [action, disabled]);
8729
+ return /* @__PURE__ */ jsxs(
8730
+ Box,
8742
8731
  {
8743
- isOpen,
8744
- onClose: handleClose,
8745
- title,
8746
- size,
8747
- className,
8748
- children
8732
+ display: "flex",
8733
+ className: cn(
8734
+ "flex-col items-center gap-1 rounded-lg border-2 transition-all select-none",
8735
+ config.px,
8736
+ disabled ? "opacity-40 cursor-not-allowed border-border bg-muted" : "cursor-grab active:cursor-grabbing hover:scale-105 hover:shadow-md border-border bg-card",
8737
+ className
8738
+ ),
8739
+ style: {
8740
+ backgroundColor: !disabled && catColor ? catColor.bg : void 0,
8741
+ borderColor: !disabled && catColor ? catColor.border : void 0
8742
+ },
8743
+ draggable: !disabled,
8744
+ onDragStart: handleDragStart,
8745
+ children: [
8746
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: cn(config.icon, "leading-none"), children: action.iconEmoji || "\u2726" }),
8747
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: cn(config.text, "text-foreground font-medium whitespace-nowrap"), children: action.name })
8748
+ ]
8749
8749
  }
8750
8750
  );
8751
- };
8752
- ModalSlot.displayName = "ModalSlot";
8753
- function extractTitle2(children) {
8754
- if (!React25__default.isValidElement(children)) return void 0;
8755
- const props = children.props;
8756
- if (typeof props.title === "string") {
8757
- return props.title;
8758
- }
8759
- return void 0;
8760
8751
  }
8761
- var DrawerSlot = ({
8762
- children,
8763
- title: overrideTitle,
8764
- position = "right",
8752
+ ActionTile.displayName = "ActionTile";
8753
+ function ActionPalette({
8754
+ actions,
8755
+ usedActionIds = [],
8756
+ allowDuplicates = true,
8757
+ categoryColors,
8765
8758
  size = "md",
8759
+ label,
8766
8760
  className
8767
- }) => {
8768
- const eventBus = useEventBus();
8769
- const isOpen = Boolean(children);
8770
- const title = overrideTitle || extractTitle2(children);
8771
- const handleClose = () => {
8772
- eventBus.emit("UI:CLOSE");
8773
- eventBus.emit("UI:CANCEL");
8774
- };
8775
- if (!isOpen) return null;
8776
- return /* @__PURE__ */ jsx(
8777
- Drawer,
8778
- {
8779
- isOpen,
8780
- onClose: handleClose,
8781
- title,
8782
- position,
8783
- width: size,
8784
- className,
8785
- children
8786
- }
8787
- );
8788
- };
8789
- DrawerSlot.displayName = "DrawerSlot";
8790
- function extractToastProps(children) {
8791
- if (!React25__default.isValidElement(children)) {
8792
- if (typeof children === "string") {
8793
- return { message: children };
8761
+ }) {
8762
+ const { t } = useTranslate();
8763
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
8764
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: label ?? t("sequencer.actions") }),
8765
+ /* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: actions.map((action) => /* @__PURE__ */ jsx(
8766
+ ActionTile,
8767
+ {
8768
+ action,
8769
+ size,
8770
+ categoryColors,
8771
+ disabled: !allowDuplicates && usedActionIds.includes(action.id)
8772
+ },
8773
+ action.id
8774
+ )) })
8775
+ ] });
8776
+ }
8777
+ ActionPalette.displayName = "ActionPalette";
8778
+ function SequenceBar({
8779
+ slots,
8780
+ maxSlots,
8781
+ onSlotDrop,
8782
+ onSlotRemove,
8783
+ playing = false,
8784
+ currentStep = -1,
8785
+ categoryColors,
8786
+ slotFeedback,
8787
+ size = "lg",
8788
+ className
8789
+ }) {
8790
+ const handleDrop = useCallback((index) => (item) => {
8791
+ if (playing) return;
8792
+ onSlotDrop(index, item);
8793
+ }, [onSlotDrop, playing]);
8794
+ const handleRemove = useCallback((index) => () => {
8795
+ if (playing) return;
8796
+ onSlotRemove(index);
8797
+ }, [onSlotRemove, playing]);
8798
+ const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
8799
+ return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
8800
+ i > 0 && /* @__PURE__ */ jsx(
8801
+ Typography,
8802
+ {
8803
+ variant: "body1",
8804
+ className: cn(
8805
+ "text-lg",
8806
+ currentStep >= 0 && i <= currentStep ? "text-primary" : "text-muted-foreground"
8807
+ ),
8808
+ children: "\u2192"
8809
+ }
8810
+ ),
8811
+ /* @__PURE__ */ jsx(
8812
+ TraitSlot,
8813
+ {
8814
+ slotNumber: i + 1,
8815
+ equippedItem: slot,
8816
+ size,
8817
+ categoryColors,
8818
+ onItemDrop: handleDrop(i),
8819
+ onRemove: slot ? handleRemove(i) : void 0,
8820
+ draggable: !playing && !!slot,
8821
+ selected: currentStep === i,
8822
+ locked: playing,
8823
+ feedback: slotFeedback?.[i]
8824
+ }
8825
+ )
8826
+ ] }, i)) });
8827
+ }
8828
+ SequenceBar.displayName = "SequenceBar";
8829
+ var ENCOURAGEMENT_KEYS = [
8830
+ "puzzle.tryAgain1",
8831
+ "puzzle.tryAgain2",
8832
+ "puzzle.tryAgain3"
8833
+ ];
8834
+ var stepLabel = (slot, i) => slot ? `${i + 1}. ${slot.name}` : `Step ${i + 1}`;
8835
+ function computeSlotFeedback(playerSeq, solutions) {
8836
+ let bestSolution = solutions[0];
8837
+ let bestMatches = -1;
8838
+ for (const sol of solutions) {
8839
+ const matches = sol.filter((id, i) => id === playerSeq[i]).length;
8840
+ if (matches > bestMatches) {
8841
+ bestMatches = matches;
8842
+ bestSolution = sol;
8794
8843
  }
8795
- return {};
8796
8844
  }
8797
- const props = children.props;
8798
- return {
8799
- message: typeof props.message === "string" ? props.message : void 0,
8800
- variant: props.variant,
8801
- title: typeof props.title === "string" ? props.title : void 0
8802
- };
8845
+ return playerSeq.map(
8846
+ (id, i) => id !== void 0 && id === bestSolution[i] ? "correct" : "wrong"
8847
+ );
8803
8848
  }
8804
- var ToastSlot = ({
8805
- children,
8806
- variant: overrideVariant,
8807
- title: overrideTitle,
8808
- duration = 5e3,
8849
+ function SequencerBoard({
8850
+ entity,
8851
+ categoryColors,
8852
+ stepDurationMs = 1e3,
8853
+ playEvent,
8854
+ completeEvent,
8809
8855
  className
8810
- }) => {
8811
- const eventBus = useEventBus();
8812
- const isVisible = Boolean(children);
8813
- const extracted = extractToastProps(children);
8814
- const variant = overrideVariant || extracted.variant || "info";
8815
- const title = overrideTitle || extracted.title;
8816
- const message = extracted.message || (typeof children === "string" ? children : "");
8817
- const handleDismiss = () => {
8818
- eventBus.emit("UI:DISMISS");
8819
- eventBus.emit("UI:CLOSE");
8856
+ }) {
8857
+ const { emit } = useEventBus();
8858
+ const { t } = useTranslate();
8859
+ const [slots, setSlots] = useState(
8860
+ () => Array.from({ length: entity.maxSlots }, () => void 0)
8861
+ );
8862
+ const [playState, setPlayState] = useState("idle");
8863
+ const [currentStep, setCurrentStep] = useState(-1);
8864
+ const [attempts, setAttempts] = useState(0);
8865
+ const [slotFeedback, setSlotFeedback] = useState(
8866
+ () => Array.from({ length: entity.maxSlots }, () => null)
8867
+ );
8868
+ const timerRef = useRef(null);
8869
+ useEffect(() => () => {
8870
+ if (timerRef.current) clearTimeout(timerRef.current);
8871
+ }, []);
8872
+ const handleSlotDrop = useCallback((index, item) => {
8873
+ setSlots((prev) => {
8874
+ const next = [...prev];
8875
+ next[index] = item;
8876
+ return next;
8877
+ });
8878
+ setSlotFeedback((prev) => {
8879
+ const next = [...prev];
8880
+ next[index] = null;
8881
+ return next;
8882
+ });
8883
+ emit("UI:PLAY_SOUND", { key: "drop_slot" });
8884
+ }, [emit]);
8885
+ const handleSlotRemove = useCallback((index) => {
8886
+ setSlots((prev) => {
8887
+ const next = [...prev];
8888
+ next[index] = void 0;
8889
+ return next;
8890
+ });
8891
+ setSlotFeedback((prev) => {
8892
+ const next = [...prev];
8893
+ next[index] = null;
8894
+ return next;
8895
+ });
8896
+ emit("UI:PLAY_SOUND", { key: "back" });
8897
+ }, [emit]);
8898
+ const handleReset = useCallback(() => {
8899
+ if (timerRef.current) clearTimeout(timerRef.current);
8900
+ setSlots(Array.from({ length: entity.maxSlots }, () => void 0));
8901
+ setPlayState("idle");
8902
+ setCurrentStep(-1);
8903
+ setAttempts(0);
8904
+ setSlotFeedback(Array.from({ length: entity.maxSlots }, () => null));
8905
+ }, [entity.maxSlots]);
8906
+ const filledSlots = slots.filter((s) => !!s);
8907
+ const canPlay = filledSlots.length > 0 && playState === "idle";
8908
+ const handlePlay = useCallback(() => {
8909
+ if (!canPlay) return;
8910
+ setSlotFeedback(Array.from({ length: entity.maxSlots }, () => null));
8911
+ emit("UI:PLAY_SOUND", { key: "confirm" });
8912
+ const sequence = slots.map((s) => s?.id || "");
8913
+ if (playEvent) {
8914
+ emit(`UI:${playEvent}`, { sequence });
8915
+ }
8916
+ setPlayState("playing");
8917
+ setCurrentStep(0);
8918
+ let step = 0;
8919
+ const advance = () => {
8920
+ step++;
8921
+ if (step >= entity.maxSlots) {
8922
+ const playerSeq = slots.map((s) => s?.id);
8923
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
8924
+ const success = entity.solutions.some(
8925
+ (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
8926
+ );
8927
+ if (success) {
8928
+ setPlayState("success");
8929
+ setCurrentStep(-1);
8930
+ emit("UI:PLAY_SOUND", { key: "levelComplete" });
8931
+ if (completeEvent) {
8932
+ emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
8933
+ }
8934
+ } else {
8935
+ setAttempts((prev) => prev + 1);
8936
+ const feedback = computeSlotFeedback(playerSeq, entity.solutions);
8937
+ setSlotFeedback(feedback);
8938
+ setPlayState("idle");
8939
+ setCurrentStep(-1);
8940
+ emit("UI:PLAY_SOUND", { key: "fail" });
8941
+ const correctCount2 = feedback.filter((f) => f === "correct").length;
8942
+ for (let ci = 0; ci < correctCount2; ci++) {
8943
+ setTimeout(() => {
8944
+ emit("UI:PLAY_SOUND", { key: "correctSlot" });
8945
+ }, 300 + ci * 150);
8946
+ }
8947
+ }
8948
+ } else {
8949
+ setCurrentStep(step);
8950
+ timerRef.current = setTimeout(advance, stepDurationMs);
8951
+ }
8952
+ };
8953
+ timerRef.current = setTimeout(advance, stepDurationMs);
8954
+ }, [canPlay, slots, entity.maxSlots, entity.solutions, stepDurationMs, playEvent, completeEvent, emit]);
8955
+ const machine = {
8956
+ name: entity.title,
8957
+ description: entity.description,
8958
+ states: slots.map((s, i) => stepLabel(s, i)),
8959
+ currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
8960
+ transitions: slots.slice(0, -1).map((s, i) => ({
8961
+ from: stepLabel(s, i),
8962
+ to: stepLabel(slots[i + 1], i + 1),
8963
+ event: "NEXT"
8964
+ }))
8820
8965
  };
8821
- if (!isVisible) return null;
8822
- const isCustomContent = React25__default.isValidElement(children) && !message;
8823
- return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
8824
- Toast,
8825
- {
8826
- variant,
8827
- title,
8828
- message: message || "Notification",
8829
- duration,
8830
- onDismiss: handleDismiss,
8831
- className
8832
- }
8833
- ) });
8834
- };
8835
- ToastSlot.displayName = "ToastSlot";
8836
- var CHART_COLORS = [
8837
- "var(--color-primary)",
8838
- "var(--color-success)",
8839
- "var(--color-warning)",
8840
- "var(--color-error)",
8841
- "var(--color-info)",
8842
- "var(--color-accent)"
8843
- ];
8844
- var BarChart = ({ data, height, showValues }) => {
8845
- const maxValue = Math.max(...data.map((d) => d.value), 1);
8846
- return /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "end", className: "w-full", style: { height }, children: data.map((point, idx) => {
8847
- const barHeight = point.value / maxValue * 100;
8848
- const color = point.color || CHART_COLORS[idx % CHART_COLORS.length];
8849
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", flex: true, className: "min-w-0", children: [
8850
- showValues && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "tabular-nums", children: point.value }),
8966
+ const usedIds = entity.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
8967
+ const showHint = attempts >= 3 && !!entity.hint;
8968
+ const hasFeedback = slotFeedback.some((f) => f !== null);
8969
+ const correctCount = slotFeedback.filter((f) => f === "correct").length;
8970
+ const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
8971
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 gap-6", className), children: [
8972
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
8973
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
8974
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description })
8975
+ ] }),
8976
+ showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-lg bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
8977
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
8978
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
8979
+ ] }) }),
8980
+ filledSlots.length > 0 && /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
8981
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
8982
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
8983
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
8984
+ hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
8985
+ `${correctCount}/${entity.maxSlots} `,
8986
+ "\u2705"
8987
+ ] })
8988
+ ] }),
8851
8989
  /* @__PURE__ */ jsx(
8852
- Box,
8990
+ SequenceBar,
8853
8991
  {
8854
- className: cn(
8855
- "w-full rounded-t-[var(--radius-sm)] transition-all duration-500 ease-out min-h-[4px]"
8856
- ),
8857
- style: {
8858
- height: `${barHeight}%`,
8859
- backgroundColor: color
8860
- }
8992
+ slots,
8993
+ maxSlots: entity.maxSlots,
8994
+ onSlotDrop: handleSlotDrop,
8995
+ onSlotRemove: handleSlotRemove,
8996
+ playing: playState === "playing",
8997
+ currentStep,
8998
+ categoryColors,
8999
+ slotFeedback,
9000
+ size: "lg"
8861
9001
  }
8862
- ),
9002
+ )
9003
+ ] }),
9004
+ playState !== "playing" && /* @__PURE__ */ jsx(
9005
+ ActionPalette,
9006
+ {
9007
+ actions: entity.availableActions,
9008
+ usedActionIds: usedIds,
9009
+ allowDuplicates: entity.allowDuplicates !== false,
9010
+ categoryColors,
9011
+ label: t("sequencer.dragActions")
9012
+ }
9013
+ ),
9014
+ hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-lg bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
9015
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-lg bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: entity.successMessage || t("sequencer.levelComplete") }) }),
9016
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
8863
9017
  /* @__PURE__ */ jsx(
8864
- Typography,
9018
+ Button,
8865
9019
  {
8866
- variant: "caption",
8867
- color: "secondary",
8868
- className: "truncate w-full text-center",
8869
- children: point.label
9020
+ variant: "primary",
9021
+ onClick: handlePlay,
9022
+ disabled: !canPlay,
9023
+ children: "\u25B6 " + t("game.play")
8870
9024
  }
8871
- )
8872
- ] }, point.label);
8873
- }) });
9025
+ ),
9026
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
9027
+ ] })
9028
+ ] });
9029
+ }
9030
+ SequencerBoard.displayName = "SequencerBoard";
9031
+ function RuleEditor({
9032
+ rule,
9033
+ availableEvents,
9034
+ availableActions,
9035
+ onChange,
9036
+ onRemove,
9037
+ disabled = false,
9038
+ className
9039
+ }) {
9040
+ const { t } = useTranslate();
9041
+ const handleWhenChange = useCallback((e) => {
9042
+ onChange({ ...rule, whenEvent: e.target.value });
9043
+ }, [rule, onChange]);
9044
+ const handleThenChange = useCallback((e) => {
9045
+ onChange({ ...rule, thenAction: e.target.value });
9046
+ }, [rule, onChange]);
9047
+ return /* @__PURE__ */ jsxs(HStack, { className: cn("items-center p-2 rounded-lg bg-muted/50 border border-border", className), gap: "sm", children: [
9048
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-primary font-bold whitespace-nowrap", children: t("eventHandler.when") }),
9049
+ /* @__PURE__ */ jsx(
9050
+ Select,
9051
+ {
9052
+ value: rule.whenEvent,
9053
+ onChange: handleWhenChange,
9054
+ options: availableEvents,
9055
+ disabled,
9056
+ className: "flex-1 min-w-0"
9057
+ }
9058
+ ),
9059
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent font-bold whitespace-nowrap", children: "\u2192 " + t("eventHandler.then") }),
9060
+ /* @__PURE__ */ jsx(
9061
+ Select,
9062
+ {
9063
+ value: rule.thenAction,
9064
+ onChange: handleThenChange,
9065
+ options: availableActions,
9066
+ disabled,
9067
+ className: "flex-1 min-w-0"
9068
+ }
9069
+ ),
9070
+ onRemove && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onRemove, disabled, className: "shrink-0", children: "\xD7" })
9071
+ ] });
9072
+ }
9073
+ RuleEditor.displayName = "RuleEditor";
9074
+ var STATUS_STYLES2 = {
9075
+ pending: "text-muted-foreground",
9076
+ active: "text-primary animate-pulse",
9077
+ done: "text-success",
9078
+ error: "text-error"
8874
9079
  };
8875
- var PieChart = ({ data, height, showValues, donut = false }) => {
8876
- const total = data.reduce((sum, d) => sum + d.value, 0);
8877
- const size = Math.min(height, 200);
8878
- const radius = size / 2 - 8;
8879
- const innerRadius = donut ? radius * 0.6 : 0;
8880
- const center = size / 2;
8881
- const segments = useMemo(() => {
8882
- let currentAngle = -Math.PI / 2;
8883
- return data.map((point, idx) => {
8884
- const angle = point.value / total * 2 * Math.PI;
8885
- const startAngle = currentAngle;
9080
+ var STATUS_DOTS = {
9081
+ pending: "\u25CB",
9082
+ active: "\u25CF",
9083
+ done: "\u2714",
9084
+ error: "\u2717"
9085
+ };
9086
+ function EventLog({
9087
+ entries,
9088
+ maxHeight = 200,
9089
+ label,
9090
+ className
9091
+ }) {
9092
+ const { t } = useTranslate();
9093
+ const scrollRef = useRef(null);
9094
+ useEffect(() => {
9095
+ if (scrollRef.current) {
9096
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
9097
+ }
9098
+ }, [entries.length]);
9099
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
9100
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: label ?? t("eventHandler.eventLog") }),
9101
+ /* @__PURE__ */ jsx(
9102
+ Box,
9103
+ {
9104
+ ref: scrollRef,
9105
+ className: "overflow-y-auto",
9106
+ style: { maxHeight },
9107
+ children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
9108
+ entries.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground italic", children: t("eventHandler.noEvents") }),
9109
+ entries.map((entry) => /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
9110
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: STATUS_STYLES2[entry.status], children: STATUS_DOTS[entry.status] }),
9111
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entry.icon }),
9112
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: cn("flex-1", STATUS_STYLES2[entry.status]), children: entry.message })
9113
+ ] }, entry.id))
9114
+ ] })
9115
+ }
9116
+ )
9117
+ ] });
9118
+ }
9119
+ EventLog.displayName = "EventLog";
9120
+ var nextRuleId = 1;
9121
+ function ObjectRulePanel({
9122
+ object,
9123
+ onRulesChange,
9124
+ disabled = false,
9125
+ className
9126
+ }) {
9127
+ const { t } = useTranslate();
9128
+ const maxRules = object.maxRules || 3;
9129
+ const canAdd = object.rules.length < maxRules;
9130
+ const handleRuleChange = useCallback((index, updatedRule) => {
9131
+ const newRules = [...object.rules];
9132
+ newRules[index] = updatedRule;
9133
+ onRulesChange(object.id, newRules);
9134
+ }, [object.id, object.rules, onRulesChange]);
9135
+ const handleRuleRemove = useCallback((index) => {
9136
+ const newRules = object.rules.filter((_, i) => i !== index);
9137
+ onRulesChange(object.id, newRules);
9138
+ }, [object.id, object.rules, onRulesChange]);
9139
+ const handleAddRule = useCallback(() => {
9140
+ if (!canAdd || disabled) return;
9141
+ const firstEvent = object.availableEvents[0]?.value || "";
9142
+ const firstAction = object.availableActions[0]?.value || "";
9143
+ const newRule = {
9144
+ id: `rule-${nextRuleId++}`,
9145
+ whenEvent: firstEvent,
9146
+ thenAction: firstAction
9147
+ };
9148
+ onRulesChange(object.id, [...object.rules, newRule]);
9149
+ }, [canAdd, disabled, object, onRulesChange]);
9150
+ const machine = {
9151
+ name: object.name,
9152
+ states: object.states,
9153
+ currentState: object.currentState,
9154
+ transitions: object.rules.map((r) => ({
9155
+ from: object.currentState,
9156
+ to: object.states.find((s) => s !== object.currentState) || object.currentState,
9157
+ event: r.whenEvent
9158
+ }))
9159
+ };
9160
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
9161
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center", gap: "sm", children: [
9162
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", children: object.icon }),
9163
+ /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
9164
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
9165
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + object.currentState })
9166
+ ] })
9167
+ ] }),
9168
+ /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
9169
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
9170
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
9171
+ object.rules.map((rule, i) => /* @__PURE__ */ jsx(
9172
+ RuleEditor,
9173
+ {
9174
+ rule,
9175
+ availableEvents: object.availableEvents,
9176
+ availableActions: object.availableActions,
9177
+ onChange: (r) => handleRuleChange(i, r),
9178
+ onRemove: () => handleRuleRemove(i),
9179
+ disabled
9180
+ },
9181
+ rule.id
9182
+ )),
9183
+ canAdd && !disabled && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleAddRule, className: "self-start", children: t("eventHandler.addRule") })
9184
+ ] })
9185
+ ] });
9186
+ }
9187
+ ObjectRulePanel.displayName = "ObjectRulePanel";
9188
+ var ENCOURAGEMENT_KEYS2 = [
9189
+ "puzzle.tryAgain1",
9190
+ "puzzle.tryAgain2",
9191
+ "puzzle.tryAgain3"
9192
+ ];
9193
+ function EventHandlerBoard({
9194
+ entity,
9195
+ stepDurationMs = 800,
9196
+ playEvent,
9197
+ completeEvent,
9198
+ className
9199
+ }) {
9200
+ const { emit } = useEventBus();
9201
+ const { t } = useTranslate();
9202
+ const [objects, setObjects] = useState(entity.objects);
9203
+ const [selectedObjectId, setSelectedObjectId] = useState(
9204
+ entity.objects[0]?.id || null
9205
+ );
9206
+ const [playState, setPlayState] = useState("editing");
9207
+ const [eventLog, setEventLog] = useState([]);
9208
+ const [attempts, setAttempts] = useState(0);
9209
+ const timerRef = useRef(null);
9210
+ const logIdCounter = useRef(0);
9211
+ useEffect(() => () => {
9212
+ if (timerRef.current) clearTimeout(timerRef.current);
9213
+ }, []);
9214
+ const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
9215
+ const handleRulesChange = useCallback((objectId, rules) => {
9216
+ setObjects((prev) => prev.map(
9217
+ (o) => o.id === objectId ? { ...o, rules } : o
9218
+ ));
9219
+ }, []);
9220
+ const addLogEntry = useCallback((icon, message, status = "done") => {
9221
+ const id = `log-${logIdCounter.current++}`;
9222
+ setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
9223
+ }, []);
9224
+ const handlePlay = useCallback(() => {
9225
+ if (playState !== "editing") return;
9226
+ if (playEvent) emit(`UI:${playEvent}`, {});
9227
+ setPlayState("playing");
9228
+ setEventLog([]);
9229
+ const allRules = [];
9230
+ objects.forEach((obj) => {
9231
+ obj.rules.forEach((rule) => {
9232
+ allRules.push({ object: obj, rule });
9233
+ });
9234
+ });
9235
+ const triggers = entity.triggerEvents || [];
9236
+ const eventQueue = [...triggers];
9237
+ const firedEvents = /* @__PURE__ */ new Set();
9238
+ let stepIdx = 0;
9239
+ let goalReached = false;
9240
+ const processNext = () => {
9241
+ if (eventQueue.length === 0 || stepIdx > 20) {
9242
+ if (goalReached) {
9243
+ setPlayState("success");
9244
+ if (completeEvent) {
9245
+ emit(`UI:${completeEvent}`, { success: true });
9246
+ }
9247
+ } else {
9248
+ setAttempts((prev) => prev + 1);
9249
+ setPlayState("fail");
9250
+ }
9251
+ return;
9252
+ }
9253
+ const currentEvent = eventQueue.shift();
9254
+ if (firedEvents.has(currentEvent)) {
9255
+ timerRef.current = setTimeout(processNext, 100);
9256
+ return;
9257
+ }
9258
+ firedEvents.add(currentEvent);
9259
+ const matching = allRules.filter((r) => r.rule.whenEvent === currentEvent);
9260
+ if (matching.length === 0) {
9261
+ addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
9262
+ } else {
9263
+ matching.forEach(({ object, rule }) => {
9264
+ addLogEntry(object.icon, t("eventHandler.heardEvent", { object: object.name, event: currentEvent, action: rule.thenAction }), "done");
9265
+ eventQueue.push(rule.thenAction);
9266
+ if (rule.thenAction === entity.goalEvent) {
9267
+ goalReached = true;
9268
+ }
9269
+ });
9270
+ }
9271
+ if (currentEvent === entity.goalEvent) {
9272
+ goalReached = true;
9273
+ }
9274
+ stepIdx++;
9275
+ timerRef.current = setTimeout(processNext, stepDurationMs);
9276
+ };
9277
+ if (triggers.length > 0) {
9278
+ addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
9279
+ }
9280
+ timerRef.current = setTimeout(processNext, stepDurationMs);
9281
+ }, [playState, objects, entity, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
9282
+ const handleTryAgain = useCallback(() => {
9283
+ if (timerRef.current) clearTimeout(timerRef.current);
9284
+ setPlayState("editing");
9285
+ setEventLog([]);
9286
+ }, []);
9287
+ const handleReset = useCallback(() => {
9288
+ if (timerRef.current) clearTimeout(timerRef.current);
9289
+ setObjects(entity.objects);
9290
+ setPlayState("editing");
9291
+ setEventLog([]);
9292
+ setSelectedObjectId(entity.objects[0]?.id || null);
9293
+ setAttempts(0);
9294
+ }, [entity.objects]);
9295
+ const objectViewers = objects.map((obj) => {
9296
+ const machine = {
9297
+ name: obj.name,
9298
+ states: obj.states,
9299
+ currentState: obj.currentState,
9300
+ transitions: obj.rules.map((r) => ({
9301
+ from: obj.currentState,
9302
+ to: obj.states.find((s) => s !== obj.currentState) || obj.currentState,
9303
+ event: r.whenEvent
9304
+ }))
9305
+ };
9306
+ return { obj, machine };
9307
+ });
9308
+ const showHint = attempts >= 3 && entity.hint;
9309
+ const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
9310
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 gap-6", className), children: [
9311
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
9312
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
9313
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description }),
9314
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
9315
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
9316
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entity.goalCondition })
9317
+ ] })
9318
+ ] }),
9319
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9320
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
9321
+ /* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsx(
9322
+ Box,
9323
+ {
9324
+ className: cn(
9325
+ "p-3 rounded-lg border-2 cursor-pointer transition-all hover:scale-105",
9326
+ selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
9327
+ ),
9328
+ onClick: () => setSelectedObjectId(obj.id),
9329
+ children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
9330
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", children: obj.icon }),
9331
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
9332
+ /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
9333
+ ] })
9334
+ },
9335
+ obj.id
9336
+ )) })
9337
+ ] }),
9338
+ selectedObject && /* @__PURE__ */ jsx(
9339
+ ObjectRulePanel,
9340
+ {
9341
+ object: selectedObject,
9342
+ onRulesChange: handleRulesChange,
9343
+ disabled: playState !== "editing"
9344
+ }
9345
+ ),
9346
+ eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
9347
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-lg bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: entity.successMessage || t("eventHandler.chainComplete") }) }),
9348
+ playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9349
+ /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-lg bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
9350
+ showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-lg bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
9351
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
9352
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
9353
+ ] }) })
9354
+ ] }),
9355
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
9356
+ playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
9357
+ Button,
9358
+ {
9359
+ variant: "primary",
9360
+ onClick: handlePlay,
9361
+ disabled: playState !== "editing",
9362
+ children: "\u25B6 " + t("game.play")
9363
+ }
9364
+ ),
9365
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
9366
+ ] })
9367
+ ] });
9368
+ }
9369
+ EventHandlerBoard.displayName = "EventHandlerBoard";
9370
+ function StateNode2({
9371
+ name,
9372
+ isCurrent = false,
9373
+ isSelected = false,
9374
+ isInitial = false,
9375
+ position,
9376
+ onClick,
9377
+ className
9378
+ }) {
9379
+ return /* @__PURE__ */ jsx(
9380
+ Box,
9381
+ {
9382
+ position: "absolute",
9383
+ display: "flex",
9384
+ className: cn(
9385
+ "items-center justify-center rounded-full border-3 transition-all cursor-pointer select-none",
9386
+ "min-w-[80px] h-[80px] px-3",
9387
+ isCurrent && "bg-primary/20 border-primary shadow-lg shadow-primary/30 scale-110",
9388
+ isSelected && !isCurrent && "bg-accent/20 border-accent ring-2 ring-accent/50",
9389
+ !isCurrent && !isSelected && "bg-card border-border hover:border-muted-foreground hover:scale-105",
9390
+ className
9391
+ ),
9392
+ style: {
9393
+ left: position.x,
9394
+ top: position.y,
9395
+ transform: "translate(-50%, -50%)"
9396
+ },
9397
+ onClick,
9398
+ children: /* @__PURE__ */ jsxs(Box, { className: "text-center", children: [
9399
+ isInitial && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground text-xs block", children: "\u25B6 start" }),
9400
+ /* @__PURE__ */ jsx(
9401
+ Typography,
9402
+ {
9403
+ variant: "body2",
9404
+ className: cn(
9405
+ "font-bold whitespace-nowrap",
9406
+ isCurrent ? "text-primary" : "text-foreground"
9407
+ ),
9408
+ children: name
9409
+ }
9410
+ )
9411
+ ] })
9412
+ }
9413
+ );
9414
+ }
9415
+ StateNode2.displayName = "StateNode";
9416
+ var NODE_RADIUS = 40;
9417
+ function TransitionArrow({
9418
+ from,
9419
+ to,
9420
+ eventLabel,
9421
+ guardHint,
9422
+ isActive = false,
9423
+ onClick,
9424
+ className
9425
+ }) {
9426
+ const dx = to.x - from.x;
9427
+ const dy = to.y - from.y;
9428
+ const dist = Math.sqrt(dx * dx + dy * dy);
9429
+ if (dist === 0) return /* @__PURE__ */ jsx(Fragment, {});
9430
+ const nx = dx / dist;
9431
+ const ny = dy / dist;
9432
+ const startX = from.x + nx * NODE_RADIUS;
9433
+ const startY = from.y + ny * NODE_RADIUS;
9434
+ const endX = to.x - nx * NODE_RADIUS;
9435
+ const endY = to.y - ny * NODE_RADIUS;
9436
+ const midX = (startX + endX) / 2;
9437
+ const midY = (startY + endY) / 2;
9438
+ const perpX = -ny * 20;
9439
+ const perpY = nx * 20;
9440
+ const ctrlX = midX + perpX;
9441
+ const ctrlY = midY + perpY;
9442
+ const path = `M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`;
9443
+ return /* @__PURE__ */ jsxs("g", { className: cn("cursor-pointer", className), onClick, children: [
9444
+ /* @__PURE__ */ jsx(
9445
+ "path",
9446
+ {
9447
+ d: path,
9448
+ fill: "none",
9449
+ stroke: isActive ? "var(--color-primary)" : "var(--color-border)",
9450
+ strokeWidth: isActive ? 3 : 2,
9451
+ markerEnd: isActive ? "url(#arrowhead-active)" : "url(#arrowhead)"
9452
+ }
9453
+ ),
9454
+ /* @__PURE__ */ jsx(
9455
+ "text",
9456
+ {
9457
+ x: ctrlX,
9458
+ y: ctrlY - 8,
9459
+ textAnchor: "middle",
9460
+ fill: isActive ? "var(--color-primary)" : "var(--color-foreground)",
9461
+ fontSize: 12,
9462
+ fontWeight: isActive ? "bold" : "normal",
9463
+ className: "select-none",
9464
+ children: eventLabel
9465
+ }
9466
+ ),
9467
+ guardHint && /* @__PURE__ */ jsx(
9468
+ "text",
9469
+ {
9470
+ x: ctrlX,
9471
+ y: ctrlY + 6,
9472
+ textAnchor: "middle",
9473
+ fill: "var(--color-warning)",
9474
+ fontSize: 10,
9475
+ className: "select-none",
9476
+ children: "\u26A0 " + guardHint
9477
+ }
9478
+ )
9479
+ ] });
9480
+ }
9481
+ TransitionArrow.displayName = "TransitionArrow";
9482
+ function VariablePanel({
9483
+ entityName,
9484
+ variables,
9485
+ className
9486
+ }) {
9487
+ const { t } = useTranslate();
9488
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
9489
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
9490
+ variables.map((v) => {
9491
+ const max = v.max ?? 100;
9492
+ const min = v.min ?? 0;
9493
+ const pct = Math.round((v.value - min) / (max - min) * 100);
9494
+ const isHigh = pct > 80;
9495
+ const isLow = pct < 20;
9496
+ return /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
9497
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
9498
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: v.name }),
9499
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: cn(
9500
+ isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
9501
+ ), children: [
9502
+ v.value,
9503
+ v.unit || "",
9504
+ " / ",
9505
+ max,
9506
+ v.unit || ""
9507
+ ] })
9508
+ ] }),
9509
+ /* @__PURE__ */ jsx(
9510
+ ProgressBar,
9511
+ {
9512
+ value: pct,
9513
+ color: isHigh ? "danger" : isLow ? "warning" : "primary",
9514
+ size: "sm"
9515
+ }
9516
+ )
9517
+ ] }, v.name);
9518
+ })
9519
+ ] });
9520
+ }
9521
+ VariablePanel.displayName = "VariablePanel";
9522
+ function CodeView({
9523
+ data,
9524
+ label,
9525
+ defaultExpanded = false,
9526
+ className
9527
+ }) {
9528
+ const { t } = useTranslate();
9529
+ const [expanded, setExpanded] = useState(defaultExpanded);
9530
+ const jsonString = JSON.stringify(data, null, 2);
9531
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("rounded-lg border border-border overflow-hidden", className), gap: "none", children: [
9532
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between p-2 bg-muted", gap: "sm", children: [
9533
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground font-medium", children: label ?? t("stateArchitect.viewCode") }),
9534
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setExpanded(!expanded), className: "text-xs", children: expanded ? t("stateArchitect.hideJson") : t("stateArchitect.showJson") })
9535
+ ] }),
9536
+ expanded && /* @__PURE__ */ jsx(Box, { className: "p-3 bg-background overflow-x-auto", children: /* @__PURE__ */ jsx(
9537
+ Typography,
9538
+ {
9539
+ variant: "caption",
9540
+ className: "text-foreground font-mono whitespace-pre text-xs leading-relaxed block",
9541
+ children: jsonString
9542
+ }
9543
+ ) })
9544
+ ] });
9545
+ }
9546
+ CodeView.displayName = "CodeView";
9547
+ var ENCOURAGEMENT_KEYS3 = [
9548
+ "puzzle.tryAgain1",
9549
+ "puzzle.tryAgain2",
9550
+ "puzzle.tryAgain3"
9551
+ ];
9552
+ function layoutStates(states, width, height) {
9553
+ const cx = width / 2;
9554
+ const cy = height / 2;
9555
+ const radius = Math.min(cx, cy) - 60;
9556
+ const positions = {};
9557
+ states.forEach((state, i) => {
9558
+ const angle = 2 * Math.PI * i / states.length - Math.PI / 2;
9559
+ positions[state] = {
9560
+ x: cx + radius * Math.cos(angle),
9561
+ y: cy + radius * Math.sin(angle)
9562
+ };
9563
+ });
9564
+ return positions;
9565
+ }
9566
+ var nextTransId = 100;
9567
+ function StateArchitectBoard({
9568
+ entity,
9569
+ stepDurationMs = 600,
9570
+ testEvent,
9571
+ completeEvent,
9572
+ className
9573
+ }) {
9574
+ const { emit } = useEventBus();
9575
+ const { t } = useTranslate();
9576
+ const [transitions, setTransitions] = useState(entity.transitions);
9577
+ const [playState, setPlayState] = useState("editing");
9578
+ const [currentState, setCurrentState] = useState(entity.initialState);
9579
+ const [selectedState, setSelectedState] = useState(null);
9580
+ const [testResults, setTestResults] = useState([]);
9581
+ const [variables, setVariables] = useState(entity.variables);
9582
+ const [attempts, setAttempts] = useState(0);
9583
+ const timerRef = useRef(null);
9584
+ const [addingFrom, setAddingFrom] = useState(null);
9585
+ useEffect(() => () => {
9586
+ if (timerRef.current) clearTimeout(timerRef.current);
9587
+ }, []);
9588
+ const GRAPH_W = 500;
9589
+ const GRAPH_H = 400;
9590
+ const positions = useMemo(() => layoutStates(entity.states, GRAPH_W, GRAPH_H), [entity.states]);
9591
+ const handleStateClick = useCallback((state) => {
9592
+ if (playState !== "editing") return;
9593
+ if (addingFrom) {
9594
+ if (addingFrom !== state) {
9595
+ const event = entity.availableEvents[0] || "EVENT";
9596
+ const newTrans = {
9597
+ id: `t-${nextTransId++}`,
9598
+ from: addingFrom,
9599
+ to: state,
9600
+ event
9601
+ };
9602
+ setTransitions((prev) => [...prev, newTrans]);
9603
+ }
9604
+ setAddingFrom(null);
9605
+ } else {
9606
+ setSelectedState(state);
9607
+ }
9608
+ }, [playState, addingFrom, entity.availableEvents]);
9609
+ const handleStartAddTransition = useCallback(() => {
9610
+ if (!selectedState) return;
9611
+ setAddingFrom(selectedState);
9612
+ }, [selectedState]);
9613
+ const handleRemoveTransition = useCallback((transId) => {
9614
+ setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
9615
+ }, []);
9616
+ const machine = useMemo(() => ({
9617
+ name: entity.entityName,
9618
+ description: entity.description,
9619
+ states: entity.states,
9620
+ currentState,
9621
+ transitions: transitions.map((t2) => ({
9622
+ from: t2.from,
9623
+ to: t2.to,
9624
+ event: t2.event,
9625
+ guardHint: t2.guardHint
9626
+ }))
9627
+ }), [entity, currentState, transitions]);
9628
+ const handleTest = useCallback(() => {
9629
+ if (playState !== "editing") return;
9630
+ if (testEvent) emit(`UI:${testEvent}`, {});
9631
+ setPlayState("testing");
9632
+ setTestResults([]);
9633
+ const results = [];
9634
+ let testIdx = 0;
9635
+ const runNextTest = () => {
9636
+ if (testIdx >= entity.testCases.length) {
9637
+ const allPassed = results.every((r) => r.passed);
9638
+ setPlayState(allPassed ? "success" : "fail");
9639
+ setTestResults(results);
9640
+ if (allPassed && completeEvent) {
9641
+ emit(`UI:${completeEvent}`, {
9642
+ success: true,
9643
+ passedTests: results.filter((r) => r.passed).length
9644
+ });
9645
+ }
9646
+ if (!allPassed) {
9647
+ setAttempts((prev) => prev + 1);
9648
+ }
9649
+ return;
9650
+ }
9651
+ const testCase = entity.testCases[testIdx];
9652
+ let state = entity.initialState;
9653
+ for (const event of testCase.events) {
9654
+ const trans = transitions.find((t2) => t2.from === state && t2.event === event);
9655
+ if (trans) {
9656
+ state = trans.to;
9657
+ }
9658
+ }
9659
+ setCurrentState(state);
9660
+ results.push({
9661
+ label: testCase.label,
9662
+ passed: state === testCase.expectedState,
9663
+ actualState: state,
9664
+ expectedState: testCase.expectedState
9665
+ });
9666
+ testIdx++;
9667
+ timerRef.current = setTimeout(runNextTest, stepDurationMs);
9668
+ };
9669
+ timerRef.current = setTimeout(runNextTest, stepDurationMs);
9670
+ }, [playState, transitions, entity, stepDurationMs, testEvent, completeEvent, emit]);
9671
+ const handleTryAgain = useCallback(() => {
9672
+ if (timerRef.current) clearTimeout(timerRef.current);
9673
+ setPlayState("editing");
9674
+ setCurrentState(entity.initialState);
9675
+ setTestResults([]);
9676
+ }, [entity.initialState]);
9677
+ const handleReset = useCallback(() => {
9678
+ if (timerRef.current) clearTimeout(timerRef.current);
9679
+ setTransitions(entity.transitions);
9680
+ setPlayState("editing");
9681
+ setCurrentState(entity.initialState);
9682
+ setTestResults([]);
9683
+ setVariables(entity.variables);
9684
+ setSelectedState(null);
9685
+ setAddingFrom(null);
9686
+ setAttempts(0);
9687
+ }, [entity]);
9688
+ const codeData = useMemo(() => ({
9689
+ name: entity.entityName,
9690
+ states: entity.states,
9691
+ initialState: entity.initialState,
9692
+ transitions: transitions.map((t2) => ({
9693
+ from: t2.from,
9694
+ to: t2.to,
9695
+ event: t2.event,
9696
+ ...t2.guardHint ? { guard: t2.guardHint } : {}
9697
+ }))
9698
+ }), [entity, transitions]);
9699
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 gap-6", className), children: [
9700
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
9701
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
9702
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description }),
9703
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
9704
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
9705
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entity.hint })
9706
+ ] })
9707
+ ] }),
9708
+ /* @__PURE__ */ jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
9709
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "flex-1 min-w-[300px]", children: [
9710
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
9711
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.graph") }),
9712
+ addingFrom && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent animate-pulse", children: t("stateArchitect.clickTarget", { state: addingFrom || "" }) })
9713
+ ] }),
9714
+ /* @__PURE__ */ jsxs(
9715
+ Box,
9716
+ {
9717
+ position: "relative",
9718
+ className: "rounded-lg border border-border bg-background overflow-hidden",
9719
+ style: { width: GRAPH_W, height: GRAPH_H },
9720
+ children: [
9721
+ /* @__PURE__ */ jsxs(
9722
+ "svg",
9723
+ {
9724
+ width: GRAPH_W,
9725
+ height: GRAPH_H,
9726
+ className: "absolute inset-0",
9727
+ style: { pointerEvents: "none" },
9728
+ children: [
9729
+ /* @__PURE__ */ jsxs("defs", { children: [
9730
+ /* @__PURE__ */ jsx("marker", { id: "arrowhead", markerWidth: "10", markerHeight: "7", refX: "10", refY: "3.5", orient: "auto", children: /* @__PURE__ */ jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: "var(--color-border)" }) }),
9731
+ /* @__PURE__ */ jsx("marker", { id: "arrowhead-active", markerWidth: "10", markerHeight: "7", refX: "10", refY: "3.5", orient: "auto", children: /* @__PURE__ */ jsx("polygon", { points: "0 0, 10 3.5, 0 7", fill: "var(--color-primary)" }) })
9732
+ ] }),
9733
+ transitions.map((t2) => {
9734
+ const fromPos = positions[t2.from];
9735
+ const toPos = positions[t2.to];
9736
+ if (!fromPos || !toPos) return null;
9737
+ const isActive = t2.from === currentState;
9738
+ return /* @__PURE__ */ jsx(
9739
+ TransitionArrow,
9740
+ {
9741
+ from: fromPos,
9742
+ to: toPos,
9743
+ eventLabel: t2.event,
9744
+ guardHint: t2.guardHint,
9745
+ isActive
9746
+ },
9747
+ t2.id
9748
+ );
9749
+ })
9750
+ ]
9751
+ }
9752
+ ),
9753
+ entity.states.map((state) => /* @__PURE__ */ jsx(
9754
+ StateNode2,
9755
+ {
9756
+ name: state,
9757
+ position: positions[state],
9758
+ isCurrent: state === currentState,
9759
+ isSelected: state === selectedState,
9760
+ isInitial: state === entity.initialState,
9761
+ onClick: () => handleStateClick(state)
9762
+ },
9763
+ state
9764
+ ))
9765
+ ]
9766
+ }
9767
+ ),
9768
+ playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
9769
+ Button,
9770
+ {
9771
+ variant: "ghost",
9772
+ onClick: handleStartAddTransition,
9773
+ disabled: !selectedState,
9774
+ children: selectedState ? t("stateArchitect.addTransition", { state: selectedState }) : t("stateArchitect.addTransitionPrompt")
9775
+ }
9776
+ ) }),
9777
+ transitions.length > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "p-3 rounded-lg bg-muted/50 border border-border", children: [
9778
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground font-medium", children: t("stateArchitect.transitions", { count: transitions.length }) + ":" }),
9779
+ transitions.map((t2) => /* @__PURE__ */ jsxs(HStack, { className: "items-center text-xs", gap: "xs", children: [
9780
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: t2.from }),
9781
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: "\u2014[" }),
9782
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent font-medium", children: t2.event }),
9783
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: "]\u2192" }),
9784
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-success", children: t2.to }),
9785
+ t2.guardHint && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-warning", children: [
9786
+ "(",
9787
+ t2.guardHint,
9788
+ ")"
9789
+ ] }),
9790
+ playState === "editing" && /* @__PURE__ */ jsx(
9791
+ Button,
9792
+ {
9793
+ variant: "ghost",
9794
+ onClick: () => handleRemoveTransition(t2.id),
9795
+ className: "text-xs ml-auto",
9796
+ children: "\xD7"
9797
+ }
9798
+ )
9799
+ ] }, t2.id))
9800
+ ] })
9801
+ ] }),
9802
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "w-[280px] shrink-0", children: [
9803
+ /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "full", size: "sm" }),
9804
+ /* @__PURE__ */ jsx(
9805
+ VariablePanel,
9806
+ {
9807
+ entityName: entity.entityName,
9808
+ variables
9809
+ }
9810
+ ),
9811
+ testResults.length > 0 && /* @__PURE__ */ jsxs(VStack, { className: "p-3 rounded-lg bg-card border border-border", gap: "xs", children: [
9812
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.testResults") + ":" }),
9813
+ testResults.map((r, i) => /* @__PURE__ */ jsxs(HStack, { className: "items-center text-xs", gap: "xs", children: [
9814
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: r.passed ? "text-success" : "text-error", children: r.passed ? "\u2714" : "\u2717" }),
9815
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground flex-1", children: r.label }),
9816
+ !r.passed && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-error", children: t("stateArchitect.gotState", { state: r.actualState }) })
9817
+ ] }, i))
9818
+ ] }),
9819
+ entity.showCodeView !== false && /* @__PURE__ */ jsx(CodeView, { data: codeData, label: "View Code" })
9820
+ ] })
9821
+ ] }),
9822
+ playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-lg bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: entity.successMessage || t("stateArchitect.allPassed") }) }),
9823
+ playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9824
+ /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-lg bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
9825
+ attempts >= 3 && entity.hint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-lg bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
9826
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
9827
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
9828
+ ] }) })
9829
+ ] }),
9830
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
9831
+ playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
9832
+ Button,
9833
+ {
9834
+ variant: "primary",
9835
+ onClick: handleTest,
9836
+ disabled: playState !== "editing",
9837
+ children: "\u25B6 " + t("game.runTests")
9838
+ }
9839
+ ),
9840
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
9841
+ ] })
9842
+ ] });
9843
+ }
9844
+ StateArchitectBoard.displayName = "StateArchitectBoard";
9845
+ function SimulatorBoard({
9846
+ entity,
9847
+ completeEvent = "PUZZLE_COMPLETE",
9848
+ className
9849
+ }) {
9850
+ const { emit } = useEventBus();
9851
+ const { t } = useTranslate();
9852
+ const [values, setValues] = useState(() => {
9853
+ const init = {};
9854
+ for (const p2 of entity.parameters) {
9855
+ init[p2.id] = p2.initial;
9856
+ }
9857
+ return init;
9858
+ });
9859
+ const [submitted, setSubmitted] = useState(false);
9860
+ const [attempts, setAttempts] = useState(0);
9861
+ const [showHint, setShowHint] = useState(false);
9862
+ const computeOutput = useCallback((params) => {
9863
+ try {
9864
+ const fn = new Function("params", `return (${entity.computeExpression})`);
9865
+ return fn(params);
9866
+ } catch {
9867
+ return 0;
9868
+ }
9869
+ }, [entity.computeExpression]);
9870
+ const output = useMemo(() => computeOutput(values), [computeOutput, values]);
9871
+ const isCorrect = Math.abs(output - entity.targetValue) <= entity.targetTolerance;
9872
+ const handleParameterChange = (id, value) => {
9873
+ if (submitted) return;
9874
+ setValues((prev) => ({ ...prev, [id]: value }));
9875
+ };
9876
+ const handleSubmit = () => {
9877
+ setSubmitted(true);
9878
+ setAttempts((a) => a + 1);
9879
+ if (isCorrect) {
9880
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
9881
+ }
9882
+ };
9883
+ const handleReset = () => {
9884
+ setSubmitted(false);
9885
+ if (attempts >= 2 && entity.hint) {
9886
+ setShowHint(true);
9887
+ }
9888
+ };
9889
+ const handleFullReset = () => {
9890
+ const init = {};
9891
+ for (const p2 of entity.parameters) {
9892
+ init[p2.id] = p2.initial;
9893
+ }
9894
+ setValues(init);
9895
+ setSubmitted(false);
9896
+ setAttempts(0);
9897
+ setShowHint(false);
9898
+ };
9899
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
9900
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9901
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
9902
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
9903
+ ] }) }),
9904
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", children: [
9905
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("simulator.parameters") }),
9906
+ entity.parameters.map((param) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
9907
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
9908
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
9909
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
9910
+ values[param.id],
9911
+ " ",
9912
+ param.unit
9913
+ ] })
9914
+ ] }),
9915
+ /* @__PURE__ */ jsx(
9916
+ "input",
9917
+ {
9918
+ type: "range",
9919
+ min: param.min,
9920
+ max: param.max,
9921
+ step: param.step,
9922
+ value: values[param.id],
9923
+ onChange: (e) => handleParameterChange(param.id, Number(e.target.value)),
9924
+ disabled: submitted,
9925
+ className: "w-full accent-[var(--color-foreground)]"
9926
+ }
9927
+ ),
9928
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
9929
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
9930
+ param.min,
9931
+ " ",
9932
+ param.unit
9933
+ ] }),
9934
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
9935
+ param.max,
9936
+ " ",
9937
+ param.unit
9938
+ ] })
9939
+ ] })
9940
+ ] }, param.id))
9941
+ ] }) }),
9942
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
9943
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: entity.outputLabel }),
9944
+ /* @__PURE__ */ jsxs(Typography, { variant: "h3", weight: "bold", children: [
9945
+ output.toFixed(2),
9946
+ " ",
9947
+ entity.outputUnit
9948
+ ] }),
9949
+ submitted && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
9950
+ /* @__PURE__ */ jsx(Icon, { icon: isCorrect ? CheckCircle : XCircle, size: "sm", className: isCorrect ? "text-green-600" : "text-red-600" }),
9951
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: isCorrect ? "text-green-600" : "text-red-600", children: isCorrect ? entity.successMessage ?? t("simulator.correct") : entity.failMessage ?? t("simulator.incorrect") })
9952
+ ] }),
9953
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
9954
+ t("simulator.target"),
9955
+ ": ",
9956
+ entity.targetValue,
9957
+ " ",
9958
+ entity.outputUnit,
9959
+ " (\xB1",
9960
+ entity.targetTolerance,
9961
+ ")"
9962
+ ] })
9963
+ ] }) }),
9964
+ showHint && entity.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-yellow-500", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.hint }) }),
9965
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
9966
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
9967
+ /* @__PURE__ */ jsx(Icon, { icon: Play, size: "sm" }),
9968
+ t("simulator.simulate")
9969
+ ] }) : !isCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("simulator.tryAgain") }) : null,
9970
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
9971
+ /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
9972
+ t("simulator.reset")
9973
+ ] })
9974
+ ] })
9975
+ ] }) });
9976
+ }
9977
+ SimulatorBoard.displayName = "SimulatorBoard";
9978
+ function ClassifierBoard({
9979
+ entity,
9980
+ completeEvent = "PUZZLE_COMPLETE",
9981
+ className
9982
+ }) {
9983
+ const { emit } = useEventBus();
9984
+ const { t } = useTranslate();
9985
+ const [assignments, setAssignments] = useState({});
9986
+ const [submitted, setSubmitted] = useState(false);
9987
+ const [attempts, setAttempts] = useState(0);
9988
+ const [showHint, setShowHint] = useState(false);
9989
+ const unassignedItems = entity.items.filter((item) => !assignments[item.id]);
9990
+ const allAssigned = Object.keys(assignments).length === entity.items.length;
9991
+ const results = submitted ? entity.items.map((item) => ({
9992
+ item,
9993
+ assigned: assignments[item.id],
9994
+ correct: assignments[item.id] === item.correctCategory
9995
+ })) : [];
9996
+ const allCorrect = results.length > 0 && results.every((r) => r.correct);
9997
+ const correctCount = results.filter((r) => r.correct).length;
9998
+ const handleAssign = (itemId, categoryId) => {
9999
+ if (submitted) return;
10000
+ setAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
10001
+ };
10002
+ const handleUnassign = (itemId) => {
10003
+ if (submitted) return;
10004
+ setAssignments((prev) => {
10005
+ const next = { ...prev };
10006
+ delete next[itemId];
10007
+ return next;
10008
+ });
10009
+ };
10010
+ const handleSubmit = useCallback(() => {
10011
+ setSubmitted(true);
10012
+ setAttempts((a) => a + 1);
10013
+ const correct = entity.items.every((item) => assignments[item.id] === item.correctCategory);
10014
+ if (correct) {
10015
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
10016
+ }
10017
+ }, [entity.items, assignments, attempts, completeEvent, emit]);
10018
+ const handleReset = () => {
10019
+ setSubmitted(false);
10020
+ if (attempts >= 2 && entity.hint) {
10021
+ setShowHint(true);
10022
+ }
10023
+ };
10024
+ const handleFullReset = () => {
10025
+ setAssignments({});
10026
+ setSubmitted(false);
10027
+ setAttempts(0);
10028
+ setShowHint(false);
10029
+ };
10030
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
10031
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10032
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
10033
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
10034
+ ] }) }),
10035
+ unassignedItems.length > 0 && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10036
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("classifier.itemsToSort") }),
10037
+ /* @__PURE__ */ jsx(HStack, { gap: "sm", className: "flex-wrap", children: unassignedItems.map((item) => /* @__PURE__ */ jsx(Badge, { size: "md", className: "cursor-pointer", children: item.label }, item.id)) })
10038
+ ] }) }),
10039
+ /* @__PURE__ */ jsx(VStack, { gap: "md", children: entity.categories.map((cat) => {
10040
+ const catItems = entity.items.filter((item) => assignments[item.id] === cat.id);
10041
+ return /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10042
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
10043
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: cat.label }),
10044
+ /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
10045
+ ] }),
10046
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
10047
+ const result = results.find((r) => r.item.id === item.id);
10048
+ return /* @__PURE__ */ jsxs(
10049
+ Badge,
10050
+ {
10051
+ size: "sm",
10052
+ className: `cursor-pointer ${result ? result.correct ? "border-green-500 bg-green-50 dark:bg-green-950" : "border-red-500 bg-red-50 dark:bg-red-950" : ""}`,
10053
+ onClick: () => handleUnassign(item.id),
10054
+ children: [
10055
+ item.label,
10056
+ result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-green-600" : "text-red-600" })
10057
+ ]
10058
+ },
10059
+ item.id
10060
+ );
10061
+ }) }),
10062
+ !submitted && unassignedItems.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: unassignedItems.map((item) => /* @__PURE__ */ jsxs(
10063
+ Button,
10064
+ {
10065
+ size: "sm",
10066
+ variant: "ghost",
10067
+ onClick: () => handleAssign(item.id, cat.id),
10068
+ className: "text-xs opacity-50 hover:opacity-100",
10069
+ children: [
10070
+ "+ ",
10071
+ item.label
10072
+ ]
10073
+ },
10074
+ item.id
10075
+ )) })
10076
+ ] }) }, cat.id);
10077
+ }) }),
10078
+ submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
10079
+ /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-green-600" : "text-red-600" }),
10080
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${entity.items.length} ${t("classifier.correct")}` }),
10081
+ !allCorrect && entity.failMessage && /* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-[var(--color-muted-foreground)]", children: entity.failMessage })
10082
+ ] }) }),
10083
+ showHint && entity.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-yellow-500", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.hint }) }),
10084
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
10085
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
10086
+ /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
10087
+ t("classifier.check")
10088
+ ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
10089
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
10090
+ /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
10091
+ t("classifier.reset")
10092
+ ] })
10093
+ ] })
10094
+ ] }) });
10095
+ }
10096
+ ClassifierBoard.displayName = "ClassifierBoard";
10097
+ function BuilderBoard({
10098
+ entity,
10099
+ completeEvent = "PUZZLE_COMPLETE",
10100
+ className
10101
+ }) {
10102
+ const { emit } = useEventBus();
10103
+ const { t } = useTranslate();
10104
+ const [placements, setPlacements] = useState({});
10105
+ const [submitted, setSubmitted] = useState(false);
10106
+ const [attempts, setAttempts] = useState(0);
10107
+ const [showHint, setShowHint] = useState(false);
10108
+ const usedComponentIds = new Set(Object.values(placements));
10109
+ const availableComponents = entity.components.filter((c) => !usedComponentIds.has(c.id));
10110
+ const [selectedComponent, setSelectedComponent] = useState(null);
10111
+ const allPlaced = Object.keys(placements).length === entity.slots.length;
10112
+ const results = submitted ? entity.slots.map((slot) => ({
10113
+ slot,
10114
+ placed: placements[slot.id],
10115
+ correct: placements[slot.id] === slot.acceptsComponentId
10116
+ })) : [];
10117
+ const allCorrect = results.length > 0 && results.every((r) => r.correct);
10118
+ const handlePlaceComponent = (slotId) => {
10119
+ if (submitted || !selectedComponent) return;
10120
+ setPlacements((prev) => ({ ...prev, [slotId]: selectedComponent }));
10121
+ setSelectedComponent(null);
10122
+ };
10123
+ const handleRemoveFromSlot = (slotId) => {
10124
+ if (submitted) return;
10125
+ setPlacements((prev) => {
10126
+ const next = { ...prev };
10127
+ delete next[slotId];
10128
+ return next;
10129
+ });
10130
+ };
10131
+ const handleSubmit = useCallback(() => {
10132
+ setSubmitted(true);
10133
+ setAttempts((a) => a + 1);
10134
+ const correct = entity.slots.every((slot) => placements[slot.id] === slot.acceptsComponentId);
10135
+ if (correct) {
10136
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
10137
+ }
10138
+ }, [entity.slots, placements, attempts, completeEvent, emit]);
10139
+ const handleReset = () => {
10140
+ setSubmitted(false);
10141
+ if (attempts >= 2 && entity.hint) {
10142
+ setShowHint(true);
10143
+ }
10144
+ };
10145
+ const handleFullReset = () => {
10146
+ setPlacements({});
10147
+ setSubmitted(false);
10148
+ setSelectedComponent(null);
10149
+ setAttempts(0);
10150
+ setShowHint(false);
10151
+ };
10152
+ const getComponentById = (id) => entity.components.find((c) => c.id === id);
10153
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
10154
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10155
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
10156
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
10157
+ ] }) }),
10158
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10159
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("builder.components") }),
10160
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "flex-wrap", children: [
10161
+ availableComponents.map((comp) => /* @__PURE__ */ jsxs(
10162
+ Button,
10163
+ {
10164
+ size: "sm",
10165
+ variant: selectedComponent === comp.id ? "primary" : "secondary",
10166
+ onClick: () => setSelectedComponent(selectedComponent === comp.id ? null : comp.id),
10167
+ disabled: submitted,
10168
+ children: [
10169
+ comp.iconEmoji && `${comp.iconEmoji} `,
10170
+ comp.label
10171
+ ]
10172
+ },
10173
+ comp.id
10174
+ )),
10175
+ availableComponents.length === 0 && !submitted && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("builder.allPlaced") })
10176
+ ] })
10177
+ ] }) }),
10178
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10179
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("builder.blueprint") }),
10180
+ /* @__PURE__ */ jsx(VStack, { gap: "sm", children: entity.slots.map((slot) => {
10181
+ const placedComp = placements[slot.id] ? getComponentById(placements[slot.id]) : null;
10182
+ const result = results.find((r) => r.slot.id === slot.id);
10183
+ return /* @__PURE__ */ jsxs(
10184
+ HStack,
10185
+ {
10186
+ gap: "sm",
10187
+ align: "center",
10188
+ className: `p-3 border-2 rounded ${result ? result.correct ? "border-green-500" : "border-red-500" : selectedComponent ? "border-dashed border-[var(--color-foreground)] cursor-pointer" : "border-[var(--color-border)]"}`,
10189
+ onClick: () => handlePlaceComponent(slot.id),
10190
+ children: [
10191
+ /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "flex-1", children: [
10192
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: slot.label }),
10193
+ slot.description && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: slot.description })
10194
+ ] }),
10195
+ placedComp ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
10196
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", onClick: () => handleRemoveFromSlot(slot.id), children: [
10197
+ placedComp.iconEmoji && `${placedComp.iconEmoji} `,
10198
+ placedComp.label
10199
+ ] }),
10200
+ result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "sm", className: result.correct ? "text-green-600" : "text-red-600" })
10201
+ ] }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("builder.empty") })
10202
+ ]
10203
+ },
10204
+ slot.id
10205
+ );
10206
+ }) })
10207
+ ] }) }),
10208
+ submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
10209
+ /* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-green-600" : "text-red-600" }),
10210
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("builder.success") : entity.failMessage ?? t("builder.incorrect") })
10211
+ ] }) }),
10212
+ showHint && entity.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-yellow-500", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.hint }) }),
10213
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
10214
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
10215
+ /* @__PURE__ */ jsx(Icon, { icon: Wrench, size: "sm" }),
10216
+ t("builder.build")
10217
+ ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("builder.tryAgain") }) : null,
10218
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
10219
+ /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
10220
+ t("builder.reset")
10221
+ ] })
10222
+ ] })
10223
+ ] }) });
10224
+ }
10225
+ BuilderBoard.displayName = "BuilderBoard";
10226
+ function DebuggerBoard({
10227
+ entity,
10228
+ completeEvent = "PUZZLE_COMPLETE",
10229
+ className
10230
+ }) {
10231
+ const { emit } = useEventBus();
10232
+ const { t } = useTranslate();
10233
+ const [flaggedLines, setFlaggedLines] = useState(/* @__PURE__ */ new Set());
10234
+ const [submitted, setSubmitted] = useState(false);
10235
+ const [attempts, setAttempts] = useState(0);
10236
+ const [showHint, setShowHint] = useState(false);
10237
+ const toggleLine = (lineId) => {
10238
+ if (submitted) return;
10239
+ setFlaggedLines((prev) => {
10240
+ const next = new Set(prev);
10241
+ if (next.has(lineId)) {
10242
+ next.delete(lineId);
10243
+ } else {
10244
+ next.add(lineId);
10245
+ }
10246
+ return next;
10247
+ });
10248
+ };
10249
+ const bugLines = entity.lines.filter((l) => l.isBug);
10250
+ const correctFlags = entity.lines.filter((l) => l.isBug && flaggedLines.has(l.id));
10251
+ const falseFlags = entity.lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
10252
+ const allCorrect = submitted && correctFlags.length === bugLines.length && falseFlags.length === 0;
10253
+ const handleSubmit = useCallback(() => {
10254
+ setSubmitted(true);
10255
+ setAttempts((a) => a + 1);
10256
+ const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
10257
+ if (correct) {
10258
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
10259
+ }
10260
+ }, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
10261
+ const handleReset = () => {
10262
+ setSubmitted(false);
10263
+ if (attempts >= 2 && entity.hint) {
10264
+ setShowHint(true);
10265
+ }
10266
+ };
10267
+ const handleFullReset = () => {
10268
+ setFlaggedLines(/* @__PURE__ */ new Set());
10269
+ setSubmitted(false);
10270
+ setAttempts(0);
10271
+ setShowHint(false);
10272
+ };
10273
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
10274
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10275
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
10276
+ /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "sm" }),
10277
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title })
10278
+ ] }),
10279
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description }),
10280
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("debugger.findBugs", { count: String(entity.bugCount) }) })
10281
+ ] }) }),
10282
+ /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: entity.lines.map((line, i) => {
10283
+ const isFlagged = flaggedLines.has(line.id);
10284
+ let lineStyle = "";
10285
+ if (submitted) {
10286
+ if (line.isBug && isFlagged) lineStyle = "bg-green-50 dark:bg-green-950";
10287
+ else if (line.isBug && !isFlagged) lineStyle = "bg-yellow-50 dark:bg-yellow-950";
10288
+ else if (!line.isBug && isFlagged) lineStyle = "bg-red-50 dark:bg-red-950";
10289
+ } else if (isFlagged) {
10290
+ lineStyle = "bg-red-50 dark:bg-red-950";
10291
+ }
10292
+ return /* @__PURE__ */ jsxs(
10293
+ HStack,
10294
+ {
10295
+ gap: "none",
10296
+ align: "stretch",
10297
+ className: `border-b border-[var(--color-border)] cursor-pointer hover:bg-[var(--color-muted)] ${lineStyle}`,
10298
+ onClick: () => toggleLine(line.id),
10299
+ children: [
10300
+ /* @__PURE__ */ jsx(Box, { className: "w-10 flex-shrink-0 flex items-center justify-center border-r border-[var(--color-border)] text-[var(--color-muted-foreground)]", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", children: i + 1 }) }),
10301
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 px-3 py-1.5 font-mono text-sm whitespace-pre", children: /* @__PURE__ */ jsx(Typography, { variant: "body", className: "font-mono text-sm", children: line.content }) }),
10302
+ /* @__PURE__ */ jsxs(Box, { className: "w-8 flex-shrink-0 flex items-center justify-center", children: [
10303
+ isFlagged && /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "xs", className: "text-red-600" }),
10304
+ submitted && line.isBug && !isFlagged && /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "xs", className: "text-yellow-600" })
10305
+ ] })
10306
+ ]
10307
+ },
10308
+ line.id
10309
+ );
10310
+ }) }) }),
10311
+ submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10312
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
10313
+ bugLines.map((line) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "start", children: [
10314
+ /* @__PURE__ */ jsx(
10315
+ Icon,
10316
+ {
10317
+ icon: flaggedLines.has(line.id) ? CheckCircle : XCircle,
10318
+ size: "xs",
10319
+ className: flaggedLines.has(line.id) ? "text-green-600 mt-0.5" : "text-yellow-600 mt-0.5"
10320
+ }
10321
+ ),
10322
+ /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
10323
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", className: "font-mono", children: line.content.trim() }),
10324
+ line.explanation && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: line.explanation })
10325
+ ] })
10326
+ ] }, line.id))
10327
+ ] }) }),
10328
+ showHint && entity.hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-yellow-500", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.hint }) }),
10329
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
10330
+ !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
10331
+ /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
10332
+ t("debugger.submit")
10333
+ ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
10334
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
10335
+ /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
10336
+ t("debugger.reset")
10337
+ ] })
10338
+ ] })
10339
+ ] }) });
10340
+ }
10341
+ DebuggerBoard.displayName = "DebuggerBoard";
10342
+ function getOpponentAction(strategy, actions, history) {
10343
+ const actionIds = actions.map((a) => a.id);
10344
+ switch (strategy) {
10345
+ case "always-cooperate":
10346
+ return actionIds[0];
10347
+ case "always-defect":
10348
+ return actionIds[actionIds.length - 1];
10349
+ case "tit-for-tat":
10350
+ if (history.length === 0) return actionIds[0];
10351
+ return history[history.length - 1].playerAction;
10352
+ case "random":
10353
+ default:
10354
+ return actionIds[Math.floor(Math.random() * actionIds.length)];
10355
+ }
10356
+ }
10357
+ function NegotiatorBoard({
10358
+ entity,
10359
+ completeEvent = "PUZZLE_COMPLETE",
10360
+ className
10361
+ }) {
10362
+ const { emit } = useEventBus();
10363
+ const { t } = useTranslate();
10364
+ const [history, setHistory] = useState([]);
10365
+ const [showHint, setShowHint] = useState(false);
10366
+ const currentRound = history.length;
10367
+ const isComplete = currentRound >= entity.totalRounds;
10368
+ const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
10369
+ const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
10370
+ const won = isComplete && playerTotal >= entity.targetScore;
10371
+ const handleAction = useCallback((actionId) => {
10372
+ if (isComplete) return;
10373
+ const opponentAction = getOpponentAction(entity.opponentStrategy, entity.actions, history);
10374
+ const payoff = entity.payoffMatrix.find(
10375
+ (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
10376
+ );
10377
+ const result = {
10378
+ round: currentRound + 1,
10379
+ playerAction: actionId,
10380
+ opponentAction,
10381
+ playerPayoff: payoff?.playerPayoff ?? 0,
10382
+ opponentPayoff: payoff?.opponentPayoff ?? 0
10383
+ };
10384
+ const newHistory = [...history, result];
10385
+ setHistory(newHistory);
10386
+ if (newHistory.length >= entity.totalRounds) {
10387
+ const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
10388
+ if (total >= entity.targetScore) {
10389
+ emit(`UI:${completeEvent}`, { success: true, score: total });
10390
+ }
10391
+ if (newHistory.length >= 3 && entity.hint) {
10392
+ setShowHint(true);
10393
+ }
10394
+ }
10395
+ }, [isComplete, entity, history, currentRound, completeEvent, emit]);
10396
+ const handleReset = () => {
10397
+ setHistory([]);
10398
+ setShowHint(false);
10399
+ };
10400
+ const getActionLabel = (id) => entity.actions.find((a) => a.id === id)?.label ?? id;
10401
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
10402
+ /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10403
+ /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
10404
+ /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description }),
10405
+ /* @__PURE__ */ jsxs(HStack, { gap: "md", children: [
10406
+ /* @__PURE__ */ jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(entity.totalRounds) }) }),
10407
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
10408
+ t("negotiator.target"),
10409
+ ": ",
10410
+ entity.targetScore
10411
+ ] })
10412
+ ] })
10413
+ ] }) }),
10414
+ /* @__PURE__ */ jsxs(HStack, { gap: "md", justify: "center", children: [
10415
+ /* @__PURE__ */ jsx(Card, { className: "p-4 flex-1 text-center", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", children: [
10416
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("negotiator.you") }),
10417
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "bold", children: playerTotal })
10418
+ ] }) }),
10419
+ /* @__PURE__ */ jsx(Card, { className: "p-4 flex-1 text-center", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", children: [
10420
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("negotiator.opponent") }),
10421
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "bold", children: opponentTotal })
10422
+ ] }) })
10423
+ ] }),
10424
+ !isComplete && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10425
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("negotiator.chooseAction") }),
10426
+ /* @__PURE__ */ jsx(HStack, { gap: "sm", justify: "center", className: "flex-wrap", children: entity.actions.map((action) => /* @__PURE__ */ jsx(
10427
+ Button,
10428
+ {
10429
+ variant: "primary",
10430
+ onClick: () => handleAction(action.id),
10431
+ children: action.label
10432
+ },
10433
+ action.id
10434
+ )) })
10435
+ ] }) }),
10436
+ history.length > 0 && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10437
+ /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("negotiator.history") }),
10438
+ history.map((round) => /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className: "text-sm", children: [
10439
+ /* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
10440
+ "R",
10441
+ round.round
10442
+ ] }),
10443
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", children: getActionLabel(round.playerAction) }),
10444
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: "vs" }),
10445
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", children: getActionLabel(round.opponentAction) }),
10446
+ /* @__PURE__ */ jsx(Icon, { icon: ArrowRight, size: "xs" }),
10447
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", weight: "bold", className: "text-green-600", children: [
10448
+ "+",
10449
+ round.playerPayoff
10450
+ ] }),
10451
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
10452
+ "/ +",
10453
+ round.opponentPayoff
10454
+ ] })
10455
+ ] }, round.round))
10456
+ ] }) }),
10457
+ isComplete && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
10458
+ /* @__PURE__ */ jsx(Icon, { icon: CheckCircle, size: "lg", className: won ? "text-green-600" : "text-red-600" }),
10459
+ /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: won ? entity.successMessage ?? t("negotiator.success") : entity.failMessage ?? t("negotiator.failed") }),
10460
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
10461
+ t("negotiator.finalScore"),
10462
+ ": ",
10463
+ playerTotal,
10464
+ "/",
10465
+ entity.targetScore
10466
+ ] })
10467
+ ] }) }),
10468
+ showHint && entity.hint && !won && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-yellow-500", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.hint }) }),
10469
+ isComplete && !won && /* @__PURE__ */ jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
10470
+ ] }) });
10471
+ }
10472
+ NegotiatorBoard.displayName = "NegotiatorBoard";
10473
+ function SimulationCanvas({
10474
+ preset,
10475
+ width = 600,
10476
+ height = 400,
10477
+ running,
10478
+ speed = 1,
10479
+ className
10480
+ }) {
10481
+ const canvasRef = useRef(null);
10482
+ const bodiesRef = useRef(structuredClone(preset.bodies));
10483
+ useEffect(() => {
10484
+ bodiesRef.current = structuredClone(preset.bodies);
10485
+ }, [preset]);
10486
+ const step = useCallback(() => {
10487
+ const dt = 1 / 60 * speed;
10488
+ const gx = preset.gravity?.x ?? 0;
10489
+ const gy = preset.gravity?.y ?? 9.81;
10490
+ const bodies = bodiesRef.current;
10491
+ for (const body of bodies) {
10492
+ if (body.fixed) continue;
10493
+ body.vx += gx * dt;
10494
+ body.vy += gy * dt;
10495
+ body.x += body.vx * dt;
10496
+ body.y += body.vy * dt;
10497
+ if (body.y + body.radius > height) {
10498
+ body.y = height - body.radius;
10499
+ body.vy = -body.vy * 0.8;
10500
+ }
10501
+ if (body.x + body.radius > width) {
10502
+ body.x = width - body.radius;
10503
+ body.vx = -body.vx * 0.8;
10504
+ }
10505
+ if (body.x - body.radius < 0) {
10506
+ body.x = body.radius;
10507
+ body.vx = -body.vx * 0.8;
10508
+ }
10509
+ }
10510
+ if (preset.constraints) {
10511
+ for (const c of preset.constraints) {
10512
+ const a = bodies[c.bodyA];
10513
+ const b = bodies[c.bodyB];
10514
+ if (!a || !b) continue;
10515
+ const dx = b.x - a.x;
10516
+ const dy = b.y - a.y;
10517
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1e-3;
10518
+ const diff = (dist - c.length) / dist;
10519
+ const fx = dx * diff * c.stiffness;
10520
+ const fy = dy * diff * c.stiffness;
10521
+ if (!a.fixed) {
10522
+ a.vx += fx * dt;
10523
+ a.vy += fy * dt;
10524
+ }
10525
+ if (!b.fixed) {
10526
+ b.vx -= fx * dt;
10527
+ b.vy -= fy * dt;
10528
+ }
10529
+ }
10530
+ }
10531
+ }, [preset, width, height, speed]);
10532
+ const draw = useCallback(() => {
10533
+ const canvas = canvasRef.current;
10534
+ if (!canvas) return;
10535
+ const ctx = canvas.getContext("2d");
10536
+ if (!ctx) return;
10537
+ const bodies = bodiesRef.current;
10538
+ ctx.clearRect(0, 0, width, height);
10539
+ ctx.fillStyle = preset.backgroundColor ?? "#1a1a2e";
10540
+ ctx.fillRect(0, 0, width, height);
10541
+ if (preset.constraints) {
10542
+ for (const c of preset.constraints) {
10543
+ const a = bodies[c.bodyA];
10544
+ const b = bodies[c.bodyB];
10545
+ if (a && b) {
10546
+ ctx.beginPath();
10547
+ ctx.moveTo(a.x, a.y);
10548
+ ctx.lineTo(b.x, b.y);
10549
+ ctx.strokeStyle = "#533483";
10550
+ ctx.lineWidth = 1;
10551
+ ctx.setLineDash([4, 4]);
10552
+ ctx.stroke();
10553
+ ctx.setLineDash([]);
10554
+ }
10555
+ }
10556
+ }
10557
+ for (const body of bodies) {
10558
+ ctx.beginPath();
10559
+ ctx.arc(body.x, body.y, body.radius, 0, Math.PI * 2);
10560
+ ctx.fillStyle = body.color ?? "#e94560";
10561
+ ctx.fill();
10562
+ if (preset.showVelocity) {
10563
+ ctx.beginPath();
10564
+ ctx.moveTo(body.x, body.y);
10565
+ ctx.lineTo(body.x + body.vx * 0.1, body.y + body.vy * 0.1);
10566
+ ctx.strokeStyle = "#16213e";
10567
+ ctx.lineWidth = 2;
10568
+ ctx.stroke();
10569
+ }
10570
+ }
10571
+ }, [width, height, preset]);
10572
+ useEffect(() => {
10573
+ if (!running) return;
10574
+ let raf;
10575
+ const loop = () => {
10576
+ step();
10577
+ draw();
10578
+ raf = requestAnimationFrame(loop);
10579
+ };
10580
+ raf = requestAnimationFrame(loop);
10581
+ return () => cancelAnimationFrame(raf);
10582
+ }, [running, step, draw]);
10583
+ useEffect(() => {
10584
+ draw();
10585
+ }, [draw]);
10586
+ return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded-md" }) });
10587
+ }
10588
+ SimulationCanvas.displayName = "SimulationCanvas";
10589
+ function SimulationControls({
10590
+ running,
10591
+ speed,
10592
+ parameters,
10593
+ onPlay,
10594
+ onPause,
10595
+ onStep,
10596
+ onReset,
10597
+ onSpeedChange,
10598
+ onParameterChange,
10599
+ className
10600
+ }) {
10601
+ return /* @__PURE__ */ jsxs(VStack, { gap: "md", className, children: [
10602
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
10603
+ running ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: onPause, icon: Pause, children: "Pause" }) : /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", onClick: onPlay, icon: Play, children: "Play" }),
10604
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onStep, icon: SkipForward, disabled: running, children: "Step" }),
10605
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onReset, icon: RotateCcw, children: "Reset" })
10606
+ ] }),
10607
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
10608
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
10609
+ "Speed: ",
10610
+ speed.toFixed(1),
10611
+ "x"
10612
+ ] }),
10613
+ /* @__PURE__ */ jsx(
10614
+ "input",
10615
+ {
10616
+ type: "range",
10617
+ min: 0.1,
10618
+ max: 5,
10619
+ step: 0.1,
10620
+ value: speed,
10621
+ onChange: (e) => onSpeedChange(parseFloat(e.target.value)),
10622
+ className: "w-full"
10623
+ }
10624
+ )
10625
+ ] }),
10626
+ Object.entries(parameters).map(([name, param]) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
10627
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
10628
+ param.label,
10629
+ ": ",
10630
+ param.value.toFixed(2)
10631
+ ] }),
10632
+ /* @__PURE__ */ jsx(
10633
+ "input",
10634
+ {
10635
+ type: "range",
10636
+ min: param.min,
10637
+ max: param.max,
10638
+ step: param.step,
10639
+ value: param.value,
10640
+ onChange: (e) => onParameterChange(name, parseFloat(e.target.value)),
10641
+ className: "w-full"
10642
+ }
10643
+ )
10644
+ ] }, name))
10645
+ ] });
10646
+ }
10647
+ SimulationControls.displayName = "SimulationControls";
10648
+ function SimulationGraph({
10649
+ label,
10650
+ unit,
10651
+ data,
10652
+ maxPoints = 200,
10653
+ width = 300,
10654
+ height = 120,
10655
+ color = "#e94560",
10656
+ className
10657
+ }) {
10658
+ const canvasRef = useRef(null);
10659
+ const visibleData = data.slice(-maxPoints);
10660
+ useEffect(() => {
10661
+ const canvas = canvasRef.current;
10662
+ if (!canvas || visibleData.length < 2) return;
10663
+ const ctx = canvas.getContext("2d");
10664
+ if (!ctx) return;
10665
+ ctx.clearRect(0, 0, width, height);
10666
+ ctx.fillStyle = "#0f0f23";
10667
+ ctx.fillRect(0, 0, width, height);
10668
+ ctx.strokeStyle = "#1a1a3e";
10669
+ ctx.lineWidth = 0.5;
10670
+ for (let i = 0; i < 5; i++) {
10671
+ const y = height / 5 * i;
10672
+ ctx.beginPath();
10673
+ ctx.moveTo(0, y);
10674
+ ctx.lineTo(width, y);
10675
+ ctx.stroke();
10676
+ }
10677
+ let minVal = Infinity;
10678
+ let maxVal = -Infinity;
10679
+ for (const pt of visibleData) {
10680
+ if (pt.value < minVal) minVal = pt.value;
10681
+ if (pt.value > maxVal) maxVal = pt.value;
10682
+ }
10683
+ const range = maxVal - minVal || 1;
10684
+ const pad = height * 0.1;
10685
+ ctx.beginPath();
10686
+ ctx.strokeStyle = color;
10687
+ ctx.lineWidth = 2;
10688
+ for (let i = 0; i < visibleData.length; i++) {
10689
+ const x = i / (maxPoints - 1) * width;
10690
+ const y = pad + (maxVal - visibleData[i].value) / range * (height - 2 * pad);
10691
+ if (i === 0) ctx.moveTo(x, y);
10692
+ else ctx.lineTo(x, y);
10693
+ }
10694
+ ctx.stroke();
10695
+ const last = visibleData[visibleData.length - 1];
10696
+ ctx.fillStyle = color;
10697
+ ctx.font = "12px monospace";
10698
+ ctx.fillText(`${last.value.toFixed(2)} ${unit}`, width - 80, 16);
10699
+ }, [visibleData, width, height, color, unit, maxPoints]);
10700
+ return /* @__PURE__ */ jsx(Card, { padding: "sm", className, children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
10701
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", children: label }),
10702
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded" })
10703
+ ] }) });
10704
+ }
10705
+ SimulationGraph.displayName = "SimulationGraph";
10706
+
10707
+ // components/organisms/game/physics-sim/presets/mechanics.ts
10708
+ var projectileMotion = {
10709
+ id: "mechanics-projectile",
10710
+ name: "Projectile Motion",
10711
+ description: "Launch a ball and observe parabolic trajectory under gravity.",
10712
+ domain: "natural",
10713
+ gravity: { x: 0, y: 9.81 },
10714
+ bodies: [
10715
+ { id: "ball", x: 50, y: 350, vx: 80, vy: -120, mass: 1, radius: 10, color: "#e94560", fixed: false },
10716
+ { id: "ground", x: 300, y: 390, vx: 0, vy: 0, mass: 1e3, radius: 400, color: "#333", fixed: true }
10717
+ ],
10718
+ showVelocity: true,
10719
+ parameters: {
10720
+ angle: { value: 45, min: 0, max: 90, step: 1, label: "Launch angle (deg)" },
10721
+ velocity: { value: 100, min: 10, max: 200, step: 5, label: "Initial velocity (m/s)" },
10722
+ gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
10723
+ }
10724
+ };
10725
+ var pendulum = {
10726
+ id: "mechanics-pendulum",
10727
+ name: "Simple Pendulum",
10728
+ description: "A mass on a string swinging under gravity.",
10729
+ domain: "natural",
10730
+ gravity: { x: 0, y: 9.81 },
10731
+ bodies: [
10732
+ { id: "pivot", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 5, color: "#888", fixed: true },
10733
+ { id: "bob", x: 400, y: 200, vx: 0, vy: 0, mass: 5, radius: 15, color: "#e94560", fixed: false }
10734
+ ],
10735
+ constraints: [{ bodyA: 0, bodyB: 1, length: 180, stiffness: 1 }],
10736
+ parameters: {
10737
+ length: { value: 180, min: 50, max: 300, step: 10, label: "String length (px)" },
10738
+ mass: { value: 5, min: 1, max: 20, step: 0.5, label: "Mass (kg)" },
10739
+ gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
10740
+ }
10741
+ };
10742
+ var springOscillator = {
10743
+ id: "mechanics-spring",
10744
+ name: "Spring Oscillator",
10745
+ description: "A mass bouncing on a spring \u2014 simple harmonic motion.",
10746
+ domain: "natural",
10747
+ gravity: { x: 0, y: 0 },
10748
+ bodies: [
10749
+ { id: "anchor", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 8, color: "#888", fixed: true },
10750
+ { id: "mass", x: 300, y: 250, vx: 0, vy: 0, mass: 2, radius: 20, color: "#0f3460", fixed: false }
10751
+ ],
10752
+ constraints: [{ bodyA: 0, bodyB: 1, length: 150, stiffness: 0.5 }],
10753
+ parameters: {
10754
+ stiffness: { value: 0.5, min: 0.1, max: 2, step: 0.05, label: "Spring stiffness (k)" },
10755
+ mass: { value: 2, min: 0.5, max: 10, step: 0.5, label: "Mass (kg)" },
10756
+ damping: { value: 0, min: 0, max: 0.5, step: 0.01, label: "Damping" }
10757
+ }
10758
+ };
10759
+
10760
+ // components/organisms/game/physics-sim/presets/index.ts
10761
+ var ALL_PRESETS = [
10762
+ projectileMotion,
10763
+ pendulum,
10764
+ springOscillator
10765
+ ];
10766
+ var eventIcons = {
10767
+ attack: Sword,
10768
+ defend: Shield,
10769
+ heal: Heart,
10770
+ move: Move,
10771
+ special: Zap,
10772
+ death: Sword,
10773
+ spawn: Zap
10774
+ };
10775
+ var eventColors = {
10776
+ attack: "text-error",
10777
+ defend: "text-info",
10778
+ heal: "text-success",
10779
+ move: "text-primary",
10780
+ special: "text-warning",
10781
+ death: "text-muted-foreground",
10782
+ spawn: "text-accent"
10783
+ };
10784
+ var eventBadgeVariants = {
10785
+ attack: "danger",
10786
+ defend: "primary",
10787
+ heal: "success",
10788
+ move: "warning",
10789
+ special: "secondary",
10790
+ death: "secondary",
10791
+ spawn: "secondary"
10792
+ };
10793
+ function CombatLog({
10794
+ events,
10795
+ maxVisible = 50,
10796
+ autoScroll = true,
10797
+ showTimestamps = false,
10798
+ className,
10799
+ title = "Combat Log"
10800
+ }) {
10801
+ const scrollRef = useRef(null);
10802
+ useEffect(() => {
10803
+ if (autoScroll && scrollRef.current) {
10804
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
10805
+ }
10806
+ }, [events, autoScroll]);
10807
+ const visibleEvents = events.slice(-maxVisible);
10808
+ return /* @__PURE__ */ jsxs(Card, { variant: "default", className: cn("flex flex-col", className), children: [
10809
+ /* @__PURE__ */ jsx(Box, { padding: "sm", border: true, className: "border-b-2 border-x-0 border-t-0 border-[var(--color-border)]", children: /* @__PURE__ */ jsxs(Box, { display: "flex", className: "items-center justify-between", children: [
10810
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-bold", children: title }),
10811
+ /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
10812
+ events.length,
10813
+ " events"
10814
+ ] })
10815
+ ] }) }),
10816
+ /* @__PURE__ */ jsx(Box, { ref: scrollRef, overflow: "auto", className: "flex-1 max-h-64", children: visibleEvents.length === 0 ? /* @__PURE__ */ jsx(Box, { padding: "md", className: "text-center opacity-50", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "No events yet" }) }) : /* @__PURE__ */ jsx(Box, { padding: "xs", className: "space-y-1", children: visibleEvents.map((event) => {
10817
+ const EventIcon = eventIcons[event.type];
10818
+ const colorClass = eventColors[event.type];
10819
+ return /* @__PURE__ */ jsxs(
10820
+ Box,
10821
+ {
10822
+ display: "flex",
10823
+ padding: "xs",
10824
+ rounded: "sm",
10825
+ className: cn("items-start gap-2 hover:bg-[var(--color-muted)] transition-colors", event.type === "death" && "opacity-60"),
10826
+ children: [
10827
+ /* @__PURE__ */ jsx(Box, { className: cn("flex-shrink-0 mt-0.5", colorClass), children: /* @__PURE__ */ jsx(EventIcon, { className: "h-4 w-4" }) }),
10828
+ /* @__PURE__ */ jsxs(Box, { className: "flex-1 min-w-0", children: [
10829
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "block", children: event.message }),
10830
+ event.value !== void 0 && /* @__PURE__ */ jsxs(Badge, { variant: eventBadgeVariants[event.type], size: "sm", className: "mt-1", children: [
10831
+ event.type === "heal" ? "+" : event.type === "attack" ? "-" : "",
10832
+ event.value
10833
+ ] })
10834
+ ] }),
10835
+ (event.turn || showTimestamps) && /* @__PURE__ */ jsx(Box, { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "opacity-40", children: event.turn ? `T${event.turn}` : "" }) })
10836
+ ]
10837
+ },
10838
+ event.id
10839
+ );
10840
+ }) }) })
10841
+ ] });
10842
+ }
10843
+ CombatLog.displayName = "CombatLog";
10844
+
10845
+ // components/organisms/game/types/game.ts
10846
+ function createInitialGameState(width, height, units, defaultTerrain = "floorStone") {
10847
+ const board = Array.from(
10848
+ { length: height },
10849
+ () => Array.from({ length: width }, () => ({
10850
+ terrain: defaultTerrain
10851
+ }))
10852
+ );
10853
+ const unitsMap = {};
10854
+ for (const unit of units) {
10855
+ unitsMap[unit.id] = unit;
10856
+ if (unit.position.y < height && unit.position.x < width) {
10857
+ board[unit.position.y][unit.position.x].unitId = unit.id;
10858
+ }
10859
+ }
10860
+ return {
10861
+ board,
10862
+ units: unitsMap,
10863
+ currentPhase: "observation",
10864
+ currentTurn: 1,
10865
+ activeTeam: "player",
10866
+ validMoves: [],
10867
+ attackTargets: []
10868
+ };
10869
+ }
10870
+ function calculateValidMoves(state, unitId) {
10871
+ const unit = state.units[unitId];
10872
+ if (!unit) return [];
10873
+ const moves = [];
10874
+ const { x, y } = unit.position;
10875
+ const range = unit.movement;
10876
+ for (let dy = -range; dy <= range; dy++) {
10877
+ for (let dx = -range; dx <= range; dx++) {
10878
+ const nx = x + dx;
10879
+ const ny = y + dy;
10880
+ const distance = Math.abs(dx) + Math.abs(dy);
10881
+ if (distance > 0 && distance <= range && ny >= 0 && ny < state.board.length && nx >= 0 && nx < state.board[0].length && !state.board[ny][nx].unitId && !state.board[ny][nx].isBlocked) {
10882
+ moves.push({ x: nx, y: ny });
10883
+ }
10884
+ }
10885
+ }
10886
+ return moves;
10887
+ }
10888
+ function calculateAttackTargets(state, unitId) {
10889
+ const unit = state.units[unitId];
10890
+ if (!unit) return [];
10891
+ const targets = [];
10892
+ const { x, y } = unit.position;
10893
+ const directions = [
10894
+ { dx: -1, dy: 0 },
10895
+ { dx: 1, dy: 0 },
10896
+ { dx: 0, dy: -1 },
10897
+ { dx: 0, dy: 1 }
10898
+ ];
10899
+ for (const { dx, dy } of directions) {
10900
+ const nx = x + dx;
10901
+ const ny = y + dy;
10902
+ if (ny >= 0 && ny < state.board.length && nx >= 0 && nx < state.board[0].length) {
10903
+ const targetTile = state.board[ny][nx];
10904
+ if (targetTile.unitId) {
10905
+ const targetUnit = state.units[targetTile.unitId];
10906
+ if (targetUnit && targetUnit.team !== unit.team) {
10907
+ targets.push({ x: nx, y: ny });
10908
+ }
10909
+ }
10910
+ }
10911
+ }
10912
+ return targets;
10913
+ }
10914
+
10915
+ // components/organisms/game/utils/combatEffects.ts
10916
+ var combatAnimations = {
10917
+ shake: `
10918
+ @keyframes combat-shake {
10919
+ 0%, 100% { transform: translateX(0); }
10920
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
10921
+ 20%, 40%, 60%, 80% { transform: translateX(5px); }
10922
+ }
10923
+ `,
10924
+ flash: `
10925
+ @keyframes combat-flash {
10926
+ 0%, 100% { opacity: 1; }
10927
+ 50% { opacity: 0.3; filter: brightness(2); }
10928
+ }
10929
+ `,
10930
+ pulseRed: `
10931
+ @keyframes combat-pulse-red {
10932
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
10933
+ 50% { box-shadow: 0 0 20px 5px rgba(239, 68, 68, 0.6); }
10934
+ }
10935
+ `,
10936
+ healGlow: `
10937
+ @keyframes combat-heal-glow {
10938
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
10939
+ 50% { box-shadow: 0 0 30px 10px rgba(34, 197, 94, 0.5); }
10940
+ }
10941
+ `
10942
+ };
10943
+ var combatClasses = {
10944
+ shake: "animate-[combat-shake_0.3s_ease-in-out]",
10945
+ flash: "animate-[combat-flash_0.2s_ease-in-out_2]",
10946
+ pulseRed: "animate-[combat-pulse-red_0.5s_ease-in-out]",
10947
+ healGlow: "animate-[combat-heal-glow_0.8s_ease-in-out]",
10948
+ hit: "animate-pulse brightness-150",
10949
+ defend: "animate-bounce",
10950
+ critical: "animate-ping"
10951
+ };
10952
+ var combatEffects = {
10953
+ attack: { className: "animate-pulse brightness-125", duration: 300 },
10954
+ hit: { className: "animate-[shake_0.3s_ease-in-out] brightness-150", duration: 300 },
10955
+ critical: { className: "animate-ping scale-110", duration: 500 },
10956
+ defend: { className: "animate-bounce ring-4 ring-blue-400", duration: 400 },
10957
+ heal: { className: "brightness-125 ring-4 ring-green-400", duration: 600 },
10958
+ defeat: { className: "grayscale opacity-50 scale-75", duration: 800 },
10959
+ levelUp: { className: "animate-bounce ring-4 ring-yellow-400 brightness-150", duration: 1e3 }
10960
+ };
10961
+ function applyTemporaryEffect(element, effect, onComplete) {
10962
+ const originalClass = element.className;
10963
+ element.className = `${originalClass} ${effect.className}`;
10964
+ setTimeout(() => {
10965
+ element.className = originalClass;
10966
+ onComplete?.();
10967
+ }, effect.duration);
10968
+ }
10969
+ function calculateDamage(attack, defense, isDefending = false, criticalChance = 0.1) {
10970
+ const isCritical = Math.random() < criticalChance;
10971
+ const baseDamage = attack;
10972
+ const defenseMultiplier = isDefending ? 2 : 1;
10973
+ const effectiveDefense = defense * defenseMultiplier;
10974
+ let finalDamage = Math.max(1, baseDamage - effectiveDefense);
10975
+ if (isCritical) finalDamage = Math.floor(finalDamage * 2);
10976
+ const isBlocked = finalDamage <= 1 && effectiveDefense > baseDamage;
10977
+ const damageReduction = baseDamage - finalDamage;
10978
+ return { baseDamage, finalDamage, isCritical, isBlocked, damageReduction };
10979
+ }
10980
+ function generateCombatMessage(event) {
10981
+ return event.message;
10982
+ }
10983
+ function extractTitle(children) {
10984
+ if (!React42__default.isValidElement(children)) return void 0;
10985
+ const props = children.props;
10986
+ if (typeof props.title === "string") {
10987
+ return props.title;
10988
+ }
10989
+ return void 0;
10990
+ }
10991
+ var ModalSlot = ({
10992
+ children,
10993
+ title: overrideTitle,
10994
+ size = "md",
10995
+ className
10996
+ }) => {
10997
+ const eventBus = useEventBus();
10998
+ const isOpen = Boolean(children);
10999
+ const title = overrideTitle || extractTitle(children);
11000
+ const handleClose = () => {
11001
+ eventBus.emit("UI:CLOSE");
11002
+ eventBus.emit("UI:CANCEL");
11003
+ };
11004
+ if (!isOpen) return null;
11005
+ return /* @__PURE__ */ jsx(
11006
+ Modal,
11007
+ {
11008
+ isOpen,
11009
+ onClose: handleClose,
11010
+ title,
11011
+ size,
11012
+ className,
11013
+ children
11014
+ }
11015
+ );
11016
+ };
11017
+ ModalSlot.displayName = "ModalSlot";
11018
+ function extractTitle2(children) {
11019
+ if (!React42__default.isValidElement(children)) return void 0;
11020
+ const props = children.props;
11021
+ if (typeof props.title === "string") {
11022
+ return props.title;
11023
+ }
11024
+ return void 0;
11025
+ }
11026
+ var DrawerSlot = ({
11027
+ children,
11028
+ title: overrideTitle,
11029
+ position = "right",
11030
+ size = "md",
11031
+ className
11032
+ }) => {
11033
+ const eventBus = useEventBus();
11034
+ const isOpen = Boolean(children);
11035
+ const title = overrideTitle || extractTitle2(children);
11036
+ const handleClose = () => {
11037
+ eventBus.emit("UI:CLOSE");
11038
+ eventBus.emit("UI:CANCEL");
11039
+ };
11040
+ if (!isOpen) return null;
11041
+ return /* @__PURE__ */ jsx(
11042
+ Drawer,
11043
+ {
11044
+ isOpen,
11045
+ onClose: handleClose,
11046
+ title,
11047
+ position,
11048
+ width: size,
11049
+ className,
11050
+ children
11051
+ }
11052
+ );
11053
+ };
11054
+ DrawerSlot.displayName = "DrawerSlot";
11055
+ function extractToastProps(children) {
11056
+ if (!React42__default.isValidElement(children)) {
11057
+ if (typeof children === "string") {
11058
+ return { message: children };
11059
+ }
11060
+ return {};
11061
+ }
11062
+ const props = children.props;
11063
+ return {
11064
+ message: typeof props.message === "string" ? props.message : void 0,
11065
+ variant: props.variant,
11066
+ title: typeof props.title === "string" ? props.title : void 0
11067
+ };
11068
+ }
11069
+ var ToastSlot = ({
11070
+ children,
11071
+ variant: overrideVariant,
11072
+ title: overrideTitle,
11073
+ duration = 5e3,
11074
+ className
11075
+ }) => {
11076
+ const eventBus = useEventBus();
11077
+ const isVisible = Boolean(children);
11078
+ const extracted = extractToastProps(children);
11079
+ const variant = overrideVariant || extracted.variant || "info";
11080
+ const title = overrideTitle || extracted.title;
11081
+ const message = extracted.message || (typeof children === "string" ? children : "");
11082
+ const handleDismiss = () => {
11083
+ eventBus.emit("UI:DISMISS");
11084
+ eventBus.emit("UI:CLOSE");
11085
+ };
11086
+ if (!isVisible) return null;
11087
+ const isCustomContent = React42__default.isValidElement(children) && !message;
11088
+ return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
11089
+ Toast,
11090
+ {
11091
+ variant,
11092
+ title,
11093
+ message: message || "Notification",
11094
+ duration,
11095
+ onDismiss: handleDismiss,
11096
+ className
11097
+ }
11098
+ ) });
11099
+ };
11100
+ ToastSlot.displayName = "ToastSlot";
11101
+ var CHART_COLORS = [
11102
+ "var(--color-primary)",
11103
+ "var(--color-success)",
11104
+ "var(--color-warning)",
11105
+ "var(--color-error)",
11106
+ "var(--color-info)",
11107
+ "var(--color-accent)"
11108
+ ];
11109
+ var BarChart = ({ data, height, showValues }) => {
11110
+ const maxValue = Math.max(...data.map((d) => d.value), 1);
11111
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "end", className: "w-full", style: { height }, children: data.map((point, idx) => {
11112
+ const barHeight = point.value / maxValue * 100;
11113
+ const color = point.color || CHART_COLORS[idx % CHART_COLORS.length];
11114
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", flex: true, className: "min-w-0", children: [
11115
+ showValues && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "tabular-nums", children: point.value }),
11116
+ /* @__PURE__ */ jsx(
11117
+ Box,
11118
+ {
11119
+ className: cn(
11120
+ "w-full rounded-t-[var(--radius-sm)] transition-all duration-500 ease-out min-h-[4px]"
11121
+ ),
11122
+ style: {
11123
+ height: `${barHeight}%`,
11124
+ backgroundColor: color
11125
+ }
11126
+ }
11127
+ ),
11128
+ /* @__PURE__ */ jsx(
11129
+ Typography,
11130
+ {
11131
+ variant: "caption",
11132
+ color: "secondary",
11133
+ className: "truncate w-full text-center",
11134
+ children: point.label
11135
+ }
11136
+ )
11137
+ ] }, point.label);
11138
+ }) });
11139
+ };
11140
+ var PieChart = ({ data, height, showValues, donut = false }) => {
11141
+ const total = data.reduce((sum, d) => sum + d.value, 0);
11142
+ const size = Math.min(height, 200);
11143
+ const radius = size / 2 - 8;
11144
+ const innerRadius = donut ? radius * 0.6 : 0;
11145
+ const center = size / 2;
11146
+ const segments = useMemo(() => {
11147
+ let currentAngle = -Math.PI / 2;
11148
+ return data.map((point, idx) => {
11149
+ const angle = point.value / total * 2 * Math.PI;
11150
+ const startAngle = currentAngle;
8886
11151
  const endAngle = currentAngle + angle;
8887
11152
  currentAngle = endAngle;
8888
11153
  const largeArc = angle > Math.PI ? 1 : 0;
@@ -9290,7 +11555,7 @@ var Meter = ({
9290
11555
  ] }) });
9291
11556
  };
9292
11557
  Meter.displayName = "Meter";
9293
- var STATUS_STYLES2 = {
11558
+ var STATUS_STYLES3 = {
9294
11559
  complete: {
9295
11560
  dotColor: "text-[var(--color-success)]",
9296
11561
  lineColor: "bg-[var(--color-success)]",
@@ -9332,7 +11597,7 @@ var Timeline = ({
9332
11597
  },
9333
11598
  [eventBus, entity]
9334
11599
  );
9335
- const items = React25__default.useMemo(() => {
11600
+ const items = React42__default.useMemo(() => {
9336
11601
  if (propItems) return propItems;
9337
11602
  if (!data) return [];
9338
11603
  return data.map((record, idx) => {
@@ -9380,7 +11645,7 @@ var Timeline = ({
9380
11645
  title && /* @__PURE__ */ jsx(Typography, { variant: "h5", weight: "semibold", children: title }),
9381
11646
  /* @__PURE__ */ jsx(VStack, { gap: "none", className: "relative", children: items.map((item, idx) => {
9382
11647
  const status = item.status || "pending";
9383
- const style = STATUS_STYLES2[status];
11648
+ const style = STATUS_STYLES3[status];
9384
11649
  const ItemIcon = item.icon || style.icon;
9385
11650
  const isLast = idx === items.length - 1;
9386
11651
  return /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "relative", children: [
@@ -9481,7 +11746,7 @@ var MediaGallery = ({
9481
11746
  const handleUpload = useCallback(() => {
9482
11747
  eventBus.emit("UI:MEDIA_UPLOAD", { entity });
9483
11748
  }, [eventBus, entity]);
9484
- const items = React25__default.useMemo(() => {
11749
+ const items = React42__default.useMemo(() => {
9485
11750
  if (propItems) return propItems;
9486
11751
  if (!data) return [];
9487
11752
  return data.map((record, idx) => ({
@@ -10815,7 +13080,7 @@ var NavLink = ({
10815
13080
  currentPath
10816
13081
  }) => {
10817
13082
  const isActive = currentPath === item.href || currentPath.startsWith(item.href + "/");
10818
- const Icon2 = item.icon;
13083
+ const Icon3 = item.icon;
10819
13084
  return /* @__PURE__ */ jsxs(
10820
13085
  Link,
10821
13086
  {
@@ -10826,7 +13091,7 @@ var NavLink = ({
10826
13091
  ),
10827
13092
  children: [
10828
13093
  /* @__PURE__ */ jsx(
10829
- Icon2,
13094
+ Icon3,
10830
13095
  {
10831
13096
  className: cn(
10832
13097
  "h-5 w-5",
@@ -11457,4 +13722,4 @@ function WorldMapTemplate({
11457
13722
  }
11458
13723
  WorldMapTemplate.displayName = "WorldMapTemplate";
11459
13724
 
11460
- export { AR_BOOK_FIELDS, AuthLayout, BattleBoard, BattleTemplate, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, CanvasEffect, CastleBoard, CastleTemplate, Chart, CodeViewer, CollapsibleSection, ConfirmDialog, ContentRenderer, CounterTemplate, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DialogueBox, DocumentViewer, StateMachineView as DomStateMachineVisualizer, DrawerSlot, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FormActions, FormLayout, FormSection, GameAudioContext, GameAudioProvider, GameAudioToggle, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Header, IDENTITY_BOOK_FIELDS, InventoryPanel, IsometricCanvas, JazariStateMachine, List, MediaGallery, Meter, ModalSlot, Navigation, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, PhysicsManager, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, Section, Sidebar, SignaturePad, Split, SplitPane, StateMachineView, StatusBar, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, Table, TerrainPalette, Timeline, ToastSlot, TraitSlot, TraitStateViewer, UncontrolledBattleBoard, WizardContainer, WorldMapBoard, WorldMapTemplate, createUnitAnimationState, getCurrentFrame, inferDirection, isoToScreen, mapBookData, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, tickAnimationState, transitionAnimation, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };
13725
+ export { ALL_PRESETS, AR_BOOK_FIELDS, ActionPalette, ActionTile, AuthLayout, BattleBoard, BattleTemplate, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, BuilderBoard, CanvasEffect, CastleBoard, CastleTemplate, Chart, ClassifierBoard, CodeView, CodeViewer, CollapsibleSection, CombatLog, ConfirmDialog, ContentRenderer, CounterTemplate, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DebuggerBoard, DialogueBox, DocumentViewer, StateMachineView as DomStateMachineVisualizer, DrawerSlot, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FormActions, FormLayout, FormSection, GameAudioContext, GameAudioProvider, GameAudioToggle, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Header, IDENTITY_BOOK_FIELDS, InventoryPanel, IsometricCanvas, JazariStateMachine, List, MediaGallery, Meter, ModalSlot, Navigation, NegotiatorBoard, ObjectRulePanel, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, PhysicsManager, RuleEditor, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, Section, SequenceBar, SequencerBoard, Sidebar, SignaturePad, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Split, SplitPane, StateArchitectBoard, StateMachineView, StateNode2 as StateNode, StatusBar, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, Table, TerrainPalette, Timeline, ToastSlot, TraitSlot, TraitStateViewer, TransitionArrow, UncontrolledBattleBoard, VariablePanel, WizardContainer, WorldMapBoard, WorldMapTemplate, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, generateCombatMessage, getCurrentFrame, inferDirection, isoToScreen, mapBookData, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, transitionAnimation, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };