@almadar/ui 2.1.1 → 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,25 +1,24 @@
1
- import { DEFAULT_CONFIG, renderStateMachineToDomData, parseContentSegments } from '../chunk-N6DJVKZ6.js';
2
- 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-6YV5YRGT.js';
3
- 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-6YV5YRGT.js';
4
- import '../chunk-BTXQJGFB.js';
5
- import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
6
- export { cn } from '../chunk-KKCVDUK7.js';
7
1
  import { useAuthContext } from '../chunk-BKC4XU44.js';
8
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
+ import { DEFAULT_CONFIG, renderStateMachineToDomData, parseContentSegments } from '../chunk-N6DJVKZ6.js';
9
4
  import '../chunk-XSEDIUM6.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';
7
+ import '../chunk-BTXQJGFB.js';
10
8
  import { useTranslate } from '../chunk-PE2H3NAW.js';
11
9
  export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useQuerySingleton, useTranslate } from '../chunk-PE2H3NAW.js';
12
10
  import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
13
11
  export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
14
12
  export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-7NEWMNNU.js';
13
+ import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
14
+ export { cn } from '../chunk-KKCVDUK7.js';
15
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
21
  import { createPortal } from 'react-dom';
22
- import { Button as Button$1, cn as cn$1 } from '@almadar/ui';
23
22
  import { useLocation, Link, Outlet } from 'react-router-dom';
24
23
 
25
24
  var FormSection = ({
@@ -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(
@@ -1019,7 +1018,7 @@ var List = ({
1019
1018
  if (d && typeof d === "object" && "id" in d) return [d];
1020
1019
  return [];
1021
1020
  }, [data]);
1022
- const getItemActions = React25__default.useCallback(
1021
+ const getItemActions = React42__default.useCallback(
1023
1022
  (item) => {
1024
1023
  if (!itemActions) return [];
1025
1024
  if (typeof itemActions === "function") {
@@ -1522,7 +1521,7 @@ var WizardContainer = ({
1522
1521
  const isCompleted = index < currentStep;
1523
1522
  const stepKey = step.id ?? step.tabId ?? `step-${index}`;
1524
1523
  const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
1525
- return /* @__PURE__ */ jsxs(React25__default.Fragment, { children: [
1524
+ return /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
1526
1525
  /* @__PURE__ */ jsx(
1527
1526
  Button,
1528
1527
  {
@@ -2746,7 +2745,7 @@ var StateMachineView = ({
2746
2745
  style: { top: title ? 30 : 0 },
2747
2746
  children: [
2748
2747
  entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
2749
- 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(
2750
2749
  StateNode,
2751
2750
  {
2752
2751
  state,
@@ -6129,12 +6128,12 @@ function GameAudioToggle({
6129
6128
  setMuted(!muted);
6130
6129
  }, [muted, setMuted]);
6131
6130
  return /* @__PURE__ */ jsx(
6132
- Button$1,
6131
+ Button,
6133
6132
  {
6134
6133
  variant: "ghost",
6135
6134
  size,
6136
6135
  onClick: handleToggle,
6137
- className: cn$1("text-lg leading-none px-2", className),
6136
+ className: cn("text-lg leading-none px-2", className),
6138
6137
  "aria-pressed": muted,
6139
6138
  children: muted ? "\u{1F507}" : "\u{1F50A}"
6140
6139
  }
@@ -6760,7 +6759,7 @@ function GameMenu({
6760
6759
  } catch {
6761
6760
  }
6762
6761
  const eventBus = eventBusProp || eventBusFromHook;
6763
- const handleOptionClick = React25.useCallback(
6762
+ const handleOptionClick = React42.useCallback(
6764
6763
  (option) => {
6765
6764
  if (option.event && eventBus) {
6766
6765
  eventBus.emit(`UI:${option.event}`, { option });
@@ -6883,7 +6882,7 @@ function GameOverScreen({
6883
6882
  } catch {
6884
6883
  }
6885
6884
  const eventBus = eventBusProp || eventBusFromHook;
6886
- const handleActionClick = React25.useCallback(
6885
+ const handleActionClick = React42.useCallback(
6887
6886
  (action) => {
6888
6887
  if (action.event && eventBus) {
6889
6888
  eventBus.emit(`UI:${action.event}`, { action });
@@ -7308,7 +7307,7 @@ function BattleBoard({
7308
7307
  onAttack,
7309
7308
  onGameEnd,
7310
7309
  onUnitMove,
7311
- calculateDamage,
7310
+ calculateDamage: calculateDamage2,
7312
7311
  onDrawEffects,
7313
7312
  hasActiveEffects: hasActiveEffects2 = false,
7314
7313
  effectSpriteUrls = [],
@@ -7460,7 +7459,7 @@ function BattleBoard({
7460
7459
  }
7461
7460
  if (currentPhase === "action" && selectedUnit) {
7462
7461
  if (unit.team === "enemy" && attackTargets.some((t) => t.x === unit.position.x && t.y === unit.position.y)) {
7463
- 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);
7464
7463
  setIsShaking(true);
7465
7464
  setTimeout(() => setIsShaking(false), 300);
7466
7465
  onAttack?.(selectedUnit, unit, damage);
@@ -7474,7 +7473,7 @@ function BattleBoard({
7474
7473
  setTimeout(checkGameEnd, 100);
7475
7474
  }
7476
7475
  }
7477
- }, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage, unitClickEvent, attackEvent, eventBus]);
7476
+ }, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
7478
7477
  const handleTileClick = useCallback((x, y) => {
7479
7478
  if (tileClickEvent) {
7480
7479
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
@@ -7639,7 +7638,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7639
7638
  attackEvent,
7640
7639
  cancelEvent
7641
7640
  } = eventConfig;
7642
- const { onAttack, onGameEnd, onUnitMove, calculateDamage } = callbacks;
7641
+ const { onAttack, onGameEnd, onUnitMove, calculateDamage: calculateDamage2 } = callbacks;
7643
7642
  const [units, setUnits] = useState(initialUnits);
7644
7643
  const [selectedUnitId, setSelectedUnitId] = useState(null);
7645
7644
  const [phase, setPhase] = useState("observation");
@@ -7682,7 +7681,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7682
7681
  const dx = Math.abs(unit.position.x - selectedUnit.position.x);
7683
7682
  const dy = Math.abs(unit.position.y - selectedUnit.position.y);
7684
7683
  if (dx <= 1 && dy <= 1 && dx + dy > 0) {
7685
- 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);
7686
7685
  const newHealth = Math.max(0, unit.health - damage);
7687
7686
  const updatedUnits = units.map(
7688
7687
  (u) => u.id === unit.id ? { ...u, health: newHealth } : u
@@ -7703,7 +7702,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
7703
7702
  }
7704
7703
  }
7705
7704
  }
7706
- }, [units, selectedUnitId, phase, checkGameEnd, onAttack, calculateDamage, unitClickEvent, attackEvent, eventBus]);
7705
+ }, [units, selectedUnitId, phase, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
7707
7706
  const handleTileClick = useCallback((x, y) => {
7708
7707
  if (tileClickEvent) {
7709
7708
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
@@ -8144,7 +8143,7 @@ function LinearView({
8144
8143
  /* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
8145
8144
  const isDone = i < currentIdx;
8146
8145
  const isCurrent = i === currentIdx;
8147
- return /* @__PURE__ */ jsxs(React25__default.Fragment, { children: [
8146
+ return /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
8148
8147
  i > 0 && /* @__PURE__ */ jsx(
8149
8148
  Typography,
8150
8149
  {
@@ -8562,7 +8561,7 @@ var FEATURE_TYPES = [
8562
8561
  "castle"
8563
8562
  ];
8564
8563
  function CollapsibleSection({ title, expanded, onToggle, children, className }) {
8565
- const Icon2 = expanded ? ChevronDown : ChevronRight;
8564
+ const Icon3 = expanded ? ChevronDown : ChevronRight;
8566
8565
  return /* @__PURE__ */ jsxs(VStack, { gap: "xs", className, children: [
8567
8566
  /* @__PURE__ */ jsx(
8568
8567
  Button,
@@ -8572,7 +8571,7 @@ function CollapsibleSection({ title, expanded, onToggle, children, className })
8572
8571
  onClick: onToggle,
8573
8572
  className: "w-full justify-start text-left",
8574
8573
  children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
8575
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
8574
+ /* @__PURE__ */ jsx(Icon3, { size: 14 }),
8576
8575
  /* @__PURE__ */ jsx(Typography, { variant: "label", weight: "semibold", children: title })
8577
8576
  ] })
8578
8577
  }
@@ -8704,174 +8703,2451 @@ function EditorToolbar({ mode, onModeChange, className }) {
8704
8703
  )) });
8705
8704
  }
8706
8705
  EditorToolbar.displayName = "EditorToolbar";
8707
- function extractTitle(children) {
8708
- if (!React25__default.isValidElement(children)) return void 0;
8709
- const props = children.props;
8710
- if (typeof props.title === "string") {
8711
- return props.title;
8712
- }
8713
- return void 0;
8714
- }
8715
- var ModalSlot = ({
8716
- children,
8717
- 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,
8718
8714
  size = "md",
8715
+ disabled = false,
8716
+ categoryColors,
8719
8717
  className
8720
- }) => {
8721
- const eventBus = useEventBus();
8722
- const isOpen = Boolean(children);
8723
- const title = overrideTitle || extractTitle(children);
8724
- const handleClose = () => {
8725
- eventBus.emit("UI:CLOSE");
8726
- eventBus.emit("UI:CANCEL");
8727
- };
8728
- if (!isOpen) return null;
8729
- return /* @__PURE__ */ jsx(
8730
- 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,
8731
8731
  {
8732
- isOpen,
8733
- onClose: handleClose,
8734
- title,
8735
- size,
8736
- className,
8737
- 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
+ ]
8738
8749
  }
8739
8750
  );
8740
- };
8741
- ModalSlot.displayName = "ModalSlot";
8742
- function extractTitle2(children) {
8743
- if (!React25__default.isValidElement(children)) return void 0;
8744
- const props = children.props;
8745
- if (typeof props.title === "string") {
8746
- return props.title;
8747
- }
8748
- return void 0;
8749
8751
  }
8750
- var DrawerSlot = ({
8751
- children,
8752
- title: overrideTitle,
8753
- position = "right",
8752
+ ActionTile.displayName = "ActionTile";
8753
+ function ActionPalette({
8754
+ actions,
8755
+ usedActionIds = [],
8756
+ allowDuplicates = true,
8757
+ categoryColors,
8754
8758
  size = "md",
8759
+ label,
8755
8760
  className
8756
- }) => {
8757
- const eventBus = useEventBus();
8758
- const isOpen = Boolean(children);
8759
- const title = overrideTitle || extractTitle2(children);
8760
- const handleClose = () => {
8761
- eventBus.emit("UI:CLOSE");
8762
- eventBus.emit("UI:CANCEL");
8763
- };
8764
- if (!isOpen) return null;
8765
- return /* @__PURE__ */ jsx(
8766
- Drawer,
8767
- {
8768
- isOpen,
8769
- onClose: handleClose,
8770
- title,
8771
- position,
8772
- width: size,
8773
- className,
8774
- children
8775
- }
8776
- );
8777
- };
8778
- DrawerSlot.displayName = "DrawerSlot";
8779
- function extractToastProps(children) {
8780
- if (!React25__default.isValidElement(children)) {
8781
- if (typeof children === "string") {
8782
- 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;
8783
8843
  }
8784
- return {};
8785
8844
  }
8786
- const props = children.props;
8787
- return {
8788
- message: typeof props.message === "string" ? props.message : void 0,
8789
- variant: props.variant,
8790
- title: typeof props.title === "string" ? props.title : void 0
8791
- };
8845
+ return playerSeq.map(
8846
+ (id, i) => id !== void 0 && id === bestSolution[i] ? "correct" : "wrong"
8847
+ );
8792
8848
  }
8793
- var ToastSlot = ({
8794
- children,
8795
- variant: overrideVariant,
8796
- title: overrideTitle,
8797
- duration = 5e3,
8849
+ function SequencerBoard({
8850
+ entity,
8851
+ categoryColors,
8852
+ stepDurationMs = 1e3,
8853
+ playEvent,
8854
+ completeEvent,
8798
8855
  className
8799
- }) => {
8800
- const eventBus = useEventBus();
8801
- const isVisible = Boolean(children);
8802
- const extracted = extractToastProps(children);
8803
- const variant = overrideVariant || extracted.variant || "info";
8804
- const title = overrideTitle || extracted.title;
8805
- const message = extracted.message || (typeof children === "string" ? children : "");
8806
- const handleDismiss = () => {
8807
- eventBus.emit("UI:DISMISS");
8808
- 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
+ }))
8809
8965
  };
8810
- if (!isVisible) return null;
8811
- const isCustomContent = React25__default.isValidElement(children) && !message;
8812
- return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
8813
- Toast,
8814
- {
8815
- variant,
8816
- title,
8817
- message: message || "Notification",
8818
- duration,
8819
- onDismiss: handleDismiss,
8820
- className
8821
- }
8822
- ) });
8823
- };
8824
- ToastSlot.displayName = "ToastSlot";
8825
- var CHART_COLORS = [
8826
- "var(--color-primary)",
8827
- "var(--color-success)",
8828
- "var(--color-warning)",
8829
- "var(--color-error)",
8830
- "var(--color-info)",
8831
- "var(--color-accent)"
8832
- ];
8833
- var BarChart = ({ data, height, showValues }) => {
8834
- const maxValue = Math.max(...data.map((d) => d.value), 1);
8835
- return /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "end", className: "w-full", style: { height }, children: data.map((point, idx) => {
8836
- const barHeight = point.value / maxValue * 100;
8837
- const color = point.color || CHART_COLORS[idx % CHART_COLORS.length];
8838
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", flex: true, className: "min-w-0", children: [
8839
- 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
+ ] }),
8840
8989
  /* @__PURE__ */ jsx(
8841
- Box,
8990
+ SequenceBar,
8842
8991
  {
8843
- className: cn(
8844
- "w-full rounded-t-[var(--radius-sm)] transition-all duration-500 ease-out min-h-[4px]"
8845
- ),
8846
- style: {
8847
- height: `${barHeight}%`,
8848
- backgroundColor: color
8849
- }
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"
8850
9001
  }
8851
- ),
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: [
8852
9017
  /* @__PURE__ */ jsx(
8853
- Typography,
9018
+ Button,
8854
9019
  {
8855
- variant: "caption",
8856
- color: "secondary",
8857
- className: "truncate w-full text-center",
8858
- children: point.label
9020
+ variant: "primary",
9021
+ onClick: handlePlay,
9022
+ disabled: !canPlay,
9023
+ children: "\u25B6 " + t("game.play")
8859
9024
  }
8860
- )
8861
- ] }, point.label);
8862
- }) });
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"
8863
9079
  };
8864
- var PieChart = ({ data, height, showValues, donut = false }) => {
8865
- const total = data.reduce((sum, d) => sum + d.value, 0);
8866
- const size = Math.min(height, 200);
8867
- const radius = size / 2 - 8;
8868
- const innerRadius = donut ? radius * 0.6 : 0;
8869
- const center = size / 2;
8870
- const segments = useMemo(() => {
8871
- let currentAngle = -Math.PI / 2;
8872
- return data.map((point, idx) => {
8873
- const angle = point.value / total * 2 * Math.PI;
8874
- 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;
8875
11151
  const endAngle = currentAngle + angle;
8876
11152
  currentAngle = endAngle;
8877
11153
  const largeArc = angle > Math.PI ? 1 : 0;
@@ -9279,7 +11555,7 @@ var Meter = ({
9279
11555
  ] }) });
9280
11556
  };
9281
11557
  Meter.displayName = "Meter";
9282
- var STATUS_STYLES2 = {
11558
+ var STATUS_STYLES3 = {
9283
11559
  complete: {
9284
11560
  dotColor: "text-[var(--color-success)]",
9285
11561
  lineColor: "bg-[var(--color-success)]",
@@ -9321,7 +11597,7 @@ var Timeline = ({
9321
11597
  },
9322
11598
  [eventBus, entity]
9323
11599
  );
9324
- const items = React25__default.useMemo(() => {
11600
+ const items = React42__default.useMemo(() => {
9325
11601
  if (propItems) return propItems;
9326
11602
  if (!data) return [];
9327
11603
  return data.map((record, idx) => {
@@ -9369,7 +11645,7 @@ var Timeline = ({
9369
11645
  title && /* @__PURE__ */ jsx(Typography, { variant: "h5", weight: "semibold", children: title }),
9370
11646
  /* @__PURE__ */ jsx(VStack, { gap: "none", className: "relative", children: items.map((item, idx) => {
9371
11647
  const status = item.status || "pending";
9372
- const style = STATUS_STYLES2[status];
11648
+ const style = STATUS_STYLES3[status];
9373
11649
  const ItemIcon = item.icon || style.icon;
9374
11650
  const isLast = idx === items.length - 1;
9375
11651
  return /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "relative", children: [
@@ -9470,7 +11746,7 @@ var MediaGallery = ({
9470
11746
  const handleUpload = useCallback(() => {
9471
11747
  eventBus.emit("UI:MEDIA_UPLOAD", { entity });
9472
11748
  }, [eventBus, entity]);
9473
- const items = React25__default.useMemo(() => {
11749
+ const items = React42__default.useMemo(() => {
9474
11750
  if (propItems) return propItems;
9475
11751
  if (!data) return [];
9476
11752
  return data.map((record, idx) => ({
@@ -10804,7 +13080,7 @@ var NavLink = ({
10804
13080
  currentPath
10805
13081
  }) => {
10806
13082
  const isActive = currentPath === item.href || currentPath.startsWith(item.href + "/");
10807
- const Icon2 = item.icon;
13083
+ const Icon3 = item.icon;
10808
13084
  return /* @__PURE__ */ jsxs(
10809
13085
  Link,
10810
13086
  {
@@ -10815,7 +13091,7 @@ var NavLink = ({
10815
13091
  ),
10816
13092
  children: [
10817
13093
  /* @__PURE__ */ jsx(
10818
- Icon2,
13094
+ Icon3,
10819
13095
  {
10820
13096
  className: cn(
10821
13097
  "h-5 w-5",
@@ -11446,4 +13722,4 @@ function WorldMapTemplate({
11446
13722
  }
11447
13723
  WorldMapTemplate.displayName = "WorldMapTemplate";
11448
13724
 
11449
- 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 };