@almadar/ui 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6YV5YRGT.js → chunk-LX4G4SVJ.js} +1 -1
- package/dist/{chunk-BTXQJGFB.js → chunk-QU4JHKVC.js} +24 -24
- package/dist/components/index.d.ts +1073 -7
- package/dist/components/index.js +2564 -171
- package/dist/context/index.js +2 -2
- package/dist/lib/index.js +1 -1
- package/dist/providers/index.js +2 -2
- package/package.json +1 -1
package/dist/components/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
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
2
|
import { useAuthContext } from '../chunk-BKC4XU44.js';
|
|
8
3
|
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';
|
|
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-LX4G4SVJ.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-LX4G4SVJ.js';
|
|
7
|
+
import '../chunk-QU4JHKVC.js';
|
|
8
|
+
import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
|
|
9
|
+
export { cn } from '../chunk-KKCVDUK7.js';
|
|
10
10
|
import { useTranslate } from '../chunk-PE2H3NAW.js';
|
|
11
11
|
export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useQuerySingleton, useTranslate } from '../chunk-PE2H3NAW.js';
|
|
12
12
|
import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
|
|
@@ -14,12 +14,11 @@ export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.j
|
|
|
14
14
|
export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-7NEWMNNU.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
|
|
18
|
-
import
|
|
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,
|
|
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] =
|
|
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
|
|
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
|
-
|
|
433
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
6131
|
+
Button,
|
|
6133
6132
|
{
|
|
6134
6133
|
variant: "ghost",
|
|
6135
6134
|
size,
|
|
6136
6135
|
onClick: handleToggle,
|
|
6137
|
-
className: cn
|
|
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 =
|
|
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 =
|
|
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 = [],
|
|
@@ -7334,6 +7333,7 @@ function BattleBoard({
|
|
|
7334
7333
|
const currentTurn = entity.turn;
|
|
7335
7334
|
const gameResult = entity.gameResult;
|
|
7336
7335
|
const eventBus = useEventBus();
|
|
7336
|
+
const { t } = useTranslate();
|
|
7337
7337
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
7338
7338
|
const [isShaking, setIsShaking] = useState(false);
|
|
7339
7339
|
const selectedUnit = useMemo(
|
|
@@ -7387,11 +7387,11 @@ function BattleBoard({
|
|
|
7387
7387
|
const anim2 = movementAnimRef.current;
|
|
7388
7388
|
if (!anim2) return;
|
|
7389
7389
|
anim2.elapsed += 16;
|
|
7390
|
-
const
|
|
7391
|
-
const eased = 1 - (1 -
|
|
7390
|
+
const t2 = Math.min(anim2.elapsed / anim2.duration, 1);
|
|
7391
|
+
const eased = 1 - (1 - t2) * (1 - t2);
|
|
7392
7392
|
const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
|
|
7393
7393
|
const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
|
|
7394
|
-
if (
|
|
7394
|
+
if (t2 >= 1) {
|
|
7395
7395
|
movementAnimRef.current = null;
|
|
7396
7396
|
setMovingPositions((prev) => {
|
|
7397
7397
|
const next = new Map(prev);
|
|
@@ -7422,16 +7422,16 @@ function BattleBoard({
|
|
|
7422
7422
|
unitType: unit.unitType,
|
|
7423
7423
|
heroId: unit.heroId,
|
|
7424
7424
|
sprite: unit.sprite,
|
|
7425
|
-
traits: unit.traits?.map((
|
|
7426
|
-
name:
|
|
7427
|
-
currentState:
|
|
7428
|
-
states:
|
|
7429
|
-
cooldown:
|
|
7425
|
+
traits: unit.traits?.map((t2) => ({
|
|
7426
|
+
name: t2.name,
|
|
7427
|
+
currentState: t2.currentState,
|
|
7428
|
+
states: t2.states,
|
|
7429
|
+
cooldown: t2.cooldown ?? 0
|
|
7430
7430
|
}))
|
|
7431
7431
|
};
|
|
7432
7432
|
});
|
|
7433
7433
|
}, [units, movingPositions]);
|
|
7434
|
-
const maxY = Math.max(...tiles.map((
|
|
7434
|
+
const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
|
|
7435
7435
|
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
7436
7436
|
const tileToScreen = useCallback(
|
|
7437
7437
|
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
@@ -7459,8 +7459,8 @@ function BattleBoard({
|
|
|
7459
7459
|
eventBus.emit(`UI:${unitClickEvent}`, { unitId });
|
|
7460
7460
|
}
|
|
7461
7461
|
if (currentPhase === "action" && selectedUnit) {
|
|
7462
|
-
if (unit.team === "enemy" && attackTargets.some((
|
|
7463
|
-
const damage =
|
|
7462
|
+
if (unit.team === "enemy" && attackTargets.some((t2) => t2.x === unit.position.x && t2.y === unit.position.y)) {
|
|
7463
|
+
const damage = calculateDamage2 ? calculateDamage2(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
|
|
7464
7464
|
setIsShaking(true);
|
|
7465
7465
|
setTimeout(() => setIsShaking(false), 300);
|
|
7466
7466
|
onAttack?.(selectedUnit, unit, damage);
|
|
@@ -7474,7 +7474,7 @@ function BattleBoard({
|
|
|
7474
7474
|
setTimeout(checkGameEnd, 100);
|
|
7475
7475
|
}
|
|
7476
7476
|
}
|
|
7477
|
-
}, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack,
|
|
7477
|
+
}, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
|
|
7478
7478
|
const handleTileClick = useCallback((x, y) => {
|
|
7479
7479
|
if (tileClickEvent) {
|
|
7480
7480
|
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
@@ -7536,7 +7536,7 @@ function BattleBoard({
|
|
|
7536
7536
|
]
|
|
7537
7537
|
);
|
|
7538
7538
|
const shakeStyle = isShaking ? { animation: "battle-shake 0.3s ease-in-out" } : {};
|
|
7539
|
-
return /* @__PURE__ */ jsxs(
|
|
7539
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("battle-board relative min-h-[600px] bg-[var(--color-background)]", className), gap: "none", children: [
|
|
7540
7540
|
/* @__PURE__ */ jsx("style", { children: `
|
|
7541
7541
|
@keyframes battle-shake {
|
|
7542
7542
|
0%, 100% { transform: translate(0, 0); }
|
|
@@ -7551,9 +7551,9 @@ function BattleBoard({
|
|
|
7551
7551
|
90% { transform: translate(-2px, 1px); }
|
|
7552
7552
|
}
|
|
7553
7553
|
` }),
|
|
7554
|
-
header && /* @__PURE__ */ jsx(
|
|
7555
|
-
/* @__PURE__ */ jsxs(
|
|
7556
|
-
/* @__PURE__ */ jsxs(
|
|
7554
|
+
header && /* @__PURE__ */ jsx(Box, { className: "p-4", children: header(ctx) }),
|
|
7555
|
+
/* @__PURE__ */ jsxs(HStack, { className: "flex-1 gap-4 p-4 pt-0", gap: "none", children: [
|
|
7556
|
+
/* @__PURE__ */ jsxs(Box, { className: "relative flex-1", style: shakeStyle, children: [
|
|
7557
7557
|
/* @__PURE__ */ jsx(
|
|
7558
7558
|
IsometricCanvas_default,
|
|
7559
7559
|
{
|
|
@@ -7581,47 +7581,52 @@ function BattleBoard({
|
|
|
7581
7581
|
),
|
|
7582
7582
|
overlay && overlay(ctx)
|
|
7583
7583
|
] }),
|
|
7584
|
-
sidebar && /* @__PURE__ */ jsx(
|
|
7584
|
+
sidebar && /* @__PURE__ */ jsx(Box, { className: "w-80 shrink-0", children: sidebar(ctx) })
|
|
7585
7585
|
] }),
|
|
7586
|
-
actions ? actions(ctx) : currentPhase !== "game_over" && /* @__PURE__ */ jsxs(
|
|
7586
|
+
actions ? actions(ctx) : currentPhase !== "game_over" && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
|
|
7587
7587
|
(currentPhase === "movement" || currentPhase === "action") && /* @__PURE__ */ jsx(
|
|
7588
|
-
|
|
7588
|
+
Button,
|
|
7589
7589
|
{
|
|
7590
|
-
|
|
7590
|
+
variant: "secondary",
|
|
7591
|
+
className: "shadow-xl",
|
|
7591
7592
|
onClick: handleCancel,
|
|
7592
|
-
children: "
|
|
7593
|
+
children: t("battle.cancel")
|
|
7593
7594
|
}
|
|
7594
7595
|
),
|
|
7595
7596
|
/* @__PURE__ */ jsx(
|
|
7596
|
-
|
|
7597
|
+
Button,
|
|
7597
7598
|
{
|
|
7598
|
-
|
|
7599
|
+
variant: "primary",
|
|
7600
|
+
className: "shadow-xl",
|
|
7599
7601
|
onClick: handleEndTurn,
|
|
7600
|
-
children: "
|
|
7602
|
+
children: t("battle.endTurn")
|
|
7601
7603
|
}
|
|
7602
7604
|
)
|
|
7603
7605
|
] }),
|
|
7604
|
-
gameResult && (gameOverOverlay ? gameOverOverlay(ctx) : /* @__PURE__ */ jsx(
|
|
7606
|
+
gameResult && (gameOverOverlay ? gameOverOverlay(ctx) : /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm rounded-xl", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
|
|
7605
7607
|
/* @__PURE__ */ jsx(
|
|
7606
|
-
|
|
7608
|
+
Typography,
|
|
7607
7609
|
{
|
|
7610
|
+
variant: "h2",
|
|
7608
7611
|
className: cn(
|
|
7609
7612
|
"text-4xl font-black tracking-widest uppercase",
|
|
7610
7613
|
gameResult === "victory" ? "text-yellow-400" : "text-red-500"
|
|
7611
7614
|
),
|
|
7612
|
-
children: gameResult === "victory" ? "
|
|
7615
|
+
children: gameResult === "victory" ? t("battle.victory") : t("battle.defeat")
|
|
7613
7616
|
}
|
|
7614
7617
|
),
|
|
7615
|
-
/* @__PURE__ */ jsxs("
|
|
7616
|
-
"
|
|
7618
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-gray-300", children: [
|
|
7619
|
+
t("battle.turnsPlayed"),
|
|
7620
|
+
": ",
|
|
7617
7621
|
currentTurn
|
|
7618
7622
|
] }),
|
|
7619
7623
|
/* @__PURE__ */ jsx(
|
|
7620
|
-
|
|
7624
|
+
Button,
|
|
7621
7625
|
{
|
|
7622
|
-
|
|
7626
|
+
variant: "primary",
|
|
7627
|
+
className: "px-8 py-3 font-semibold",
|
|
7623
7628
|
onClick: handleReset,
|
|
7624
|
-
children: "
|
|
7629
|
+
children: t("battle.playAgain")
|
|
7625
7630
|
}
|
|
7626
7631
|
)
|
|
7627
7632
|
] }) }))
|
|
@@ -7639,7 +7644,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
|
|
|
7639
7644
|
attackEvent,
|
|
7640
7645
|
cancelEvent
|
|
7641
7646
|
} = eventConfig;
|
|
7642
|
-
const { onAttack, onGameEnd, onUnitMove, calculateDamage } = callbacks;
|
|
7647
|
+
const { onAttack, onGameEnd, onUnitMove, calculateDamage: calculateDamage2 } = callbacks;
|
|
7643
7648
|
const [units, setUnits] = useState(initialUnits);
|
|
7644
7649
|
const [selectedUnitId, setSelectedUnitId] = useState(null);
|
|
7645
7650
|
const [phase, setPhase] = useState("observation");
|
|
@@ -7682,7 +7687,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
|
|
|
7682
7687
|
const dx = Math.abs(unit.position.x - selectedUnit.position.x);
|
|
7683
7688
|
const dy = Math.abs(unit.position.y - selectedUnit.position.y);
|
|
7684
7689
|
if (dx <= 1 && dy <= 1 && dx + dy > 0) {
|
|
7685
|
-
const damage =
|
|
7690
|
+
const damage = calculateDamage2 ? calculateDamage2(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
|
|
7686
7691
|
const newHealth = Math.max(0, unit.health - damage);
|
|
7687
7692
|
const updatedUnits = units.map(
|
|
7688
7693
|
(u) => u.id === unit.id ? { ...u, health: newHealth } : u
|
|
@@ -7703,7 +7708,7 @@ function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
|
|
|
7703
7708
|
}
|
|
7704
7709
|
}
|
|
7705
7710
|
}
|
|
7706
|
-
}, [units, selectedUnitId, phase, checkGameEnd, onAttack,
|
|
7711
|
+
}, [units, selectedUnitId, phase, checkGameEnd, onAttack, calculateDamage2, unitClickEvent, attackEvent, eventBus]);
|
|
7707
7712
|
const handleTileClick = useCallback((x, y) => {
|
|
7708
7713
|
if (tileClickEvent) {
|
|
7709
7714
|
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
@@ -8144,7 +8149,7 @@ function LinearView({
|
|
|
8144
8149
|
/* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
|
|
8145
8150
|
const isDone = i < currentIdx;
|
|
8146
8151
|
const isCurrent = i === currentIdx;
|
|
8147
|
-
return /* @__PURE__ */ jsxs(
|
|
8152
|
+
return /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
|
|
8148
8153
|
i > 0 && /* @__PURE__ */ jsx(
|
|
8149
8154
|
Typography,
|
|
8150
8155
|
{
|
|
@@ -8562,7 +8567,7 @@ var FEATURE_TYPES = [
|
|
|
8562
8567
|
"castle"
|
|
8563
8568
|
];
|
|
8564
8569
|
function CollapsibleSection({ title, expanded, onToggle, children, className }) {
|
|
8565
|
-
const
|
|
8570
|
+
const Icon3 = expanded ? ChevronDown : ChevronRight;
|
|
8566
8571
|
return /* @__PURE__ */ jsxs(VStack, { gap: "xs", className, children: [
|
|
8567
8572
|
/* @__PURE__ */ jsx(
|
|
8568
8573
|
Button,
|
|
@@ -8572,7 +8577,7 @@ function CollapsibleSection({ title, expanded, onToggle, children, className })
|
|
|
8572
8577
|
onClick: onToggle,
|
|
8573
8578
|
className: "w-full justify-start text-left",
|
|
8574
8579
|
children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
|
|
8575
|
-
/* @__PURE__ */ jsx(
|
|
8580
|
+
/* @__PURE__ */ jsx(Icon3, { size: 14 }),
|
|
8576
8581
|
/* @__PURE__ */ jsx(Typography, { variant: "label", weight: "semibold", children: title })
|
|
8577
8582
|
] })
|
|
8578
8583
|
}
|
|
@@ -8704,128 +8709,2516 @@ function EditorToolbar({ mode, onModeChange, className }) {
|
|
|
8704
8709
|
)) });
|
|
8705
8710
|
}
|
|
8706
8711
|
EditorToolbar.displayName = "EditorToolbar";
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
var ModalSlot = ({
|
|
8716
|
-
children,
|
|
8717
|
-
title: overrideTitle,
|
|
8712
|
+
var DRAG_MIME2 = "application/x-almadar-slot-item";
|
|
8713
|
+
var SIZE_CONFIG3 = {
|
|
8714
|
+
sm: { px: "px-2 py-1", icon: "text-lg", text: "text-xs" },
|
|
8715
|
+
md: { px: "px-3 py-2", icon: "text-2xl", text: "text-sm" },
|
|
8716
|
+
lg: { px: "px-4 py-3", icon: "text-3xl", text: "text-base" }
|
|
8717
|
+
};
|
|
8718
|
+
function ActionTile({
|
|
8719
|
+
action,
|
|
8718
8720
|
size = "md",
|
|
8721
|
+
disabled = false,
|
|
8722
|
+
categoryColors,
|
|
8719
8723
|
className
|
|
8720
|
-
})
|
|
8721
|
-
|
|
8722
|
-
const
|
|
8723
|
-
const
|
|
8724
|
-
const
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8724
|
+
}) {
|
|
8725
|
+
useTranslate();
|
|
8726
|
+
const config = SIZE_CONFIG3[size];
|
|
8727
|
+
const catColor = categoryColors?.[action.category];
|
|
8728
|
+
const handleDragStart = useCallback((e) => {
|
|
8729
|
+
if (disabled) {
|
|
8730
|
+
e.preventDefault();
|
|
8731
|
+
return;
|
|
8732
|
+
}
|
|
8733
|
+
e.dataTransfer.setData(DRAG_MIME2, JSON.stringify(action));
|
|
8734
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
8735
|
+
}, [action, disabled]);
|
|
8736
|
+
return /* @__PURE__ */ jsxs(
|
|
8737
|
+
Box,
|
|
8731
8738
|
{
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8739
|
+
display: "flex",
|
|
8740
|
+
className: cn(
|
|
8741
|
+
"flex-col items-center gap-1 rounded-lg border-2 transition-all select-none",
|
|
8742
|
+
config.px,
|
|
8743
|
+
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",
|
|
8744
|
+
className
|
|
8745
|
+
),
|
|
8746
|
+
style: {
|
|
8747
|
+
backgroundColor: !disabled && catColor ? catColor.bg : void 0,
|
|
8748
|
+
borderColor: !disabled && catColor ? catColor.border : void 0
|
|
8749
|
+
},
|
|
8750
|
+
draggable: !disabled,
|
|
8751
|
+
onDragStart: handleDragStart,
|
|
8752
|
+
children: [
|
|
8753
|
+
action.iconUrl ? /* @__PURE__ */ jsx("img", { src: action.iconUrl, alt: "", className: "w-8 h-8 object-contain" }) : /* @__PURE__ */ jsx(Typography, { variant: "body1", className: cn(config.icon, "leading-none"), children: action.iconEmoji || "\u2726" }),
|
|
8754
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: cn(config.text, "text-foreground font-medium whitespace-nowrap"), children: action.name })
|
|
8755
|
+
]
|
|
8738
8756
|
}
|
|
8739
8757
|
);
|
|
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
8758
|
}
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8759
|
+
ActionTile.displayName = "ActionTile";
|
|
8760
|
+
function ActionPalette({
|
|
8761
|
+
actions,
|
|
8762
|
+
usedActionIds = [],
|
|
8763
|
+
allowDuplicates = true,
|
|
8764
|
+
categoryColors,
|
|
8754
8765
|
size = "md",
|
|
8766
|
+
label,
|
|
8755
8767
|
className
|
|
8756
|
-
})
|
|
8757
|
-
const
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8768
|
+
}) {
|
|
8769
|
+
const { t } = useTranslate();
|
|
8770
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
|
|
8771
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: label ?? t("sequencer.actions") }),
|
|
8772
|
+
/* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: actions.map((action) => /* @__PURE__ */ jsx(
|
|
8773
|
+
ActionTile,
|
|
8774
|
+
{
|
|
8775
|
+
action,
|
|
8776
|
+
size,
|
|
8777
|
+
categoryColors,
|
|
8778
|
+
disabled: !allowDuplicates && usedActionIds.includes(action.id)
|
|
8779
|
+
},
|
|
8780
|
+
action.id
|
|
8781
|
+
)) })
|
|
8782
|
+
] });
|
|
8783
|
+
}
|
|
8784
|
+
ActionPalette.displayName = "ActionPalette";
|
|
8785
|
+
function SequenceBar({
|
|
8786
|
+
slots,
|
|
8787
|
+
maxSlots,
|
|
8788
|
+
onSlotDrop,
|
|
8789
|
+
onSlotRemove,
|
|
8790
|
+
playing = false,
|
|
8791
|
+
currentStep = -1,
|
|
8792
|
+
categoryColors,
|
|
8793
|
+
slotFeedback,
|
|
8794
|
+
size = "lg",
|
|
8795
|
+
className
|
|
8796
|
+
}) {
|
|
8797
|
+
const handleDrop = useCallback((index) => (item) => {
|
|
8798
|
+
if (playing) return;
|
|
8799
|
+
onSlotDrop(index, item);
|
|
8800
|
+
}, [onSlotDrop, playing]);
|
|
8801
|
+
const handleRemove = useCallback((index) => () => {
|
|
8802
|
+
if (playing) return;
|
|
8803
|
+
onSlotRemove(index);
|
|
8804
|
+
}, [onSlotRemove, playing]);
|
|
8805
|
+
const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
|
|
8806
|
+
return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React42__default.Fragment, { children: [
|
|
8807
|
+
i > 0 && /* @__PURE__ */ jsx(
|
|
8808
|
+
Typography,
|
|
8809
|
+
{
|
|
8810
|
+
variant: "body1",
|
|
8811
|
+
className: cn(
|
|
8812
|
+
"text-lg",
|
|
8813
|
+
currentStep >= 0 && i <= currentStep ? "text-primary" : "text-muted-foreground"
|
|
8814
|
+
),
|
|
8815
|
+
children: "\u2192"
|
|
8816
|
+
}
|
|
8817
|
+
),
|
|
8818
|
+
/* @__PURE__ */ jsx(
|
|
8819
|
+
TraitSlot,
|
|
8820
|
+
{
|
|
8821
|
+
slotNumber: i + 1,
|
|
8822
|
+
equippedItem: slot,
|
|
8823
|
+
size,
|
|
8824
|
+
categoryColors,
|
|
8825
|
+
onItemDrop: handleDrop(i),
|
|
8826
|
+
onRemove: slot ? handleRemove(i) : void 0,
|
|
8827
|
+
draggable: !playing && !!slot,
|
|
8828
|
+
selected: currentStep === i,
|
|
8829
|
+
locked: playing,
|
|
8830
|
+
feedback: slotFeedback?.[i]
|
|
8831
|
+
}
|
|
8832
|
+
)
|
|
8833
|
+
] }, i)) });
|
|
8834
|
+
}
|
|
8835
|
+
SequenceBar.displayName = "SequenceBar";
|
|
8836
|
+
var ENCOURAGEMENT_KEYS = [
|
|
8837
|
+
"puzzle.tryAgain1",
|
|
8838
|
+
"puzzle.tryAgain2",
|
|
8839
|
+
"puzzle.tryAgain3"
|
|
8840
|
+
];
|
|
8841
|
+
var stepLabel = (slot, i) => slot ? `${i + 1}. ${slot.name}` : `Step ${i + 1}`;
|
|
8842
|
+
function computeSlotFeedback(playerSeq, solutions) {
|
|
8843
|
+
let bestSolution = solutions[0];
|
|
8844
|
+
let bestMatches = -1;
|
|
8845
|
+
for (const sol of solutions) {
|
|
8846
|
+
const matches = sol.filter((id, i) => id === playerSeq[i]).length;
|
|
8847
|
+
if (matches > bestMatches) {
|
|
8848
|
+
bestMatches = matches;
|
|
8849
|
+
bestSolution = sol;
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
return playerSeq.map(
|
|
8853
|
+
(id, i) => id !== void 0 && id === bestSolution[i] ? "correct" : "wrong"
|
|
8854
|
+
);
|
|
8855
|
+
}
|
|
8856
|
+
function SequencerBoard({
|
|
8857
|
+
entity,
|
|
8858
|
+
categoryColors,
|
|
8859
|
+
stepDurationMs = 1e3,
|
|
8860
|
+
playEvent,
|
|
8861
|
+
completeEvent,
|
|
8862
|
+
className
|
|
8863
|
+
}) {
|
|
8864
|
+
const { emit } = useEventBus();
|
|
8865
|
+
const { t } = useTranslate();
|
|
8866
|
+
const [headerError, setHeaderError] = useState(false);
|
|
8867
|
+
const [slots, setSlots] = useState(
|
|
8868
|
+
() => Array.from({ length: entity.maxSlots }, () => void 0)
|
|
8869
|
+
);
|
|
8870
|
+
const [playState, setPlayState] = useState("idle");
|
|
8871
|
+
const [currentStep, setCurrentStep] = useState(-1);
|
|
8872
|
+
const [attempts, setAttempts] = useState(0);
|
|
8873
|
+
const [slotFeedback, setSlotFeedback] = useState(
|
|
8874
|
+
() => Array.from({ length: entity.maxSlots }, () => null)
|
|
8875
|
+
);
|
|
8876
|
+
const timerRef = useRef(null);
|
|
8877
|
+
useEffect(() => () => {
|
|
8878
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
8879
|
+
}, []);
|
|
8880
|
+
const handleSlotDrop = useCallback((index, item) => {
|
|
8881
|
+
setSlots((prev) => {
|
|
8882
|
+
const next = [...prev];
|
|
8883
|
+
next[index] = item;
|
|
8884
|
+
return next;
|
|
8885
|
+
});
|
|
8886
|
+
setSlotFeedback((prev) => {
|
|
8887
|
+
const next = [...prev];
|
|
8888
|
+
next[index] = null;
|
|
8889
|
+
return next;
|
|
8890
|
+
});
|
|
8891
|
+
emit("UI:PLAY_SOUND", { key: "drop_slot" });
|
|
8892
|
+
}, [emit]);
|
|
8893
|
+
const handleSlotRemove = useCallback((index) => {
|
|
8894
|
+
setSlots((prev) => {
|
|
8895
|
+
const next = [...prev];
|
|
8896
|
+
next[index] = void 0;
|
|
8897
|
+
return next;
|
|
8898
|
+
});
|
|
8899
|
+
setSlotFeedback((prev) => {
|
|
8900
|
+
const next = [...prev];
|
|
8901
|
+
next[index] = null;
|
|
8902
|
+
return next;
|
|
8903
|
+
});
|
|
8904
|
+
emit("UI:PLAY_SOUND", { key: "back" });
|
|
8905
|
+
}, [emit]);
|
|
8906
|
+
const handleReset = useCallback(() => {
|
|
8907
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
8908
|
+
setSlots(Array.from({ length: entity.maxSlots }, () => void 0));
|
|
8909
|
+
setPlayState("idle");
|
|
8910
|
+
setCurrentStep(-1);
|
|
8911
|
+
setAttempts(0);
|
|
8912
|
+
setSlotFeedback(Array.from({ length: entity.maxSlots }, () => null));
|
|
8913
|
+
}, [entity.maxSlots]);
|
|
8914
|
+
const filledSlots = slots.filter((s) => !!s);
|
|
8915
|
+
const canPlay = filledSlots.length > 0 && playState === "idle";
|
|
8916
|
+
const handlePlay = useCallback(() => {
|
|
8917
|
+
if (!canPlay) return;
|
|
8918
|
+
setSlotFeedback(Array.from({ length: entity.maxSlots }, () => null));
|
|
8919
|
+
emit("UI:PLAY_SOUND", { key: "confirm" });
|
|
8920
|
+
const sequence = slots.map((s) => s?.id || "");
|
|
8921
|
+
if (playEvent) {
|
|
8922
|
+
emit(`UI:${playEvent}`, { sequence });
|
|
8923
|
+
}
|
|
8924
|
+
setPlayState("playing");
|
|
8925
|
+
setCurrentStep(0);
|
|
8926
|
+
let step = 0;
|
|
8927
|
+
const advance = () => {
|
|
8928
|
+
step++;
|
|
8929
|
+
if (step >= entity.maxSlots) {
|
|
8930
|
+
const playerSeq = slots.map((s) => s?.id);
|
|
8931
|
+
const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
|
|
8932
|
+
const success = entity.solutions.some(
|
|
8933
|
+
(sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
|
|
8934
|
+
);
|
|
8935
|
+
if (success) {
|
|
8936
|
+
setPlayState("success");
|
|
8937
|
+
setCurrentStep(-1);
|
|
8938
|
+
emit("UI:PLAY_SOUND", { key: "levelComplete" });
|
|
8939
|
+
if (completeEvent) {
|
|
8940
|
+
emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
|
|
8941
|
+
}
|
|
8942
|
+
} else {
|
|
8943
|
+
setAttempts((prev) => prev + 1);
|
|
8944
|
+
const feedback = computeSlotFeedback(playerSeq, entity.solutions);
|
|
8945
|
+
setSlotFeedback(feedback);
|
|
8946
|
+
setPlayState("idle");
|
|
8947
|
+
setCurrentStep(-1);
|
|
8948
|
+
emit("UI:PLAY_SOUND", { key: "fail" });
|
|
8949
|
+
const correctCount2 = feedback.filter((f) => f === "correct").length;
|
|
8950
|
+
for (let ci = 0; ci < correctCount2; ci++) {
|
|
8951
|
+
setTimeout(() => {
|
|
8952
|
+
emit("UI:PLAY_SOUND", { key: "correctSlot" });
|
|
8953
|
+
}, 300 + ci * 150);
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
} else {
|
|
8957
|
+
setCurrentStep(step);
|
|
8958
|
+
timerRef.current = setTimeout(advance, stepDurationMs);
|
|
8959
|
+
}
|
|
8960
|
+
};
|
|
8961
|
+
timerRef.current = setTimeout(advance, stepDurationMs);
|
|
8962
|
+
}, [canPlay, slots, entity.maxSlots, entity.solutions, stepDurationMs, playEvent, completeEvent, emit]);
|
|
8963
|
+
const machine = {
|
|
8964
|
+
name: entity.title,
|
|
8965
|
+
description: entity.description,
|
|
8966
|
+
states: slots.map((s, i) => stepLabel(s, i)),
|
|
8967
|
+
currentState: currentStep >= 0 ? stepLabel(slots[currentStep], currentStep) : "__idle__",
|
|
8968
|
+
transitions: slots.slice(0, -1).map((s, i) => ({
|
|
8969
|
+
from: stepLabel(s, i),
|
|
8970
|
+
to: stepLabel(slots[i + 1], i + 1),
|
|
8971
|
+
event: "NEXT"
|
|
8972
|
+
}))
|
|
8763
8973
|
};
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8974
|
+
const usedIds = entity.allowDuplicates === false ? slots.filter(Boolean).map((s) => s?.id || "") : [];
|
|
8975
|
+
const showHint = attempts >= 3 && !!entity.hint;
|
|
8976
|
+
const hasFeedback = slotFeedback.some((f) => f !== null);
|
|
8977
|
+
const correctCount = slotFeedback.filter((f) => f === "correct").length;
|
|
8978
|
+
const encourageKey = ENCOURAGEMENT_KEYS[Math.min(attempts - 1, ENCOURAGEMENT_KEYS.length - 1)] ?? ENCOURAGEMENT_KEYS[0];
|
|
8979
|
+
return /* @__PURE__ */ jsxs(
|
|
8980
|
+
VStack,
|
|
8767
8981
|
{
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
children
|
|
8982
|
+
className: cn("p-4 gap-6", className),
|
|
8983
|
+
style: {
|
|
8984
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
8985
|
+
backgroundSize: "cover",
|
|
8986
|
+
backgroundPosition: "center"
|
|
8987
|
+
},
|
|
8988
|
+
children: [
|
|
8989
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
8990
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
8991
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
|
|
8992
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description })
|
|
8993
|
+
] }),
|
|
8994
|
+
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: [
|
|
8995
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
|
|
8996
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
|
|
8997
|
+
] }) }),
|
|
8998
|
+
filledSlots.length > 0 && /* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "linear", size: "md" }),
|
|
8999
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9000
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
|
|
9001
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
|
|
9002
|
+
hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
|
|
9003
|
+
`${correctCount}/${entity.maxSlots} `,
|
|
9004
|
+
"\u2705"
|
|
9005
|
+
] })
|
|
9006
|
+
] }),
|
|
9007
|
+
/* @__PURE__ */ jsx(
|
|
9008
|
+
SequenceBar,
|
|
9009
|
+
{
|
|
9010
|
+
slots,
|
|
9011
|
+
maxSlots: entity.maxSlots,
|
|
9012
|
+
onSlotDrop: handleSlotDrop,
|
|
9013
|
+
onSlotRemove: handleSlotRemove,
|
|
9014
|
+
playing: playState === "playing",
|
|
9015
|
+
currentStep,
|
|
9016
|
+
categoryColors,
|
|
9017
|
+
slotFeedback,
|
|
9018
|
+
size: "lg"
|
|
9019
|
+
}
|
|
9020
|
+
)
|
|
9021
|
+
] }),
|
|
9022
|
+
playState !== "playing" && /* @__PURE__ */ jsx(
|
|
9023
|
+
ActionPalette,
|
|
9024
|
+
{
|
|
9025
|
+
actions: entity.availableActions,
|
|
9026
|
+
usedActionIds: usedIds,
|
|
9027
|
+
allowDuplicates: entity.allowDuplicates !== false,
|
|
9028
|
+
categoryColors,
|
|
9029
|
+
label: t("sequencer.dragActions")
|
|
9030
|
+
}
|
|
9031
|
+
),
|
|
9032
|
+
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) }) }),
|
|
9033
|
+
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") }) }),
|
|
9034
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
9035
|
+
/* @__PURE__ */ jsx(
|
|
9036
|
+
Button,
|
|
9037
|
+
{
|
|
9038
|
+
variant: "primary",
|
|
9039
|
+
onClick: handlePlay,
|
|
9040
|
+
disabled: !canPlay,
|
|
9041
|
+
children: "\u25B6 " + t("game.play")
|
|
9042
|
+
}
|
|
9043
|
+
),
|
|
9044
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
|
|
9045
|
+
] })
|
|
9046
|
+
]
|
|
8775
9047
|
}
|
|
8776
9048
|
);
|
|
9049
|
+
}
|
|
9050
|
+
SequencerBoard.displayName = "SequencerBoard";
|
|
9051
|
+
function RuleEditor({
|
|
9052
|
+
rule,
|
|
9053
|
+
availableEvents,
|
|
9054
|
+
availableActions,
|
|
9055
|
+
onChange,
|
|
9056
|
+
onRemove,
|
|
9057
|
+
disabled = false,
|
|
9058
|
+
className
|
|
9059
|
+
}) {
|
|
9060
|
+
const { t } = useTranslate();
|
|
9061
|
+
const handleWhenChange = useCallback((e) => {
|
|
9062
|
+
onChange({ ...rule, whenEvent: e.target.value });
|
|
9063
|
+
}, [rule, onChange]);
|
|
9064
|
+
const handleThenChange = useCallback((e) => {
|
|
9065
|
+
onChange({ ...rule, thenAction: e.target.value });
|
|
9066
|
+
}, [rule, onChange]);
|
|
9067
|
+
return /* @__PURE__ */ jsxs(HStack, { className: cn("items-center p-2 rounded-lg bg-muted/50 border border-border", className), gap: "sm", children: [
|
|
9068
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-primary font-bold whitespace-nowrap", children: t("eventHandler.when") }),
|
|
9069
|
+
/* @__PURE__ */ jsx(
|
|
9070
|
+
Select,
|
|
9071
|
+
{
|
|
9072
|
+
value: rule.whenEvent,
|
|
9073
|
+
onChange: handleWhenChange,
|
|
9074
|
+
options: availableEvents,
|
|
9075
|
+
disabled,
|
|
9076
|
+
className: "flex-1 min-w-0"
|
|
9077
|
+
}
|
|
9078
|
+
),
|
|
9079
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent font-bold whitespace-nowrap", children: "\u2192 " + t("eventHandler.then") }),
|
|
9080
|
+
/* @__PURE__ */ jsx(
|
|
9081
|
+
Select,
|
|
9082
|
+
{
|
|
9083
|
+
value: rule.thenAction,
|
|
9084
|
+
onChange: handleThenChange,
|
|
9085
|
+
options: availableActions,
|
|
9086
|
+
disabled,
|
|
9087
|
+
className: "flex-1 min-w-0"
|
|
9088
|
+
}
|
|
9089
|
+
),
|
|
9090
|
+
onRemove && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onRemove, disabled, className: "shrink-0", children: "\xD7" })
|
|
9091
|
+
] });
|
|
9092
|
+
}
|
|
9093
|
+
RuleEditor.displayName = "RuleEditor";
|
|
9094
|
+
var STATUS_STYLES2 = {
|
|
9095
|
+
pending: "text-muted-foreground",
|
|
9096
|
+
active: "text-primary animate-pulse",
|
|
9097
|
+
done: "text-success",
|
|
9098
|
+
error: "text-error"
|
|
8777
9099
|
};
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
9100
|
+
var STATUS_DOTS = {
|
|
9101
|
+
pending: "\u25CB",
|
|
9102
|
+
active: "\u25CF",
|
|
9103
|
+
done: "\u2714",
|
|
9104
|
+
error: "\u2717"
|
|
9105
|
+
};
|
|
9106
|
+
function EventLog({
|
|
9107
|
+
entries,
|
|
9108
|
+
maxHeight = 200,
|
|
9109
|
+
label,
|
|
9110
|
+
className
|
|
9111
|
+
}) {
|
|
9112
|
+
const { t } = useTranslate();
|
|
9113
|
+
const scrollRef = useRef(null);
|
|
9114
|
+
useEffect(() => {
|
|
9115
|
+
if (scrollRef.current) {
|
|
9116
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
8783
9117
|
}
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
9118
|
+
}, [entries.length]);
|
|
9119
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
|
|
9120
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: label ?? t("eventHandler.eventLog") }),
|
|
9121
|
+
/* @__PURE__ */ jsx(
|
|
9122
|
+
Box,
|
|
9123
|
+
{
|
|
9124
|
+
ref: scrollRef,
|
|
9125
|
+
className: "overflow-y-auto",
|
|
9126
|
+
style: { maxHeight },
|
|
9127
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9128
|
+
entries.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground italic", children: t("eventHandler.noEvents") }),
|
|
9129
|
+
entries.map((entry) => /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
|
|
9130
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: STATUS_STYLES2[entry.status], children: STATUS_DOTS[entry.status] }),
|
|
9131
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entry.icon }),
|
|
9132
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: cn("flex-1", STATUS_STYLES2[entry.status]), children: entry.message })
|
|
9133
|
+
] }, entry.id))
|
|
9134
|
+
] })
|
|
9135
|
+
}
|
|
9136
|
+
)
|
|
9137
|
+
] });
|
|
8792
9138
|
}
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
9139
|
+
EventLog.displayName = "EventLog";
|
|
9140
|
+
var nextRuleId = 1;
|
|
9141
|
+
function ObjectRulePanel({
|
|
9142
|
+
object,
|
|
9143
|
+
onRulesChange,
|
|
9144
|
+
disabled = false,
|
|
8798
9145
|
className
|
|
8799
|
-
})
|
|
8800
|
-
const
|
|
8801
|
-
const
|
|
8802
|
-
const
|
|
8803
|
-
const
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
9146
|
+
}) {
|
|
9147
|
+
const { t } = useTranslate();
|
|
9148
|
+
const maxRules = object.maxRules || 3;
|
|
9149
|
+
const canAdd = object.rules.length < maxRules;
|
|
9150
|
+
const handleRuleChange = useCallback((index, updatedRule) => {
|
|
9151
|
+
const newRules = [...object.rules];
|
|
9152
|
+
newRules[index] = updatedRule;
|
|
9153
|
+
onRulesChange(object.id, newRules);
|
|
9154
|
+
}, [object.id, object.rules, onRulesChange]);
|
|
9155
|
+
const handleRuleRemove = useCallback((index) => {
|
|
9156
|
+
const newRules = object.rules.filter((_, i) => i !== index);
|
|
9157
|
+
onRulesChange(object.id, newRules);
|
|
9158
|
+
}, [object.id, object.rules, onRulesChange]);
|
|
9159
|
+
const handleAddRule = useCallback(() => {
|
|
9160
|
+
if (!canAdd || disabled) return;
|
|
9161
|
+
const firstEvent = object.availableEvents[0]?.value || "";
|
|
9162
|
+
const firstAction = object.availableActions[0]?.value || "";
|
|
9163
|
+
const newRule = {
|
|
9164
|
+
id: `rule-${nextRuleId++}`,
|
|
9165
|
+
whenEvent: firstEvent,
|
|
9166
|
+
thenAction: firstAction
|
|
9167
|
+
};
|
|
9168
|
+
onRulesChange(object.id, [...object.rules, newRule]);
|
|
9169
|
+
}, [canAdd, disabled, object, onRulesChange]);
|
|
9170
|
+
const machine = {
|
|
9171
|
+
name: object.name,
|
|
9172
|
+
states: object.states,
|
|
9173
|
+
currentState: object.currentState,
|
|
9174
|
+
transitions: object.rules.map((r) => ({
|
|
9175
|
+
from: object.currentState,
|
|
9176
|
+
to: object.states.find((s) => s !== object.currentState) || object.currentState,
|
|
9177
|
+
event: r.whenEvent
|
|
9178
|
+
}))
|
|
8809
9179
|
};
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
className
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
9180
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("p-4 rounded-lg bg-card border border-border", className), gap: "sm", children: [
|
|
9181
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center", gap: "sm", children: [
|
|
9182
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h5", children: object.icon }),
|
|
9183
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
|
|
9184
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-bold", children: object.name }),
|
|
9185
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("eventHandler.state") + ": " + object.currentState })
|
|
9186
|
+
] })
|
|
9187
|
+
] }),
|
|
9188
|
+
/* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" }),
|
|
9189
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9190
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.rules", { count: object.rules.length, max: maxRules }) + ":" }),
|
|
9191
|
+
object.rules.map((rule, i) => /* @__PURE__ */ jsx(
|
|
9192
|
+
RuleEditor,
|
|
9193
|
+
{
|
|
9194
|
+
rule,
|
|
9195
|
+
availableEvents: object.availableEvents,
|
|
9196
|
+
availableActions: object.availableActions,
|
|
9197
|
+
onChange: (r) => handleRuleChange(i, r),
|
|
9198
|
+
onRemove: () => handleRuleRemove(i),
|
|
9199
|
+
disabled
|
|
9200
|
+
},
|
|
9201
|
+
rule.id
|
|
9202
|
+
)),
|
|
9203
|
+
canAdd && !disabled && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleAddRule, className: "self-start", children: t("eventHandler.addRule") })
|
|
9204
|
+
] })
|
|
9205
|
+
] });
|
|
9206
|
+
}
|
|
9207
|
+
ObjectRulePanel.displayName = "ObjectRulePanel";
|
|
9208
|
+
var ENCOURAGEMENT_KEYS2 = [
|
|
9209
|
+
"puzzle.tryAgain1",
|
|
9210
|
+
"puzzle.tryAgain2",
|
|
9211
|
+
"puzzle.tryAgain3"
|
|
9212
|
+
];
|
|
9213
|
+
function EventHandlerBoard({
|
|
9214
|
+
entity,
|
|
9215
|
+
stepDurationMs = 800,
|
|
9216
|
+
playEvent,
|
|
9217
|
+
completeEvent,
|
|
9218
|
+
className
|
|
9219
|
+
}) {
|
|
9220
|
+
const { emit } = useEventBus();
|
|
9221
|
+
const { t } = useTranslate();
|
|
9222
|
+
const [objects, setObjects] = useState(entity.objects);
|
|
9223
|
+
const [selectedObjectId, setSelectedObjectId] = useState(
|
|
9224
|
+
entity.objects[0]?.id || null
|
|
9225
|
+
);
|
|
9226
|
+
const [headerError, setHeaderError] = useState(false);
|
|
9227
|
+
const [playState, setPlayState] = useState("editing");
|
|
9228
|
+
const [eventLog, setEventLog] = useState([]);
|
|
9229
|
+
const [attempts, setAttempts] = useState(0);
|
|
9230
|
+
const timerRef = useRef(null);
|
|
9231
|
+
const logIdCounter = useRef(0);
|
|
9232
|
+
useEffect(() => () => {
|
|
9233
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9234
|
+
}, []);
|
|
9235
|
+
const selectedObject = objects.find((o) => o.id === selectedObjectId) || null;
|
|
9236
|
+
const handleRulesChange = useCallback((objectId, rules) => {
|
|
9237
|
+
setObjects((prev) => prev.map(
|
|
9238
|
+
(o) => o.id === objectId ? { ...o, rules } : o
|
|
9239
|
+
));
|
|
9240
|
+
}, []);
|
|
9241
|
+
const addLogEntry = useCallback((icon, message, status = "done") => {
|
|
9242
|
+
const id = `log-${logIdCounter.current++}`;
|
|
9243
|
+
setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
|
|
9244
|
+
}, []);
|
|
9245
|
+
const handlePlay = useCallback(() => {
|
|
9246
|
+
if (playState !== "editing") return;
|
|
9247
|
+
if (playEvent) emit(`UI:${playEvent}`, {});
|
|
9248
|
+
setPlayState("playing");
|
|
9249
|
+
setEventLog([]);
|
|
9250
|
+
const allRules = [];
|
|
9251
|
+
objects.forEach((obj) => {
|
|
9252
|
+
obj.rules.forEach((rule) => {
|
|
9253
|
+
allRules.push({ object: obj, rule });
|
|
9254
|
+
});
|
|
9255
|
+
});
|
|
9256
|
+
const triggers = entity.triggerEvents || [];
|
|
9257
|
+
const eventQueue = [...triggers];
|
|
9258
|
+
const firedEvents = /* @__PURE__ */ new Set();
|
|
9259
|
+
let stepIdx = 0;
|
|
9260
|
+
let goalReached = false;
|
|
9261
|
+
const processNext = () => {
|
|
9262
|
+
if (eventQueue.length === 0 || stepIdx > 20) {
|
|
9263
|
+
if (goalReached) {
|
|
9264
|
+
setPlayState("success");
|
|
9265
|
+
if (completeEvent) {
|
|
9266
|
+
emit(`UI:${completeEvent}`, { success: true });
|
|
9267
|
+
}
|
|
9268
|
+
} else {
|
|
9269
|
+
setAttempts((prev) => prev + 1);
|
|
9270
|
+
setPlayState("fail");
|
|
9271
|
+
}
|
|
9272
|
+
return;
|
|
9273
|
+
}
|
|
9274
|
+
const currentEvent = eventQueue.shift();
|
|
9275
|
+
if (firedEvents.has(currentEvent)) {
|
|
9276
|
+
timerRef.current = setTimeout(processNext, 100);
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
firedEvents.add(currentEvent);
|
|
9280
|
+
const matching = allRules.filter((r) => r.rule.whenEvent === currentEvent);
|
|
9281
|
+
if (matching.length === 0) {
|
|
9282
|
+
addLogEntry("\u26A1", t("eventHandler.noListeners", { event: currentEvent }), "done");
|
|
9283
|
+
} else {
|
|
9284
|
+
matching.forEach(({ object, rule }) => {
|
|
9285
|
+
addLogEntry(object.icon, t("eventHandler.heardEvent", { object: object.name, event: currentEvent, action: rule.thenAction }), "done");
|
|
9286
|
+
eventQueue.push(rule.thenAction);
|
|
9287
|
+
if (rule.thenAction === entity.goalEvent) {
|
|
9288
|
+
goalReached = true;
|
|
9289
|
+
}
|
|
9290
|
+
});
|
|
9291
|
+
}
|
|
9292
|
+
if (currentEvent === entity.goalEvent) {
|
|
9293
|
+
goalReached = true;
|
|
9294
|
+
}
|
|
9295
|
+
stepIdx++;
|
|
9296
|
+
timerRef.current = setTimeout(processNext, stepDurationMs);
|
|
9297
|
+
};
|
|
9298
|
+
if (triggers.length > 0) {
|
|
9299
|
+
addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
|
|
9300
|
+
}
|
|
9301
|
+
timerRef.current = setTimeout(processNext, stepDurationMs);
|
|
9302
|
+
}, [playState, objects, entity, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
|
|
9303
|
+
const handleTryAgain = useCallback(() => {
|
|
9304
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9305
|
+
setPlayState("editing");
|
|
9306
|
+
setEventLog([]);
|
|
9307
|
+
}, []);
|
|
9308
|
+
const handleReset = useCallback(() => {
|
|
9309
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9310
|
+
setObjects(entity.objects);
|
|
9311
|
+
setPlayState("editing");
|
|
9312
|
+
setEventLog([]);
|
|
9313
|
+
setSelectedObjectId(entity.objects[0]?.id || null);
|
|
9314
|
+
setAttempts(0);
|
|
9315
|
+
}, [entity.objects]);
|
|
9316
|
+
const objectViewers = objects.map((obj) => {
|
|
9317
|
+
const machine = {
|
|
9318
|
+
name: obj.name,
|
|
9319
|
+
states: obj.states,
|
|
9320
|
+
currentState: obj.currentState,
|
|
9321
|
+
transitions: obj.rules.map((r) => ({
|
|
9322
|
+
from: obj.currentState,
|
|
9323
|
+
to: obj.states.find((s) => s !== obj.currentState) || obj.currentState,
|
|
9324
|
+
event: r.whenEvent
|
|
9325
|
+
}))
|
|
9326
|
+
};
|
|
9327
|
+
return { obj, machine };
|
|
9328
|
+
});
|
|
9329
|
+
const showHint = attempts >= 3 && entity.hint;
|
|
9330
|
+
const encourageKey = ENCOURAGEMENT_KEYS2[Math.min(attempts - 1, ENCOURAGEMENT_KEYS2.length - 1)] ?? ENCOURAGEMENT_KEYS2[0];
|
|
9331
|
+
return /* @__PURE__ */ jsxs(
|
|
9332
|
+
VStack,
|
|
9333
|
+
{
|
|
9334
|
+
className: cn("p-4 gap-6", className),
|
|
9335
|
+
style: {
|
|
9336
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
9337
|
+
backgroundSize: "cover",
|
|
9338
|
+
backgroundPosition: "center"
|
|
9339
|
+
},
|
|
9340
|
+
children: [
|
|
9341
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
9342
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9343
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
|
|
9344
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description }),
|
|
9345
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-primary/10 border border-primary/30", gap: "xs", children: [
|
|
9346
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-primary font-bold", children: t("game.goal") + ":" }),
|
|
9347
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entity.goalCondition })
|
|
9348
|
+
] })
|
|
9349
|
+
] }),
|
|
9350
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
9351
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("eventHandler.clickObject") + ":" }),
|
|
9352
|
+
/* @__PURE__ */ jsx(HStack, { className: "flex-wrap", gap: "sm", children: objectViewers.map(({ obj, machine }) => /* @__PURE__ */ jsx(
|
|
9353
|
+
Box,
|
|
9354
|
+
{
|
|
9355
|
+
className: cn(
|
|
9356
|
+
"p-3 rounded-lg border-2 cursor-pointer transition-all hover:scale-105",
|
|
9357
|
+
selectedObjectId === obj.id ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
|
|
9358
|
+
),
|
|
9359
|
+
onClick: () => setSelectedObjectId(obj.id),
|
|
9360
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "items-center min-w-[120px]", children: [
|
|
9361
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h5", children: obj.icon }),
|
|
9362
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground font-medium", children: obj.name }),
|
|
9363
|
+
/* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "compact", size: "sm" })
|
|
9364
|
+
] })
|
|
9365
|
+
},
|
|
9366
|
+
obj.id
|
|
9367
|
+
)) })
|
|
9368
|
+
] }),
|
|
9369
|
+
selectedObject && /* @__PURE__ */ jsx(
|
|
9370
|
+
ObjectRulePanel,
|
|
9371
|
+
{
|
|
9372
|
+
object: selectedObject,
|
|
9373
|
+
onRulesChange: handleRulesChange,
|
|
9374
|
+
disabled: playState !== "editing"
|
|
9375
|
+
}
|
|
9376
|
+
),
|
|
9377
|
+
eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
|
|
9378
|
+
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") }) }),
|
|
9379
|
+
playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
9380
|
+
/* @__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) }) }),
|
|
9381
|
+
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: [
|
|
9382
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
|
|
9383
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
|
|
9384
|
+
] }) })
|
|
9385
|
+
] }),
|
|
9386
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
9387
|
+
playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
|
|
9388
|
+
Button,
|
|
9389
|
+
{
|
|
9390
|
+
variant: "primary",
|
|
9391
|
+
onClick: handlePlay,
|
|
9392
|
+
disabled: playState !== "editing",
|
|
9393
|
+
children: "\u25B6 " + t("game.play")
|
|
9394
|
+
}
|
|
9395
|
+
),
|
|
9396
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
|
|
9397
|
+
] })
|
|
9398
|
+
]
|
|
9399
|
+
}
|
|
9400
|
+
);
|
|
9401
|
+
}
|
|
9402
|
+
EventHandlerBoard.displayName = "EventHandlerBoard";
|
|
9403
|
+
function StateNode2({
|
|
9404
|
+
name,
|
|
9405
|
+
isCurrent = false,
|
|
9406
|
+
isSelected = false,
|
|
9407
|
+
isInitial = false,
|
|
9408
|
+
position,
|
|
9409
|
+
onClick,
|
|
9410
|
+
className
|
|
9411
|
+
}) {
|
|
9412
|
+
return /* @__PURE__ */ jsx(
|
|
9413
|
+
Box,
|
|
9414
|
+
{
|
|
9415
|
+
position: "absolute",
|
|
9416
|
+
display: "flex",
|
|
9417
|
+
className: cn(
|
|
9418
|
+
"items-center justify-center rounded-full border-3 transition-all cursor-pointer select-none",
|
|
9419
|
+
"min-w-[80px] h-[80px] px-3",
|
|
9420
|
+
isCurrent && "bg-primary/20 border-primary shadow-lg shadow-primary/30 scale-110",
|
|
9421
|
+
isSelected && !isCurrent && "bg-accent/20 border-accent ring-2 ring-accent/50",
|
|
9422
|
+
!isCurrent && !isSelected && "bg-card border-border hover:border-muted-foreground hover:scale-105",
|
|
9423
|
+
className
|
|
9424
|
+
),
|
|
9425
|
+
style: {
|
|
9426
|
+
left: position.x,
|
|
9427
|
+
top: position.y,
|
|
9428
|
+
transform: "translate(-50%, -50%)"
|
|
9429
|
+
},
|
|
9430
|
+
onClick,
|
|
9431
|
+
children: /* @__PURE__ */ jsxs(Box, { className: "text-center", children: [
|
|
9432
|
+
isInitial && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground text-xs block", children: "\u25B6 start" }),
|
|
9433
|
+
/* @__PURE__ */ jsx(
|
|
9434
|
+
Typography,
|
|
9435
|
+
{
|
|
9436
|
+
variant: "body2",
|
|
9437
|
+
className: cn(
|
|
9438
|
+
"font-bold whitespace-nowrap",
|
|
9439
|
+
isCurrent ? "text-primary" : "text-foreground"
|
|
9440
|
+
),
|
|
9441
|
+
children: name
|
|
9442
|
+
}
|
|
9443
|
+
)
|
|
9444
|
+
] })
|
|
9445
|
+
}
|
|
9446
|
+
);
|
|
9447
|
+
}
|
|
9448
|
+
StateNode2.displayName = "StateNode";
|
|
9449
|
+
var NODE_RADIUS = 40;
|
|
9450
|
+
function TransitionArrow({
|
|
9451
|
+
from,
|
|
9452
|
+
to,
|
|
9453
|
+
eventLabel,
|
|
9454
|
+
guardHint,
|
|
9455
|
+
isActive = false,
|
|
9456
|
+
onClick,
|
|
9457
|
+
className
|
|
9458
|
+
}) {
|
|
9459
|
+
const dx = to.x - from.x;
|
|
9460
|
+
const dy = to.y - from.y;
|
|
9461
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
9462
|
+
if (dist === 0) return /* @__PURE__ */ jsx(Fragment, {});
|
|
9463
|
+
const nx = dx / dist;
|
|
9464
|
+
const ny = dy / dist;
|
|
9465
|
+
const startX = from.x + nx * NODE_RADIUS;
|
|
9466
|
+
const startY = from.y + ny * NODE_RADIUS;
|
|
9467
|
+
const endX = to.x - nx * NODE_RADIUS;
|
|
9468
|
+
const endY = to.y - ny * NODE_RADIUS;
|
|
9469
|
+
const midX = (startX + endX) / 2;
|
|
9470
|
+
const midY = (startY + endY) / 2;
|
|
9471
|
+
const perpX = -ny * 20;
|
|
9472
|
+
const perpY = nx * 20;
|
|
9473
|
+
const ctrlX = midX + perpX;
|
|
9474
|
+
const ctrlY = midY + perpY;
|
|
9475
|
+
const path = `M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`;
|
|
9476
|
+
return /* @__PURE__ */ jsxs("g", { className: cn("cursor-pointer", className), onClick, children: [
|
|
9477
|
+
/* @__PURE__ */ jsx(
|
|
9478
|
+
"path",
|
|
9479
|
+
{
|
|
9480
|
+
d: path,
|
|
9481
|
+
fill: "none",
|
|
9482
|
+
stroke: isActive ? "var(--color-primary)" : "var(--color-border)",
|
|
9483
|
+
strokeWidth: isActive ? 3 : 2,
|
|
9484
|
+
markerEnd: isActive ? "url(#arrowhead-active)" : "url(#arrowhead)"
|
|
9485
|
+
}
|
|
9486
|
+
),
|
|
9487
|
+
/* @__PURE__ */ jsx(
|
|
9488
|
+
"text",
|
|
9489
|
+
{
|
|
9490
|
+
x: ctrlX,
|
|
9491
|
+
y: ctrlY - 8,
|
|
9492
|
+
textAnchor: "middle",
|
|
9493
|
+
fill: isActive ? "var(--color-primary)" : "var(--color-foreground)",
|
|
9494
|
+
fontSize: 12,
|
|
9495
|
+
fontWeight: isActive ? "bold" : "normal",
|
|
9496
|
+
className: "select-none",
|
|
9497
|
+
children: eventLabel
|
|
9498
|
+
}
|
|
9499
|
+
),
|
|
9500
|
+
guardHint && /* @__PURE__ */ jsx(
|
|
9501
|
+
"text",
|
|
9502
|
+
{
|
|
9503
|
+
x: ctrlX,
|
|
9504
|
+
y: ctrlY + 6,
|
|
9505
|
+
textAnchor: "middle",
|
|
9506
|
+
fill: "var(--color-warning)",
|
|
9507
|
+
fontSize: 10,
|
|
9508
|
+
className: "select-none",
|
|
9509
|
+
children: "\u26A0 " + guardHint
|
|
9510
|
+
}
|
|
9511
|
+
)
|
|
9512
|
+
] });
|
|
9513
|
+
}
|
|
9514
|
+
TransitionArrow.displayName = "TransitionArrow";
|
|
9515
|
+
function VariablePanel({
|
|
9516
|
+
entityName,
|
|
9517
|
+
variables,
|
|
9518
|
+
className
|
|
9519
|
+
}) {
|
|
9520
|
+
const { t } = useTranslate();
|
|
9521
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("p-3 rounded-lg bg-card border border-border", className), gap: "sm", children: [
|
|
9522
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
|
|
9523
|
+
variables.map((v) => {
|
|
9524
|
+
const max = v.max ?? 100;
|
|
9525
|
+
const min = v.min ?? 0;
|
|
9526
|
+
const pct = Math.round((v.value - min) / (max - min) * 100);
|
|
9527
|
+
const isHigh = pct > 80;
|
|
9528
|
+
const isLow = pct < 20;
|
|
9529
|
+
return /* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
|
|
9530
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
|
|
9531
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground font-medium", children: v.name }),
|
|
9532
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: cn(
|
|
9533
|
+
isHigh ? "text-error" : isLow ? "text-warning" : "text-foreground"
|
|
9534
|
+
), children: [
|
|
9535
|
+
v.value,
|
|
9536
|
+
v.unit || "",
|
|
9537
|
+
" / ",
|
|
9538
|
+
max,
|
|
9539
|
+
v.unit || ""
|
|
9540
|
+
] })
|
|
9541
|
+
] }),
|
|
9542
|
+
/* @__PURE__ */ jsx(
|
|
9543
|
+
ProgressBar,
|
|
9544
|
+
{
|
|
9545
|
+
value: pct,
|
|
9546
|
+
color: isHigh ? "danger" : isLow ? "warning" : "primary",
|
|
9547
|
+
size: "sm"
|
|
9548
|
+
}
|
|
9549
|
+
)
|
|
9550
|
+
] }, v.name);
|
|
9551
|
+
})
|
|
9552
|
+
] });
|
|
9553
|
+
}
|
|
9554
|
+
VariablePanel.displayName = "VariablePanel";
|
|
9555
|
+
function CodeView({
|
|
9556
|
+
data,
|
|
9557
|
+
label,
|
|
9558
|
+
defaultExpanded = false,
|
|
9559
|
+
className
|
|
9560
|
+
}) {
|
|
9561
|
+
const { t } = useTranslate();
|
|
9562
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
9563
|
+
const jsonString = JSON.stringify(data, null, 2);
|
|
9564
|
+
return /* @__PURE__ */ jsxs(VStack, { className: cn("rounded-lg border border-border overflow-hidden", className), gap: "none", children: [
|
|
9565
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between p-2 bg-muted", gap: "sm", children: [
|
|
9566
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground font-medium", children: label ?? t("stateArchitect.viewCode") }),
|
|
9567
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setExpanded(!expanded), className: "text-xs", children: expanded ? t("stateArchitect.hideJson") : t("stateArchitect.showJson") })
|
|
9568
|
+
] }),
|
|
9569
|
+
expanded && /* @__PURE__ */ jsx(Box, { className: "p-3 bg-background overflow-x-auto", children: /* @__PURE__ */ jsx(
|
|
9570
|
+
Typography,
|
|
9571
|
+
{
|
|
9572
|
+
variant: "caption",
|
|
9573
|
+
className: "text-foreground font-mono whitespace-pre text-xs leading-relaxed block",
|
|
9574
|
+
children: jsonString
|
|
9575
|
+
}
|
|
9576
|
+
) })
|
|
9577
|
+
] });
|
|
9578
|
+
}
|
|
9579
|
+
CodeView.displayName = "CodeView";
|
|
9580
|
+
var ENCOURAGEMENT_KEYS3 = [
|
|
9581
|
+
"puzzle.tryAgain1",
|
|
9582
|
+
"puzzle.tryAgain2",
|
|
9583
|
+
"puzzle.tryAgain3"
|
|
9584
|
+
];
|
|
9585
|
+
function layoutStates(states, width, height) {
|
|
9586
|
+
const cx = width / 2;
|
|
9587
|
+
const cy = height / 2;
|
|
9588
|
+
const radius = Math.min(cx, cy) - 60;
|
|
9589
|
+
const positions = {};
|
|
9590
|
+
states.forEach((state, i) => {
|
|
9591
|
+
const angle = 2 * Math.PI * i / states.length - Math.PI / 2;
|
|
9592
|
+
positions[state] = {
|
|
9593
|
+
x: cx + radius * Math.cos(angle),
|
|
9594
|
+
y: cy + radius * Math.sin(angle)
|
|
9595
|
+
};
|
|
9596
|
+
});
|
|
9597
|
+
return positions;
|
|
9598
|
+
}
|
|
9599
|
+
var nextTransId = 100;
|
|
9600
|
+
function StateArchitectBoard({
|
|
9601
|
+
entity,
|
|
9602
|
+
stepDurationMs = 600,
|
|
9603
|
+
testEvent,
|
|
9604
|
+
completeEvent,
|
|
9605
|
+
className
|
|
9606
|
+
}) {
|
|
9607
|
+
const { emit } = useEventBus();
|
|
9608
|
+
const { t } = useTranslate();
|
|
9609
|
+
const [transitions, setTransitions] = useState(entity.transitions);
|
|
9610
|
+
const [headerError, setHeaderError] = useState(false);
|
|
9611
|
+
const [playState, setPlayState] = useState("editing");
|
|
9612
|
+
const [currentState, setCurrentState] = useState(entity.initialState);
|
|
9613
|
+
const [selectedState, setSelectedState] = useState(null);
|
|
9614
|
+
const [testResults, setTestResults] = useState([]);
|
|
9615
|
+
const [variables, setVariables] = useState(entity.variables);
|
|
9616
|
+
const [attempts, setAttempts] = useState(0);
|
|
9617
|
+
const timerRef = useRef(null);
|
|
9618
|
+
const [addingFrom, setAddingFrom] = useState(null);
|
|
9619
|
+
useEffect(() => () => {
|
|
9620
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9621
|
+
}, []);
|
|
9622
|
+
const GRAPH_W = 500;
|
|
9623
|
+
const GRAPH_H = 400;
|
|
9624
|
+
const positions = useMemo(() => layoutStates(entity.states, GRAPH_W, GRAPH_H), [entity.states]);
|
|
9625
|
+
const handleStateClick = useCallback((state) => {
|
|
9626
|
+
if (playState !== "editing") return;
|
|
9627
|
+
if (addingFrom) {
|
|
9628
|
+
if (addingFrom !== state) {
|
|
9629
|
+
const event = entity.availableEvents[0] || "EVENT";
|
|
9630
|
+
const newTrans = {
|
|
9631
|
+
id: `t-${nextTransId++}`,
|
|
9632
|
+
from: addingFrom,
|
|
9633
|
+
to: state,
|
|
9634
|
+
event
|
|
9635
|
+
};
|
|
9636
|
+
setTransitions((prev) => [...prev, newTrans]);
|
|
9637
|
+
}
|
|
9638
|
+
setAddingFrom(null);
|
|
9639
|
+
} else {
|
|
9640
|
+
setSelectedState(state);
|
|
9641
|
+
}
|
|
9642
|
+
}, [playState, addingFrom, entity.availableEvents]);
|
|
9643
|
+
const handleStartAddTransition = useCallback(() => {
|
|
9644
|
+
if (!selectedState) return;
|
|
9645
|
+
setAddingFrom(selectedState);
|
|
9646
|
+
}, [selectedState]);
|
|
9647
|
+
const handleRemoveTransition = useCallback((transId) => {
|
|
9648
|
+
setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
|
|
9649
|
+
}, []);
|
|
9650
|
+
const machine = useMemo(() => ({
|
|
9651
|
+
name: entity.entityName,
|
|
9652
|
+
description: entity.description,
|
|
9653
|
+
states: entity.states,
|
|
9654
|
+
currentState,
|
|
9655
|
+
transitions: transitions.map((t2) => ({
|
|
9656
|
+
from: t2.from,
|
|
9657
|
+
to: t2.to,
|
|
9658
|
+
event: t2.event,
|
|
9659
|
+
guardHint: t2.guardHint
|
|
9660
|
+
}))
|
|
9661
|
+
}), [entity, currentState, transitions]);
|
|
9662
|
+
const handleTest = useCallback(() => {
|
|
9663
|
+
if (playState !== "editing") return;
|
|
9664
|
+
if (testEvent) emit(`UI:${testEvent}`, {});
|
|
9665
|
+
setPlayState("testing");
|
|
9666
|
+
setTestResults([]);
|
|
9667
|
+
const results = [];
|
|
9668
|
+
let testIdx = 0;
|
|
9669
|
+
const runNextTest = () => {
|
|
9670
|
+
if (testIdx >= entity.testCases.length) {
|
|
9671
|
+
const allPassed = results.every((r) => r.passed);
|
|
9672
|
+
setPlayState(allPassed ? "success" : "fail");
|
|
9673
|
+
setTestResults(results);
|
|
9674
|
+
if (allPassed && completeEvent) {
|
|
9675
|
+
emit(`UI:${completeEvent}`, {
|
|
9676
|
+
success: true,
|
|
9677
|
+
passedTests: results.filter((r) => r.passed).length
|
|
9678
|
+
});
|
|
9679
|
+
}
|
|
9680
|
+
if (!allPassed) {
|
|
9681
|
+
setAttempts((prev) => prev + 1);
|
|
9682
|
+
}
|
|
9683
|
+
return;
|
|
9684
|
+
}
|
|
9685
|
+
const testCase = entity.testCases[testIdx];
|
|
9686
|
+
let state = entity.initialState;
|
|
9687
|
+
for (const event of testCase.events) {
|
|
9688
|
+
const trans = transitions.find((t2) => t2.from === state && t2.event === event);
|
|
9689
|
+
if (trans) {
|
|
9690
|
+
state = trans.to;
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9693
|
+
setCurrentState(state);
|
|
9694
|
+
results.push({
|
|
9695
|
+
label: testCase.label,
|
|
9696
|
+
passed: state === testCase.expectedState,
|
|
9697
|
+
actualState: state,
|
|
9698
|
+
expectedState: testCase.expectedState
|
|
9699
|
+
});
|
|
9700
|
+
testIdx++;
|
|
9701
|
+
timerRef.current = setTimeout(runNextTest, stepDurationMs);
|
|
9702
|
+
};
|
|
9703
|
+
timerRef.current = setTimeout(runNextTest, stepDurationMs);
|
|
9704
|
+
}, [playState, transitions, entity, stepDurationMs, testEvent, completeEvent, emit]);
|
|
9705
|
+
const handleTryAgain = useCallback(() => {
|
|
9706
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9707
|
+
setPlayState("editing");
|
|
9708
|
+
setCurrentState(entity.initialState);
|
|
9709
|
+
setTestResults([]);
|
|
9710
|
+
}, [entity.initialState]);
|
|
9711
|
+
const handleReset = useCallback(() => {
|
|
9712
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
9713
|
+
setTransitions(entity.transitions);
|
|
9714
|
+
setPlayState("editing");
|
|
9715
|
+
setCurrentState(entity.initialState);
|
|
9716
|
+
setTestResults([]);
|
|
9717
|
+
setVariables(entity.variables);
|
|
9718
|
+
setSelectedState(null);
|
|
9719
|
+
setAddingFrom(null);
|
|
9720
|
+
setAttempts(0);
|
|
9721
|
+
}, [entity]);
|
|
9722
|
+
const codeData = useMemo(() => ({
|
|
9723
|
+
name: entity.entityName,
|
|
9724
|
+
states: entity.states,
|
|
9725
|
+
initialState: entity.initialState,
|
|
9726
|
+
transitions: transitions.map((t2) => ({
|
|
9727
|
+
from: t2.from,
|
|
9728
|
+
to: t2.to,
|
|
9729
|
+
event: t2.event,
|
|
9730
|
+
...t2.guardHint ? { guard: t2.guardHint } : {}
|
|
9731
|
+
}))
|
|
9732
|
+
}), [entity, transitions]);
|
|
9733
|
+
return /* @__PURE__ */ jsxs(
|
|
9734
|
+
VStack,
|
|
9735
|
+
{
|
|
9736
|
+
className: cn("p-4 gap-6", className),
|
|
9737
|
+
style: {
|
|
9738
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
9739
|
+
backgroundSize: "cover",
|
|
9740
|
+
backgroundPosition: "center"
|
|
9741
|
+
},
|
|
9742
|
+
children: [
|
|
9743
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
9744
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9745
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", className: "text-foreground", children: entity.title }),
|
|
9746
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: entity.description }),
|
|
9747
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center p-2 rounded bg-warning/10 border border-warning/30", gap: "xs", children: [
|
|
9748
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-warning font-bold", children: t("game.hint") + ":" }),
|
|
9749
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: entity.hint })
|
|
9750
|
+
] })
|
|
9751
|
+
] }),
|
|
9752
|
+
/* @__PURE__ */ jsxs(HStack, { className: "flex-wrap items-start", gap: "lg", children: [
|
|
9753
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "flex-1 min-w-[300px]", children: [
|
|
9754
|
+
/* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
|
|
9755
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.graph") }),
|
|
9756
|
+
addingFrom && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent animate-pulse", children: t("stateArchitect.clickTarget", { state: addingFrom || "" }) })
|
|
9757
|
+
] }),
|
|
9758
|
+
/* @__PURE__ */ jsxs(
|
|
9759
|
+
Box,
|
|
9760
|
+
{
|
|
9761
|
+
position: "relative",
|
|
9762
|
+
className: "rounded-lg border border-border bg-background overflow-hidden",
|
|
9763
|
+
style: { width: GRAPH_W, height: GRAPH_H },
|
|
9764
|
+
children: [
|
|
9765
|
+
/* @__PURE__ */ jsxs(
|
|
9766
|
+
"svg",
|
|
9767
|
+
{
|
|
9768
|
+
width: GRAPH_W,
|
|
9769
|
+
height: GRAPH_H,
|
|
9770
|
+
className: "absolute inset-0",
|
|
9771
|
+
style: { pointerEvents: "none" },
|
|
9772
|
+
children: [
|
|
9773
|
+
/* @__PURE__ */ jsxs("defs", { children: [
|
|
9774
|
+
/* @__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)" }) }),
|
|
9775
|
+
/* @__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)" }) })
|
|
9776
|
+
] }),
|
|
9777
|
+
transitions.map((t2) => {
|
|
9778
|
+
const fromPos = positions[t2.from];
|
|
9779
|
+
const toPos = positions[t2.to];
|
|
9780
|
+
if (!fromPos || !toPos) return null;
|
|
9781
|
+
const isActive = t2.from === currentState;
|
|
9782
|
+
return /* @__PURE__ */ jsx(
|
|
9783
|
+
TransitionArrow,
|
|
9784
|
+
{
|
|
9785
|
+
from: fromPos,
|
|
9786
|
+
to: toPos,
|
|
9787
|
+
eventLabel: t2.event,
|
|
9788
|
+
guardHint: t2.guardHint,
|
|
9789
|
+
isActive
|
|
9790
|
+
},
|
|
9791
|
+
t2.id
|
|
9792
|
+
);
|
|
9793
|
+
})
|
|
9794
|
+
]
|
|
9795
|
+
}
|
|
9796
|
+
),
|
|
9797
|
+
entity.states.map((state) => /* @__PURE__ */ jsx(
|
|
9798
|
+
StateNode2,
|
|
9799
|
+
{
|
|
9800
|
+
name: state,
|
|
9801
|
+
position: positions[state],
|
|
9802
|
+
isCurrent: state === currentState,
|
|
9803
|
+
isSelected: state === selectedState,
|
|
9804
|
+
isInitial: state === entity.initialState,
|
|
9805
|
+
onClick: () => handleStateClick(state)
|
|
9806
|
+
},
|
|
9807
|
+
state
|
|
9808
|
+
))
|
|
9809
|
+
]
|
|
9810
|
+
}
|
|
9811
|
+
),
|
|
9812
|
+
playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
|
|
9813
|
+
Button,
|
|
9814
|
+
{
|
|
9815
|
+
variant: "ghost",
|
|
9816
|
+
onClick: handleStartAddTransition,
|
|
9817
|
+
disabled: !selectedState,
|
|
9818
|
+
children: selectedState ? t("stateArchitect.addTransition", { state: selectedState }) : t("stateArchitect.addTransitionPrompt")
|
|
9819
|
+
}
|
|
9820
|
+
) }),
|
|
9821
|
+
transitions.length > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "p-3 rounded-lg bg-muted/50 border border-border", children: [
|
|
9822
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground font-medium", children: t("stateArchitect.transitions", { count: transitions.length }) + ":" }),
|
|
9823
|
+
transitions.map((t2) => /* @__PURE__ */ jsxs(HStack, { className: "items-center text-xs", gap: "xs", children: [
|
|
9824
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground", children: t2.from }),
|
|
9825
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: "\u2014[" }),
|
|
9826
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-accent font-medium", children: t2.event }),
|
|
9827
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: "]\u2192" }),
|
|
9828
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-success", children: t2.to }),
|
|
9829
|
+
t2.guardHint && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-warning", children: [
|
|
9830
|
+
"(",
|
|
9831
|
+
t2.guardHint,
|
|
9832
|
+
")"
|
|
9833
|
+
] }),
|
|
9834
|
+
playState === "editing" && /* @__PURE__ */ jsx(
|
|
9835
|
+
Button,
|
|
9836
|
+
{
|
|
9837
|
+
variant: "ghost",
|
|
9838
|
+
onClick: () => handleRemoveTransition(t2.id),
|
|
9839
|
+
className: "text-xs ml-auto",
|
|
9840
|
+
children: "\xD7"
|
|
9841
|
+
}
|
|
9842
|
+
)
|
|
9843
|
+
] }, t2.id))
|
|
9844
|
+
] })
|
|
9845
|
+
] }),
|
|
9846
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "w-[280px] shrink-0", children: [
|
|
9847
|
+
/* @__PURE__ */ jsx(TraitStateViewer, { trait: machine, variant: "full", size: "sm" }),
|
|
9848
|
+
/* @__PURE__ */ jsx(
|
|
9849
|
+
VariablePanel,
|
|
9850
|
+
{
|
|
9851
|
+
entityName: entity.entityName,
|
|
9852
|
+
variables
|
|
9853
|
+
}
|
|
9854
|
+
),
|
|
9855
|
+
testResults.length > 0 && /* @__PURE__ */ jsxs(VStack, { className: "p-3 rounded-lg bg-card border border-border", gap: "xs", children: [
|
|
9856
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.testResults") + ":" }),
|
|
9857
|
+
testResults.map((r, i) => /* @__PURE__ */ jsxs(HStack, { className: "items-center text-xs", gap: "xs", children: [
|
|
9858
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: r.passed ? "text-success" : "text-error", children: r.passed ? "\u2714" : "\u2717" }),
|
|
9859
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-foreground flex-1", children: r.label }),
|
|
9860
|
+
!r.passed && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-error", children: t("stateArchitect.gotState", { state: r.actualState }) })
|
|
9861
|
+
] }, i))
|
|
9862
|
+
] }),
|
|
9863
|
+
entity.showCodeView !== false && /* @__PURE__ */ jsx(CodeView, { data: codeData, label: "View Code" })
|
|
9864
|
+
] })
|
|
9865
|
+
] }),
|
|
9866
|
+
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") }) }),
|
|
9867
|
+
playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
9868
|
+
/* @__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]) }) }),
|
|
9869
|
+
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: [
|
|
9870
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
|
|
9871
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: entity.hint })
|
|
9872
|
+
] }) })
|
|
9873
|
+
] }),
|
|
9874
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
|
|
9875
|
+
playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
|
|
9876
|
+
Button,
|
|
9877
|
+
{
|
|
9878
|
+
variant: "primary",
|
|
9879
|
+
onClick: handleTest,
|
|
9880
|
+
disabled: playState !== "editing",
|
|
9881
|
+
children: "\u25B6 " + t("game.runTests")
|
|
9882
|
+
}
|
|
9883
|
+
),
|
|
9884
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleReset, children: "\u21BA " + t("game.reset") })
|
|
9885
|
+
] })
|
|
9886
|
+
]
|
|
9887
|
+
}
|
|
9888
|
+
);
|
|
9889
|
+
}
|
|
9890
|
+
StateArchitectBoard.displayName = "StateArchitectBoard";
|
|
9891
|
+
function SimulatorBoard({
|
|
9892
|
+
entity,
|
|
9893
|
+
completeEvent = "PUZZLE_COMPLETE",
|
|
9894
|
+
className
|
|
9895
|
+
}) {
|
|
9896
|
+
const { emit } = useEventBus();
|
|
9897
|
+
const { t } = useTranslate();
|
|
9898
|
+
const [values, setValues] = useState(() => {
|
|
9899
|
+
const init = {};
|
|
9900
|
+
for (const p2 of entity.parameters) {
|
|
9901
|
+
init[p2.id] = p2.initial;
|
|
9902
|
+
}
|
|
9903
|
+
return init;
|
|
9904
|
+
});
|
|
9905
|
+
const [headerError, setHeaderError] = useState(false);
|
|
9906
|
+
const [submitted, setSubmitted] = useState(false);
|
|
9907
|
+
const [attempts, setAttempts] = useState(0);
|
|
9908
|
+
const [showHint, setShowHint] = useState(false);
|
|
9909
|
+
const computeOutput = useCallback((params) => {
|
|
9910
|
+
try {
|
|
9911
|
+
const fn = new Function("params", `return (${entity.computeExpression})`);
|
|
9912
|
+
return fn(params);
|
|
9913
|
+
} catch {
|
|
9914
|
+
return 0;
|
|
9915
|
+
}
|
|
9916
|
+
}, [entity.computeExpression]);
|
|
9917
|
+
const output = useMemo(() => computeOutput(values), [computeOutput, values]);
|
|
9918
|
+
const isCorrect = Math.abs(output - entity.targetValue) <= entity.targetTolerance;
|
|
9919
|
+
const handleParameterChange = (id, value) => {
|
|
9920
|
+
if (submitted) return;
|
|
9921
|
+
setValues((prev) => ({ ...prev, [id]: value }));
|
|
9922
|
+
};
|
|
9923
|
+
const handleSubmit = () => {
|
|
9924
|
+
setSubmitted(true);
|
|
9925
|
+
setAttempts((a) => a + 1);
|
|
9926
|
+
if (isCorrect) {
|
|
9927
|
+
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
9928
|
+
}
|
|
9929
|
+
};
|
|
9930
|
+
const handleReset = () => {
|
|
9931
|
+
setSubmitted(false);
|
|
9932
|
+
if (attempts >= 2 && entity.hint) {
|
|
9933
|
+
setShowHint(true);
|
|
9934
|
+
}
|
|
9935
|
+
};
|
|
9936
|
+
const handleFullReset = () => {
|
|
9937
|
+
const init = {};
|
|
9938
|
+
for (const p2 of entity.parameters) {
|
|
9939
|
+
init[p2.id] = p2.initial;
|
|
9940
|
+
}
|
|
9941
|
+
setValues(init);
|
|
9942
|
+
setSubmitted(false);
|
|
9943
|
+
setAttempts(0);
|
|
9944
|
+
setShowHint(false);
|
|
9945
|
+
};
|
|
9946
|
+
return /* @__PURE__ */ jsx(
|
|
9947
|
+
Box,
|
|
9948
|
+
{
|
|
9949
|
+
className,
|
|
9950
|
+
style: {
|
|
9951
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
9952
|
+
backgroundSize: "cover",
|
|
9953
|
+
backgroundPosition: "center"
|
|
9954
|
+
},
|
|
9955
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
|
|
9956
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
9957
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
9958
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
|
|
9959
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
|
|
9960
|
+
] }) }),
|
|
9961
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", children: [
|
|
9962
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("simulator.parameters") }),
|
|
9963
|
+
entity.parameters.map((param) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
9964
|
+
/* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
|
|
9965
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: param.label }),
|
|
9966
|
+
/* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
|
|
9967
|
+
values[param.id],
|
|
9968
|
+
" ",
|
|
9969
|
+
param.unit
|
|
9970
|
+
] })
|
|
9971
|
+
] }),
|
|
9972
|
+
/* @__PURE__ */ jsx(
|
|
9973
|
+
"input",
|
|
9974
|
+
{
|
|
9975
|
+
type: "range",
|
|
9976
|
+
min: param.min,
|
|
9977
|
+
max: param.max,
|
|
9978
|
+
step: param.step,
|
|
9979
|
+
value: values[param.id],
|
|
9980
|
+
onChange: (e) => handleParameterChange(param.id, Number(e.target.value)),
|
|
9981
|
+
disabled: submitted,
|
|
9982
|
+
className: "w-full accent-[var(--color-foreground)]"
|
|
9983
|
+
}
|
|
9984
|
+
),
|
|
9985
|
+
/* @__PURE__ */ jsxs(HStack, { justify: "between", children: [
|
|
9986
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
|
|
9987
|
+
param.min,
|
|
9988
|
+
" ",
|
|
9989
|
+
param.unit
|
|
9990
|
+
] }),
|
|
9991
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
|
|
9992
|
+
param.max,
|
|
9993
|
+
" ",
|
|
9994
|
+
param.unit
|
|
9995
|
+
] })
|
|
9996
|
+
] })
|
|
9997
|
+
] }, param.id))
|
|
9998
|
+
] }) }),
|
|
9999
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
|
|
10000
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: entity.outputLabel }),
|
|
10001
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "h3", weight: "bold", children: [
|
|
10002
|
+
output.toFixed(2),
|
|
10003
|
+
" ",
|
|
10004
|
+
entity.outputUnit
|
|
10005
|
+
] }),
|
|
10006
|
+
submitted && /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
|
|
10007
|
+
/* @__PURE__ */ jsx(Icon, { icon: isCorrect ? CheckCircle : XCircle, size: "sm", className: isCorrect ? "text-green-600" : "text-red-600" }),
|
|
10008
|
+
/* @__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") })
|
|
10009
|
+
] }),
|
|
10010
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
|
|
10011
|
+
t("simulator.target"),
|
|
10012
|
+
": ",
|
|
10013
|
+
entity.targetValue,
|
|
10014
|
+
" ",
|
|
10015
|
+
entity.outputUnit,
|
|
10016
|
+
" (\xB1",
|
|
10017
|
+
entity.targetTolerance,
|
|
10018
|
+
")"
|
|
10019
|
+
] })
|
|
10020
|
+
] }) }),
|
|
10021
|
+
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 }) }),
|
|
10022
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
10023
|
+
!submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, children: [
|
|
10024
|
+
/* @__PURE__ */ jsx(Icon, { icon: Play, size: "sm" }),
|
|
10025
|
+
t("simulator.simulate")
|
|
10026
|
+
] }) : !isCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("simulator.tryAgain") }) : null,
|
|
10027
|
+
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
|
|
10028
|
+
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
10029
|
+
t("simulator.reset")
|
|
10030
|
+
] })
|
|
10031
|
+
] })
|
|
10032
|
+
] })
|
|
10033
|
+
}
|
|
10034
|
+
);
|
|
10035
|
+
}
|
|
10036
|
+
SimulatorBoard.displayName = "SimulatorBoard";
|
|
10037
|
+
function ClassifierBoard({
|
|
10038
|
+
entity,
|
|
10039
|
+
completeEvent = "PUZZLE_COMPLETE",
|
|
10040
|
+
className
|
|
10041
|
+
}) {
|
|
10042
|
+
const { emit } = useEventBus();
|
|
10043
|
+
const { t } = useTranslate();
|
|
10044
|
+
const [assignments, setAssignments] = useState({});
|
|
10045
|
+
const [headerError, setHeaderError] = useState(false);
|
|
10046
|
+
const [submitted, setSubmitted] = useState(false);
|
|
10047
|
+
const [attempts, setAttempts] = useState(0);
|
|
10048
|
+
const [showHint, setShowHint] = useState(false);
|
|
10049
|
+
const unassignedItems = entity.items.filter((item) => !assignments[item.id]);
|
|
10050
|
+
const allAssigned = Object.keys(assignments).length === entity.items.length;
|
|
10051
|
+
const results = submitted ? entity.items.map((item) => ({
|
|
10052
|
+
item,
|
|
10053
|
+
assigned: assignments[item.id],
|
|
10054
|
+
correct: assignments[item.id] === item.correctCategory
|
|
10055
|
+
})) : [];
|
|
10056
|
+
const allCorrect = results.length > 0 && results.every((r) => r.correct);
|
|
10057
|
+
const correctCount = results.filter((r) => r.correct).length;
|
|
10058
|
+
const handleAssign = (itemId, categoryId) => {
|
|
10059
|
+
if (submitted) return;
|
|
10060
|
+
setAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
|
|
10061
|
+
};
|
|
10062
|
+
const handleUnassign = (itemId) => {
|
|
10063
|
+
if (submitted) return;
|
|
10064
|
+
setAssignments((prev) => {
|
|
10065
|
+
const next = { ...prev };
|
|
10066
|
+
delete next[itemId];
|
|
10067
|
+
return next;
|
|
10068
|
+
});
|
|
10069
|
+
};
|
|
10070
|
+
const handleSubmit = useCallback(() => {
|
|
10071
|
+
setSubmitted(true);
|
|
10072
|
+
setAttempts((a) => a + 1);
|
|
10073
|
+
const correct = entity.items.every((item) => assignments[item.id] === item.correctCategory);
|
|
10074
|
+
if (correct) {
|
|
10075
|
+
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
10076
|
+
}
|
|
10077
|
+
}, [entity.items, assignments, attempts, completeEvent, emit]);
|
|
10078
|
+
const handleReset = () => {
|
|
10079
|
+
setSubmitted(false);
|
|
10080
|
+
if (attempts >= 2 && entity.hint) {
|
|
10081
|
+
setShowHint(true);
|
|
10082
|
+
}
|
|
10083
|
+
};
|
|
10084
|
+
const handleFullReset = () => {
|
|
10085
|
+
setAssignments({});
|
|
10086
|
+
setSubmitted(false);
|
|
10087
|
+
setAttempts(0);
|
|
10088
|
+
setShowHint(false);
|
|
10089
|
+
};
|
|
10090
|
+
return /* @__PURE__ */ jsx(
|
|
10091
|
+
Box,
|
|
10092
|
+
{
|
|
10093
|
+
className,
|
|
10094
|
+
style: {
|
|
10095
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
10096
|
+
backgroundSize: "cover",
|
|
10097
|
+
backgroundPosition: "center"
|
|
10098
|
+
},
|
|
10099
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
|
|
10100
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
10101
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10102
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
|
|
10103
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
|
|
10104
|
+
] }) }),
|
|
10105
|
+
unassignedItems.length > 0 && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10106
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("classifier.itemsToSort") }),
|
|
10107
|
+
/* @__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)) })
|
|
10108
|
+
] }) }),
|
|
10109
|
+
/* @__PURE__ */ jsx(VStack, { gap: "md", children: entity.categories.map((cat) => {
|
|
10110
|
+
const catItems = entity.items.filter((item) => assignments[item.id] === cat.id);
|
|
10111
|
+
return /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10112
|
+
/* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
|
|
10113
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: cat.label }),
|
|
10114
|
+
/* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
|
|
10115
|
+
] }),
|
|
10116
|
+
/* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
|
|
10117
|
+
const result = results.find((r) => r.item.id === item.id);
|
|
10118
|
+
return /* @__PURE__ */ jsxs(
|
|
10119
|
+
Badge,
|
|
10120
|
+
{
|
|
10121
|
+
size: "sm",
|
|
10122
|
+
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" : ""}`,
|
|
10123
|
+
onClick: () => handleUnassign(item.id),
|
|
10124
|
+
children: [
|
|
10125
|
+
item.label,
|
|
10126
|
+
result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-green-600" : "text-red-600" })
|
|
10127
|
+
]
|
|
10128
|
+
},
|
|
10129
|
+
item.id
|
|
10130
|
+
);
|
|
10131
|
+
}) }),
|
|
10132
|
+
!submitted && unassignedItems.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: unassignedItems.map((item) => /* @__PURE__ */ jsxs(
|
|
10133
|
+
Button,
|
|
10134
|
+
{
|
|
10135
|
+
size: "sm",
|
|
10136
|
+
variant: "ghost",
|
|
10137
|
+
onClick: () => handleAssign(item.id, cat.id),
|
|
10138
|
+
className: "text-xs opacity-50 hover:opacity-100",
|
|
10139
|
+
children: [
|
|
10140
|
+
"+ ",
|
|
10141
|
+
item.label
|
|
10142
|
+
]
|
|
10143
|
+
},
|
|
10144
|
+
item.id
|
|
10145
|
+
)) })
|
|
10146
|
+
] }) }, cat.id);
|
|
10147
|
+
}) }),
|
|
10148
|
+
submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
|
|
10149
|
+
/* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-green-600" : "text-red-600" }),
|
|
10150
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("classifier.allCorrect") : `${correctCount}/${entity.items.length} ${t("classifier.correct")}` }),
|
|
10151
|
+
!allCorrect && entity.failMessage && /* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-[var(--color-muted-foreground)]", children: entity.failMessage })
|
|
10152
|
+
] }) }),
|
|
10153
|
+
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 }) }),
|
|
10154
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
10155
|
+
!submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
|
|
10156
|
+
/* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
|
|
10157
|
+
t("classifier.check")
|
|
10158
|
+
] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
|
|
10159
|
+
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
|
|
10160
|
+
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
10161
|
+
t("classifier.reset")
|
|
10162
|
+
] })
|
|
10163
|
+
] })
|
|
10164
|
+
] })
|
|
10165
|
+
}
|
|
10166
|
+
);
|
|
10167
|
+
}
|
|
10168
|
+
ClassifierBoard.displayName = "ClassifierBoard";
|
|
10169
|
+
function BuilderBoard({
|
|
10170
|
+
entity,
|
|
10171
|
+
completeEvent = "PUZZLE_COMPLETE",
|
|
10172
|
+
className
|
|
10173
|
+
}) {
|
|
10174
|
+
const { emit } = useEventBus();
|
|
10175
|
+
const { t } = useTranslate();
|
|
10176
|
+
const [placements, setPlacements] = useState({});
|
|
10177
|
+
const [headerError, setHeaderError] = useState(false);
|
|
10178
|
+
const [submitted, setSubmitted] = useState(false);
|
|
10179
|
+
const [attempts, setAttempts] = useState(0);
|
|
10180
|
+
const [showHint, setShowHint] = useState(false);
|
|
10181
|
+
const usedComponentIds = new Set(Object.values(placements));
|
|
10182
|
+
const availableComponents = entity.components.filter((c) => !usedComponentIds.has(c.id));
|
|
10183
|
+
const [selectedComponent, setSelectedComponent] = useState(null);
|
|
10184
|
+
const allPlaced = Object.keys(placements).length === entity.slots.length;
|
|
10185
|
+
const results = submitted ? entity.slots.map((slot) => ({
|
|
10186
|
+
slot,
|
|
10187
|
+
placed: placements[slot.id],
|
|
10188
|
+
correct: placements[slot.id] === slot.acceptsComponentId
|
|
10189
|
+
})) : [];
|
|
10190
|
+
const allCorrect = results.length > 0 && results.every((r) => r.correct);
|
|
10191
|
+
const handlePlaceComponent = (slotId) => {
|
|
10192
|
+
if (submitted || !selectedComponent) return;
|
|
10193
|
+
setPlacements((prev) => ({ ...prev, [slotId]: selectedComponent }));
|
|
10194
|
+
setSelectedComponent(null);
|
|
10195
|
+
};
|
|
10196
|
+
const handleRemoveFromSlot = (slotId) => {
|
|
10197
|
+
if (submitted) return;
|
|
10198
|
+
setPlacements((prev) => {
|
|
10199
|
+
const next = { ...prev };
|
|
10200
|
+
delete next[slotId];
|
|
10201
|
+
return next;
|
|
10202
|
+
});
|
|
10203
|
+
};
|
|
10204
|
+
const handleSubmit = useCallback(() => {
|
|
10205
|
+
setSubmitted(true);
|
|
10206
|
+
setAttempts((a) => a + 1);
|
|
10207
|
+
const correct = entity.slots.every((slot) => placements[slot.id] === slot.acceptsComponentId);
|
|
10208
|
+
if (correct) {
|
|
10209
|
+
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
10210
|
+
}
|
|
10211
|
+
}, [entity.slots, placements, attempts, completeEvent, emit]);
|
|
10212
|
+
const handleReset = () => {
|
|
10213
|
+
setSubmitted(false);
|
|
10214
|
+
if (attempts >= 2 && entity.hint) {
|
|
10215
|
+
setShowHint(true);
|
|
10216
|
+
}
|
|
10217
|
+
};
|
|
10218
|
+
const handleFullReset = () => {
|
|
10219
|
+
setPlacements({});
|
|
10220
|
+
setSubmitted(false);
|
|
10221
|
+
setSelectedComponent(null);
|
|
10222
|
+
setAttempts(0);
|
|
10223
|
+
setShowHint(false);
|
|
10224
|
+
};
|
|
10225
|
+
const getComponentById = (id) => entity.components.find((c) => c.id === id);
|
|
10226
|
+
return /* @__PURE__ */ jsx(
|
|
10227
|
+
Box,
|
|
10228
|
+
{
|
|
10229
|
+
className,
|
|
10230
|
+
style: {
|
|
10231
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
10232
|
+
backgroundSize: "cover",
|
|
10233
|
+
backgroundPosition: "center"
|
|
10234
|
+
},
|
|
10235
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
|
|
10236
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
10237
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10238
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
|
|
10239
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description })
|
|
10240
|
+
] }) }),
|
|
10241
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10242
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("builder.components") }),
|
|
10243
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "flex-wrap", children: [
|
|
10244
|
+
availableComponents.map((comp) => /* @__PURE__ */ jsxs(
|
|
10245
|
+
Button,
|
|
10246
|
+
{
|
|
10247
|
+
size: "sm",
|
|
10248
|
+
variant: selectedComponent === comp.id ? "primary" : "secondary",
|
|
10249
|
+
onClick: () => setSelectedComponent(selectedComponent === comp.id ? null : comp.id),
|
|
10250
|
+
disabled: submitted,
|
|
10251
|
+
children: [
|
|
10252
|
+
comp.iconUrl ? /* @__PURE__ */ jsx("img", { src: comp.iconUrl, alt: "", className: "w-5 h-5 object-contain inline-block mr-1" }) : comp.iconEmoji ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10253
|
+
comp.iconEmoji,
|
|
10254
|
+
" "
|
|
10255
|
+
] }) : null,
|
|
10256
|
+
comp.label
|
|
10257
|
+
]
|
|
10258
|
+
},
|
|
10259
|
+
comp.id
|
|
10260
|
+
)),
|
|
10261
|
+
availableComponents.length === 0 && !submitted && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("builder.allPlaced") })
|
|
10262
|
+
] })
|
|
10263
|
+
] }) }),
|
|
10264
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10265
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("builder.blueprint") }),
|
|
10266
|
+
/* @__PURE__ */ jsx(VStack, { gap: "sm", children: entity.slots.map((slot) => {
|
|
10267
|
+
const placedComp = placements[slot.id] ? getComponentById(placements[slot.id]) : null;
|
|
10268
|
+
const result = results.find((r) => r.slot.id === slot.id);
|
|
10269
|
+
return /* @__PURE__ */ jsxs(
|
|
10270
|
+
HStack,
|
|
10271
|
+
{
|
|
10272
|
+
gap: "sm",
|
|
10273
|
+
align: "center",
|
|
10274
|
+
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)]"}`,
|
|
10275
|
+
onClick: () => handlePlaceComponent(slot.id),
|
|
10276
|
+
children: [
|
|
10277
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "none", className: "flex-1", children: [
|
|
10278
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "medium", children: slot.label }),
|
|
10279
|
+
slot.description && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: slot.description })
|
|
10280
|
+
] }),
|
|
10281
|
+
placedComp ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
|
|
10282
|
+
/* @__PURE__ */ jsxs(Badge, { size: "sm", onClick: () => handleRemoveFromSlot(slot.id), children: [
|
|
10283
|
+
placedComp.iconUrl ? /* @__PURE__ */ jsx("img", { src: placedComp.iconUrl, alt: "", className: "w-4 h-4 object-contain inline-block mr-1" }) : placedComp.iconEmoji ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10284
|
+
placedComp.iconEmoji,
|
|
10285
|
+
" "
|
|
10286
|
+
] }) : null,
|
|
10287
|
+
placedComp.label
|
|
10288
|
+
] }),
|
|
10289
|
+
result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "sm", className: result.correct ? "text-green-600" : "text-red-600" })
|
|
10290
|
+
] }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("builder.empty") })
|
|
10291
|
+
]
|
|
10292
|
+
},
|
|
10293
|
+
slot.id
|
|
10294
|
+
);
|
|
10295
|
+
}) })
|
|
10296
|
+
] }) }),
|
|
10297
|
+
submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
|
|
10298
|
+
/* @__PURE__ */ jsx(Icon, { icon: allCorrect ? CheckCircle : XCircle, size: "lg", className: allCorrect ? "text-green-600" : "text-red-600" }),
|
|
10299
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("builder.success") : entity.failMessage ?? t("builder.incorrect") })
|
|
10300
|
+
] }) }),
|
|
10301
|
+
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 }) }),
|
|
10302
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
10303
|
+
!submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allPlaced, children: [
|
|
10304
|
+
/* @__PURE__ */ jsx(Icon, { icon: Wrench, size: "sm" }),
|
|
10305
|
+
t("builder.build")
|
|
10306
|
+
] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("builder.tryAgain") }) : null,
|
|
10307
|
+
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
|
|
10308
|
+
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
10309
|
+
t("builder.reset")
|
|
10310
|
+
] })
|
|
10311
|
+
] })
|
|
10312
|
+
] })
|
|
10313
|
+
}
|
|
10314
|
+
);
|
|
10315
|
+
}
|
|
10316
|
+
BuilderBoard.displayName = "BuilderBoard";
|
|
10317
|
+
function DebuggerBoard({
|
|
10318
|
+
entity,
|
|
10319
|
+
completeEvent = "PUZZLE_COMPLETE",
|
|
10320
|
+
className
|
|
10321
|
+
}) {
|
|
10322
|
+
const { emit } = useEventBus();
|
|
10323
|
+
const { t } = useTranslate();
|
|
10324
|
+
const [flaggedLines, setFlaggedLines] = useState(/* @__PURE__ */ new Set());
|
|
10325
|
+
const [headerError, setHeaderError] = useState(false);
|
|
10326
|
+
const [submitted, setSubmitted] = useState(false);
|
|
10327
|
+
const [attempts, setAttempts] = useState(0);
|
|
10328
|
+
const [showHint, setShowHint] = useState(false);
|
|
10329
|
+
const toggleLine = (lineId) => {
|
|
10330
|
+
if (submitted) return;
|
|
10331
|
+
setFlaggedLines((prev) => {
|
|
10332
|
+
const next = new Set(prev);
|
|
10333
|
+
if (next.has(lineId)) {
|
|
10334
|
+
next.delete(lineId);
|
|
10335
|
+
} else {
|
|
10336
|
+
next.add(lineId);
|
|
10337
|
+
}
|
|
10338
|
+
return next;
|
|
10339
|
+
});
|
|
10340
|
+
};
|
|
10341
|
+
const bugLines = entity.lines.filter((l) => l.isBug);
|
|
10342
|
+
const correctFlags = entity.lines.filter((l) => l.isBug && flaggedLines.has(l.id));
|
|
10343
|
+
const falseFlags = entity.lines.filter((l) => !l.isBug && flaggedLines.has(l.id));
|
|
10344
|
+
const allCorrect = submitted && correctFlags.length === bugLines.length && falseFlags.length === 0;
|
|
10345
|
+
const handleSubmit = useCallback(() => {
|
|
10346
|
+
setSubmitted(true);
|
|
10347
|
+
setAttempts((a) => a + 1);
|
|
10348
|
+
const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
|
|
10349
|
+
if (correct) {
|
|
10350
|
+
emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
|
|
10351
|
+
}
|
|
10352
|
+
}, [correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
|
|
10353
|
+
const handleReset = () => {
|
|
10354
|
+
setSubmitted(false);
|
|
10355
|
+
if (attempts >= 2 && entity.hint) {
|
|
10356
|
+
setShowHint(true);
|
|
10357
|
+
}
|
|
10358
|
+
};
|
|
10359
|
+
const handleFullReset = () => {
|
|
10360
|
+
setFlaggedLines(/* @__PURE__ */ new Set());
|
|
10361
|
+
setSubmitted(false);
|
|
10362
|
+
setAttempts(0);
|
|
10363
|
+
setShowHint(false);
|
|
10364
|
+
};
|
|
10365
|
+
return /* @__PURE__ */ jsx(
|
|
10366
|
+
Box,
|
|
10367
|
+
{
|
|
10368
|
+
className,
|
|
10369
|
+
style: {
|
|
10370
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
10371
|
+
backgroundSize: "cover",
|
|
10372
|
+
backgroundPosition: "center"
|
|
10373
|
+
},
|
|
10374
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
|
|
10375
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
10376
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10377
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
|
|
10378
|
+
/* @__PURE__ */ jsx(Icon, { icon: Bug, size: "sm" }),
|
|
10379
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title })
|
|
10380
|
+
] }),
|
|
10381
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description }),
|
|
10382
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("debugger.findBugs", { count: String(entity.bugCount) }) })
|
|
10383
|
+
] }) }),
|
|
10384
|
+
/* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: entity.lines.map((line, i) => {
|
|
10385
|
+
const isFlagged = flaggedLines.has(line.id);
|
|
10386
|
+
let lineStyle = "";
|
|
10387
|
+
if (submitted) {
|
|
10388
|
+
if (line.isBug && isFlagged) lineStyle = "bg-green-50 dark:bg-green-950";
|
|
10389
|
+
else if (line.isBug && !isFlagged) lineStyle = "bg-yellow-50 dark:bg-yellow-950";
|
|
10390
|
+
else if (!line.isBug && isFlagged) lineStyle = "bg-red-50 dark:bg-red-950";
|
|
10391
|
+
} else if (isFlagged) {
|
|
10392
|
+
lineStyle = "bg-red-50 dark:bg-red-950";
|
|
10393
|
+
}
|
|
10394
|
+
return /* @__PURE__ */ jsxs(
|
|
10395
|
+
HStack,
|
|
10396
|
+
{
|
|
10397
|
+
gap: "none",
|
|
10398
|
+
align: "stretch",
|
|
10399
|
+
className: `border-b border-[var(--color-border)] cursor-pointer hover:bg-[var(--color-muted)] ${lineStyle}`,
|
|
10400
|
+
onClick: () => toggleLine(line.id),
|
|
10401
|
+
children: [
|
|
10402
|
+
/* @__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 }) }),
|
|
10403
|
+
/* @__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 }) }),
|
|
10404
|
+
/* @__PURE__ */ jsxs(Box, { className: "w-8 flex-shrink-0 flex items-center justify-center", children: [
|
|
10405
|
+
isFlagged && /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "xs", className: "text-red-600" }),
|
|
10406
|
+
submitted && line.isBug && !isFlagged && /* @__PURE__ */ jsx(Icon, { icon: Bug, size: "xs", className: "text-yellow-600" })
|
|
10407
|
+
] })
|
|
10408
|
+
]
|
|
10409
|
+
},
|
|
10410
|
+
line.id
|
|
10411
|
+
);
|
|
10412
|
+
}) }) }),
|
|
10413
|
+
submitted && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10414
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: allCorrect ? entity.successMessage ?? t("debugger.allFound") : `${correctFlags.length}/${bugLines.length} ${t("debugger.bugsFound")}` }),
|
|
10415
|
+
bugLines.map((line) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "start", children: [
|
|
10416
|
+
/* @__PURE__ */ jsx(
|
|
10417
|
+
Icon,
|
|
10418
|
+
{
|
|
10419
|
+
icon: flaggedLines.has(line.id) ? CheckCircle : XCircle,
|
|
10420
|
+
size: "xs",
|
|
10421
|
+
className: flaggedLines.has(line.id) ? "text-green-600 mt-0.5" : "text-yellow-600 mt-0.5"
|
|
10422
|
+
}
|
|
10423
|
+
),
|
|
10424
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "none", children: [
|
|
10425
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", className: "font-mono", children: line.content.trim() }),
|
|
10426
|
+
line.explanation && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: line.explanation })
|
|
10427
|
+
] })
|
|
10428
|
+
] }, line.id))
|
|
10429
|
+
] }) }),
|
|
10430
|
+
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 }) }),
|
|
10431
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
|
|
10432
|
+
!submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.size === 0, children: [
|
|
10433
|
+
/* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
|
|
10434
|
+
t("debugger.submit")
|
|
10435
|
+
] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
|
|
10436
|
+
/* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
|
|
10437
|
+
/* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
|
|
10438
|
+
t("debugger.reset")
|
|
10439
|
+
] })
|
|
10440
|
+
] })
|
|
10441
|
+
] })
|
|
10442
|
+
}
|
|
10443
|
+
);
|
|
10444
|
+
}
|
|
10445
|
+
DebuggerBoard.displayName = "DebuggerBoard";
|
|
10446
|
+
function getOpponentAction(strategy, actions, history) {
|
|
10447
|
+
const actionIds = actions.map((a) => a.id);
|
|
10448
|
+
switch (strategy) {
|
|
10449
|
+
case "always-cooperate":
|
|
10450
|
+
return actionIds[0];
|
|
10451
|
+
case "always-defect":
|
|
10452
|
+
return actionIds[actionIds.length - 1];
|
|
10453
|
+
case "tit-for-tat":
|
|
10454
|
+
if (history.length === 0) return actionIds[0];
|
|
10455
|
+
return history[history.length - 1].playerAction;
|
|
10456
|
+
case "random":
|
|
10457
|
+
default:
|
|
10458
|
+
return actionIds[Math.floor(Math.random() * actionIds.length)];
|
|
10459
|
+
}
|
|
10460
|
+
}
|
|
10461
|
+
function NegotiatorBoard({
|
|
10462
|
+
entity,
|
|
10463
|
+
completeEvent = "PUZZLE_COMPLETE",
|
|
10464
|
+
className
|
|
10465
|
+
}) {
|
|
10466
|
+
const { emit } = useEventBus();
|
|
10467
|
+
const { t } = useTranslate();
|
|
10468
|
+
const [history, setHistory] = useState([]);
|
|
10469
|
+
const [headerError, setHeaderError] = useState(false);
|
|
10470
|
+
const [showHint, setShowHint] = useState(false);
|
|
10471
|
+
const currentRound = history.length;
|
|
10472
|
+
const isComplete = currentRound >= entity.totalRounds;
|
|
10473
|
+
const playerTotal = history.reduce((s, r) => s + r.playerPayoff, 0);
|
|
10474
|
+
const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
|
|
10475
|
+
const won = isComplete && playerTotal >= entity.targetScore;
|
|
10476
|
+
const handleAction = useCallback((actionId) => {
|
|
10477
|
+
if (isComplete) return;
|
|
10478
|
+
const opponentAction = getOpponentAction(entity.opponentStrategy, entity.actions, history);
|
|
10479
|
+
const payoff = entity.payoffMatrix.find(
|
|
10480
|
+
(p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
|
|
10481
|
+
);
|
|
10482
|
+
const result = {
|
|
10483
|
+
round: currentRound + 1,
|
|
10484
|
+
playerAction: actionId,
|
|
10485
|
+
opponentAction,
|
|
10486
|
+
playerPayoff: payoff?.playerPayoff ?? 0,
|
|
10487
|
+
opponentPayoff: payoff?.opponentPayoff ?? 0
|
|
10488
|
+
};
|
|
10489
|
+
const newHistory = [...history, result];
|
|
10490
|
+
setHistory(newHistory);
|
|
10491
|
+
if (newHistory.length >= entity.totalRounds) {
|
|
10492
|
+
const total = newHistory.reduce((s, r) => s + r.playerPayoff, 0);
|
|
10493
|
+
if (total >= entity.targetScore) {
|
|
10494
|
+
emit(`UI:${completeEvent}`, { success: true, score: total });
|
|
10495
|
+
}
|
|
10496
|
+
if (newHistory.length >= 3 && entity.hint) {
|
|
10497
|
+
setShowHint(true);
|
|
10498
|
+
}
|
|
10499
|
+
}
|
|
10500
|
+
}, [isComplete, entity, history, currentRound, completeEvent, emit]);
|
|
10501
|
+
const handleReset = () => {
|
|
10502
|
+
setHistory([]);
|
|
10503
|
+
setShowHint(false);
|
|
10504
|
+
};
|
|
10505
|
+
const getActionLabel = (id) => entity.actions.find((a) => a.id === id)?.label ?? id;
|
|
10506
|
+
return /* @__PURE__ */ jsx(
|
|
10507
|
+
Box,
|
|
10508
|
+
{
|
|
10509
|
+
className,
|
|
10510
|
+
style: {
|
|
10511
|
+
backgroundImage: entity.theme?.background ? `url(${entity.theme.background})` : void 0,
|
|
10512
|
+
backgroundSize: "cover",
|
|
10513
|
+
backgroundPosition: "center"
|
|
10514
|
+
},
|
|
10515
|
+
children: /* @__PURE__ */ jsxs(VStack, { gap: "lg", className: "p-4", children: [
|
|
10516
|
+
entity.headerImage && !headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx("img", { src: entity.headerImage, alt: "", onError: () => setHeaderError(true), className: "w-full h-full object-cover" }) }) : entity.headerImage && headerError ? /* @__PURE__ */ jsx(Box, { className: "w-full h-32 rounded-lg bg-gradient-to-br from-[var(--color-muted)] to-[var(--color-accent)] opacity-60" }) : null,
|
|
10517
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10518
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: entity.title }),
|
|
10519
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", children: entity.description }),
|
|
10520
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "md", children: [
|
|
10521
|
+
/* @__PURE__ */ jsx(Badge, { size: "sm", children: t("negotiator.round", { current: String(currentRound), total: String(entity.totalRounds) }) }),
|
|
10522
|
+
/* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
|
|
10523
|
+
t("negotiator.target"),
|
|
10524
|
+
": ",
|
|
10525
|
+
entity.targetScore
|
|
10526
|
+
] })
|
|
10527
|
+
] })
|
|
10528
|
+
] }) }),
|
|
10529
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "md", justify: "center", children: [
|
|
10530
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4 flex-1 text-center", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", children: [
|
|
10531
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("negotiator.you") }),
|
|
10532
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "bold", children: playerTotal })
|
|
10533
|
+
] }) }),
|
|
10534
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4 flex-1 text-center", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", children: [
|
|
10535
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: t("negotiator.opponent") }),
|
|
10536
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "bold", children: opponentTotal })
|
|
10537
|
+
] }) })
|
|
10538
|
+
] }),
|
|
10539
|
+
!isComplete && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10540
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("negotiator.chooseAction") }),
|
|
10541
|
+
/* @__PURE__ */ jsx(HStack, { gap: "sm", justify: "center", className: "flex-wrap", children: entity.actions.map((action) => /* @__PURE__ */ jsx(
|
|
10542
|
+
Button,
|
|
10543
|
+
{
|
|
10544
|
+
variant: "primary",
|
|
10545
|
+
onClick: () => handleAction(action.id),
|
|
10546
|
+
children: action.label
|
|
10547
|
+
},
|
|
10548
|
+
action.id
|
|
10549
|
+
)) })
|
|
10550
|
+
] }) }),
|
|
10551
|
+
history.length > 0 && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
10552
|
+
/* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "uppercase tracking-wider text-[var(--color-muted-foreground)]", children: t("negotiator.history") }),
|
|
10553
|
+
history.map((round) => /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className: "text-sm", children: [
|
|
10554
|
+
/* @__PURE__ */ jsxs(Badge, { size: "sm", children: [
|
|
10555
|
+
"R",
|
|
10556
|
+
round.round
|
|
10557
|
+
] }),
|
|
10558
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", children: getActionLabel(round.playerAction) }),
|
|
10559
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: "vs" }),
|
|
10560
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", children: getActionLabel(round.opponentAction) }),
|
|
10561
|
+
/* @__PURE__ */ jsx(Icon, { icon: ArrowRight, size: "xs" }),
|
|
10562
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", weight: "bold", className: "text-green-600", children: [
|
|
10563
|
+
"+",
|
|
10564
|
+
round.playerPayoff
|
|
10565
|
+
] }),
|
|
10566
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
|
|
10567
|
+
"/ +",
|
|
10568
|
+
round.opponentPayoff
|
|
10569
|
+
] })
|
|
10570
|
+
] }, round.round))
|
|
10571
|
+
] }) }),
|
|
10572
|
+
isComplete && /* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
|
|
10573
|
+
/* @__PURE__ */ jsx(Icon, { icon: CheckCircle, size: "lg", className: won ? "text-green-600" : "text-red-600" }),
|
|
10574
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body", weight: "bold", children: won ? entity.successMessage ?? t("negotiator.success") : entity.failMessage ?? t("negotiator.failed") }),
|
|
10575
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-[var(--color-muted-foreground)]", children: [
|
|
10576
|
+
t("negotiator.finalScore"),
|
|
10577
|
+
": ",
|
|
10578
|
+
playerTotal,
|
|
10579
|
+
"/",
|
|
10580
|
+
entity.targetScore
|
|
10581
|
+
] })
|
|
10582
|
+
] }) }),
|
|
10583
|
+
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 }) }),
|
|
10584
|
+
isComplete && !won && /* @__PURE__ */ jsx(HStack, { justify: "center", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("negotiator.playAgain") }) })
|
|
10585
|
+
] })
|
|
10586
|
+
}
|
|
10587
|
+
);
|
|
10588
|
+
}
|
|
10589
|
+
NegotiatorBoard.displayName = "NegotiatorBoard";
|
|
10590
|
+
function SimulationCanvas({
|
|
10591
|
+
preset,
|
|
10592
|
+
width = 600,
|
|
10593
|
+
height = 400,
|
|
10594
|
+
running,
|
|
10595
|
+
speed = 1,
|
|
10596
|
+
className
|
|
10597
|
+
}) {
|
|
10598
|
+
const canvasRef = useRef(null);
|
|
10599
|
+
const bodiesRef = useRef(structuredClone(preset.bodies));
|
|
10600
|
+
useEffect(() => {
|
|
10601
|
+
bodiesRef.current = structuredClone(preset.bodies);
|
|
10602
|
+
}, [preset]);
|
|
10603
|
+
const step = useCallback(() => {
|
|
10604
|
+
const dt = 1 / 60 * speed;
|
|
10605
|
+
const gx = preset.gravity?.x ?? 0;
|
|
10606
|
+
const gy = preset.gravity?.y ?? 9.81;
|
|
10607
|
+
const bodies = bodiesRef.current;
|
|
10608
|
+
for (const body of bodies) {
|
|
10609
|
+
if (body.fixed) continue;
|
|
10610
|
+
body.vx += gx * dt;
|
|
10611
|
+
body.vy += gy * dt;
|
|
10612
|
+
body.x += body.vx * dt;
|
|
10613
|
+
body.y += body.vy * dt;
|
|
10614
|
+
if (body.y + body.radius > height) {
|
|
10615
|
+
body.y = height - body.radius;
|
|
10616
|
+
body.vy = -body.vy * 0.8;
|
|
10617
|
+
}
|
|
10618
|
+
if (body.x + body.radius > width) {
|
|
10619
|
+
body.x = width - body.radius;
|
|
10620
|
+
body.vx = -body.vx * 0.8;
|
|
10621
|
+
}
|
|
10622
|
+
if (body.x - body.radius < 0) {
|
|
10623
|
+
body.x = body.radius;
|
|
10624
|
+
body.vx = -body.vx * 0.8;
|
|
10625
|
+
}
|
|
10626
|
+
}
|
|
10627
|
+
if (preset.constraints) {
|
|
10628
|
+
for (const c of preset.constraints) {
|
|
10629
|
+
const a = bodies[c.bodyA];
|
|
10630
|
+
const b = bodies[c.bodyB];
|
|
10631
|
+
if (!a || !b) continue;
|
|
10632
|
+
const dx = b.x - a.x;
|
|
10633
|
+
const dy = b.y - a.y;
|
|
10634
|
+
const dist = Math.sqrt(dx * dx + dy * dy) || 1e-3;
|
|
10635
|
+
const diff = (dist - c.length) / dist;
|
|
10636
|
+
const fx = dx * diff * c.stiffness;
|
|
10637
|
+
const fy = dy * diff * c.stiffness;
|
|
10638
|
+
if (!a.fixed) {
|
|
10639
|
+
a.vx += fx * dt;
|
|
10640
|
+
a.vy += fy * dt;
|
|
10641
|
+
}
|
|
10642
|
+
if (!b.fixed) {
|
|
10643
|
+
b.vx -= fx * dt;
|
|
10644
|
+
b.vy -= fy * dt;
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
}
|
|
10648
|
+
}, [preset, width, height, speed]);
|
|
10649
|
+
const draw = useCallback(() => {
|
|
10650
|
+
const canvas = canvasRef.current;
|
|
10651
|
+
if (!canvas) return;
|
|
10652
|
+
const ctx = canvas.getContext("2d");
|
|
10653
|
+
if (!ctx) return;
|
|
10654
|
+
const bodies = bodiesRef.current;
|
|
10655
|
+
ctx.clearRect(0, 0, width, height);
|
|
10656
|
+
ctx.fillStyle = preset.backgroundColor ?? "#1a1a2e";
|
|
10657
|
+
ctx.fillRect(0, 0, width, height);
|
|
10658
|
+
if (preset.constraints) {
|
|
10659
|
+
for (const c of preset.constraints) {
|
|
10660
|
+
const a = bodies[c.bodyA];
|
|
10661
|
+
const b = bodies[c.bodyB];
|
|
10662
|
+
if (a && b) {
|
|
10663
|
+
ctx.beginPath();
|
|
10664
|
+
ctx.moveTo(a.x, a.y);
|
|
10665
|
+
ctx.lineTo(b.x, b.y);
|
|
10666
|
+
ctx.strokeStyle = "#533483";
|
|
10667
|
+
ctx.lineWidth = 1;
|
|
10668
|
+
ctx.setLineDash([4, 4]);
|
|
10669
|
+
ctx.stroke();
|
|
10670
|
+
ctx.setLineDash([]);
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
10673
|
+
}
|
|
10674
|
+
for (const body of bodies) {
|
|
10675
|
+
ctx.beginPath();
|
|
10676
|
+
ctx.arc(body.x, body.y, body.radius, 0, Math.PI * 2);
|
|
10677
|
+
ctx.fillStyle = body.color ?? "#e94560";
|
|
10678
|
+
ctx.fill();
|
|
10679
|
+
if (preset.showVelocity) {
|
|
10680
|
+
ctx.beginPath();
|
|
10681
|
+
ctx.moveTo(body.x, body.y);
|
|
10682
|
+
ctx.lineTo(body.x + body.vx * 0.1, body.y + body.vy * 0.1);
|
|
10683
|
+
ctx.strokeStyle = "#16213e";
|
|
10684
|
+
ctx.lineWidth = 2;
|
|
10685
|
+
ctx.stroke();
|
|
10686
|
+
}
|
|
10687
|
+
}
|
|
10688
|
+
}, [width, height, preset]);
|
|
10689
|
+
useEffect(() => {
|
|
10690
|
+
if (!running) return;
|
|
10691
|
+
let raf;
|
|
10692
|
+
const loop = () => {
|
|
10693
|
+
step();
|
|
10694
|
+
draw();
|
|
10695
|
+
raf = requestAnimationFrame(loop);
|
|
10696
|
+
};
|
|
10697
|
+
raf = requestAnimationFrame(loop);
|
|
10698
|
+
return () => cancelAnimationFrame(raf);
|
|
10699
|
+
}, [running, step, draw]);
|
|
10700
|
+
useEffect(() => {
|
|
10701
|
+
draw();
|
|
10702
|
+
}, [draw]);
|
|
10703
|
+
return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded-md" }) });
|
|
10704
|
+
}
|
|
10705
|
+
SimulationCanvas.displayName = "SimulationCanvas";
|
|
10706
|
+
function SimulationControls({
|
|
10707
|
+
running,
|
|
10708
|
+
speed,
|
|
10709
|
+
parameters,
|
|
10710
|
+
onPlay,
|
|
10711
|
+
onPause,
|
|
10712
|
+
onStep,
|
|
10713
|
+
onReset,
|
|
10714
|
+
onSpeedChange,
|
|
10715
|
+
onParameterChange,
|
|
10716
|
+
className
|
|
10717
|
+
}) {
|
|
10718
|
+
return /* @__PURE__ */ jsxs(VStack, { gap: "md", className, children: [
|
|
10719
|
+
/* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", children: [
|
|
10720
|
+
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" }),
|
|
10721
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onStep, icon: SkipForward, disabled: running, children: "Step" }),
|
|
10722
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onReset, icon: RotateCcw, children: "Reset" })
|
|
10723
|
+
] }),
|
|
10724
|
+
/* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
10725
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
|
|
10726
|
+
"Speed: ",
|
|
10727
|
+
speed.toFixed(1),
|
|
10728
|
+
"x"
|
|
10729
|
+
] }),
|
|
10730
|
+
/* @__PURE__ */ jsx(
|
|
10731
|
+
"input",
|
|
10732
|
+
{
|
|
10733
|
+
type: "range",
|
|
10734
|
+
min: 0.1,
|
|
10735
|
+
max: 5,
|
|
10736
|
+
step: 0.1,
|
|
10737
|
+
value: speed,
|
|
10738
|
+
onChange: (e) => onSpeedChange(parseFloat(e.target.value)),
|
|
10739
|
+
className: "w-full"
|
|
10740
|
+
}
|
|
10741
|
+
)
|
|
10742
|
+
] }),
|
|
10743
|
+
Object.entries(parameters).map(([name, param]) => /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
10744
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
|
|
10745
|
+
param.label,
|
|
10746
|
+
": ",
|
|
10747
|
+
param.value.toFixed(2)
|
|
10748
|
+
] }),
|
|
10749
|
+
/* @__PURE__ */ jsx(
|
|
10750
|
+
"input",
|
|
10751
|
+
{
|
|
10752
|
+
type: "range",
|
|
10753
|
+
min: param.min,
|
|
10754
|
+
max: param.max,
|
|
10755
|
+
step: param.step,
|
|
10756
|
+
value: param.value,
|
|
10757
|
+
onChange: (e) => onParameterChange(name, parseFloat(e.target.value)),
|
|
10758
|
+
className: "w-full"
|
|
10759
|
+
}
|
|
10760
|
+
)
|
|
10761
|
+
] }, name))
|
|
10762
|
+
] });
|
|
10763
|
+
}
|
|
10764
|
+
SimulationControls.displayName = "SimulationControls";
|
|
10765
|
+
function SimulationGraph({
|
|
10766
|
+
label,
|
|
10767
|
+
unit,
|
|
10768
|
+
data,
|
|
10769
|
+
maxPoints = 200,
|
|
10770
|
+
width = 300,
|
|
10771
|
+
height = 120,
|
|
10772
|
+
color = "#e94560",
|
|
10773
|
+
className
|
|
10774
|
+
}) {
|
|
10775
|
+
const canvasRef = useRef(null);
|
|
10776
|
+
const visibleData = data.slice(-maxPoints);
|
|
10777
|
+
useEffect(() => {
|
|
10778
|
+
const canvas = canvasRef.current;
|
|
10779
|
+
if (!canvas || visibleData.length < 2) return;
|
|
10780
|
+
const ctx = canvas.getContext("2d");
|
|
10781
|
+
if (!ctx) return;
|
|
10782
|
+
ctx.clearRect(0, 0, width, height);
|
|
10783
|
+
ctx.fillStyle = "#0f0f23";
|
|
10784
|
+
ctx.fillRect(0, 0, width, height);
|
|
10785
|
+
ctx.strokeStyle = "#1a1a3e";
|
|
10786
|
+
ctx.lineWidth = 0.5;
|
|
10787
|
+
for (let i = 0; i < 5; i++) {
|
|
10788
|
+
const y = height / 5 * i;
|
|
10789
|
+
ctx.beginPath();
|
|
10790
|
+
ctx.moveTo(0, y);
|
|
10791
|
+
ctx.lineTo(width, y);
|
|
10792
|
+
ctx.stroke();
|
|
10793
|
+
}
|
|
10794
|
+
let minVal = Infinity;
|
|
10795
|
+
let maxVal = -Infinity;
|
|
10796
|
+
for (const pt of visibleData) {
|
|
10797
|
+
if (pt.value < minVal) minVal = pt.value;
|
|
10798
|
+
if (pt.value > maxVal) maxVal = pt.value;
|
|
10799
|
+
}
|
|
10800
|
+
const range = maxVal - minVal || 1;
|
|
10801
|
+
const pad = height * 0.1;
|
|
10802
|
+
ctx.beginPath();
|
|
10803
|
+
ctx.strokeStyle = color;
|
|
10804
|
+
ctx.lineWidth = 2;
|
|
10805
|
+
for (let i = 0; i < visibleData.length; i++) {
|
|
10806
|
+
const x = i / (maxPoints - 1) * width;
|
|
10807
|
+
const y = pad + (maxVal - visibleData[i].value) / range * (height - 2 * pad);
|
|
10808
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
10809
|
+
else ctx.lineTo(x, y);
|
|
10810
|
+
}
|
|
10811
|
+
ctx.stroke();
|
|
10812
|
+
const last = visibleData[visibleData.length - 1];
|
|
10813
|
+
ctx.fillStyle = color;
|
|
10814
|
+
ctx.font = "12px monospace";
|
|
10815
|
+
ctx.fillText(`${last.value.toFixed(2)} ${unit}`, width - 80, 16);
|
|
10816
|
+
}, [visibleData, width, height, color, unit, maxPoints]);
|
|
10817
|
+
return /* @__PURE__ */ jsx(Card, { padding: "sm", className, children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
|
|
10818
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", weight: "bold", children: label }),
|
|
10819
|
+
/* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded" })
|
|
10820
|
+
] }) });
|
|
10821
|
+
}
|
|
10822
|
+
SimulationGraph.displayName = "SimulationGraph";
|
|
10823
|
+
|
|
10824
|
+
// components/organisms/game/physics-sim/presets/mechanics.ts
|
|
10825
|
+
var projectileMotion = {
|
|
10826
|
+
id: "mechanics-projectile",
|
|
10827
|
+
name: "Projectile Motion",
|
|
10828
|
+
description: "Launch a ball and observe parabolic trajectory under gravity.",
|
|
10829
|
+
domain: "natural",
|
|
10830
|
+
gravity: { x: 0, y: 9.81 },
|
|
10831
|
+
bodies: [
|
|
10832
|
+
{ id: "ball", x: 50, y: 350, vx: 80, vy: -120, mass: 1, radius: 10, color: "#e94560", fixed: false },
|
|
10833
|
+
{ id: "ground", x: 300, y: 390, vx: 0, vy: 0, mass: 1e3, radius: 400, color: "#333", fixed: true }
|
|
10834
|
+
],
|
|
10835
|
+
showVelocity: true,
|
|
10836
|
+
parameters: {
|
|
10837
|
+
angle: { value: 45, min: 0, max: 90, step: 1, label: "Launch angle (deg)" },
|
|
10838
|
+
velocity: { value: 100, min: 10, max: 200, step: 5, label: "Initial velocity (m/s)" },
|
|
10839
|
+
gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
|
|
10840
|
+
}
|
|
10841
|
+
};
|
|
10842
|
+
var pendulum = {
|
|
10843
|
+
id: "mechanics-pendulum",
|
|
10844
|
+
name: "Simple Pendulum",
|
|
10845
|
+
description: "A mass on a string swinging under gravity.",
|
|
10846
|
+
domain: "natural",
|
|
10847
|
+
gravity: { x: 0, y: 9.81 },
|
|
10848
|
+
bodies: [
|
|
10849
|
+
{ id: "pivot", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 5, color: "#888", fixed: true },
|
|
10850
|
+
{ id: "bob", x: 400, y: 200, vx: 0, vy: 0, mass: 5, radius: 15, color: "#e94560", fixed: false }
|
|
10851
|
+
],
|
|
10852
|
+
constraints: [{ bodyA: 0, bodyB: 1, length: 180, stiffness: 1 }],
|
|
10853
|
+
parameters: {
|
|
10854
|
+
length: { value: 180, min: 50, max: 300, step: 10, label: "String length (px)" },
|
|
10855
|
+
mass: { value: 5, min: 1, max: 20, step: 0.5, label: "Mass (kg)" },
|
|
10856
|
+
gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
|
|
10857
|
+
}
|
|
10858
|
+
};
|
|
10859
|
+
var springOscillator = {
|
|
10860
|
+
id: "mechanics-spring",
|
|
10861
|
+
name: "Spring Oscillator",
|
|
10862
|
+
description: "A mass bouncing on a spring \u2014 simple harmonic motion.",
|
|
10863
|
+
domain: "natural",
|
|
10864
|
+
gravity: { x: 0, y: 0 },
|
|
10865
|
+
bodies: [
|
|
10866
|
+
{ id: "anchor", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 8, color: "#888", fixed: true },
|
|
10867
|
+
{ id: "mass", x: 300, y: 250, vx: 0, vy: 0, mass: 2, radius: 20, color: "#0f3460", fixed: false }
|
|
10868
|
+
],
|
|
10869
|
+
constraints: [{ bodyA: 0, bodyB: 1, length: 150, stiffness: 0.5 }],
|
|
10870
|
+
parameters: {
|
|
10871
|
+
stiffness: { value: 0.5, min: 0.1, max: 2, step: 0.05, label: "Spring stiffness (k)" },
|
|
10872
|
+
mass: { value: 2, min: 0.5, max: 10, step: 0.5, label: "Mass (kg)" },
|
|
10873
|
+
damping: { value: 0, min: 0, max: 0.5, step: 0.01, label: "Damping" }
|
|
10874
|
+
}
|
|
10875
|
+
};
|
|
10876
|
+
|
|
10877
|
+
// components/organisms/game/physics-sim/presets/index.ts
|
|
10878
|
+
var ALL_PRESETS = [
|
|
10879
|
+
projectileMotion,
|
|
10880
|
+
pendulum,
|
|
10881
|
+
springOscillator
|
|
10882
|
+
];
|
|
10883
|
+
var eventIcons = {
|
|
10884
|
+
attack: Sword,
|
|
10885
|
+
defend: Shield,
|
|
10886
|
+
heal: Heart,
|
|
10887
|
+
move: Move,
|
|
10888
|
+
special: Zap,
|
|
10889
|
+
death: Sword,
|
|
10890
|
+
spawn: Zap
|
|
10891
|
+
};
|
|
10892
|
+
var eventColors = {
|
|
10893
|
+
attack: "text-error",
|
|
10894
|
+
defend: "text-info",
|
|
10895
|
+
heal: "text-success",
|
|
10896
|
+
move: "text-primary",
|
|
10897
|
+
special: "text-warning",
|
|
10898
|
+
death: "text-muted-foreground",
|
|
10899
|
+
spawn: "text-accent"
|
|
10900
|
+
};
|
|
10901
|
+
var eventBadgeVariants = {
|
|
10902
|
+
attack: "danger",
|
|
10903
|
+
defend: "primary",
|
|
10904
|
+
heal: "success",
|
|
10905
|
+
move: "warning",
|
|
10906
|
+
special: "secondary",
|
|
10907
|
+
death: "secondary",
|
|
10908
|
+
spawn: "secondary"
|
|
10909
|
+
};
|
|
10910
|
+
function CombatLog({
|
|
10911
|
+
events,
|
|
10912
|
+
maxVisible = 50,
|
|
10913
|
+
autoScroll = true,
|
|
10914
|
+
showTimestamps = false,
|
|
10915
|
+
className,
|
|
10916
|
+
title = "Combat Log"
|
|
10917
|
+
}) {
|
|
10918
|
+
const scrollRef = useRef(null);
|
|
10919
|
+
useEffect(() => {
|
|
10920
|
+
if (autoScroll && scrollRef.current) {
|
|
10921
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
10922
|
+
}
|
|
10923
|
+
}, [events, autoScroll]);
|
|
10924
|
+
const visibleEvents = events.slice(-maxVisible);
|
|
10925
|
+
return /* @__PURE__ */ jsxs(Card, { variant: "default", className: cn("flex flex-col", className), children: [
|
|
10926
|
+
/* @__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: [
|
|
10927
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-bold", children: title }),
|
|
10928
|
+
/* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
|
|
10929
|
+
events.length,
|
|
10930
|
+
" events"
|
|
10931
|
+
] })
|
|
10932
|
+
] }) }),
|
|
10933
|
+
/* @__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) => {
|
|
10934
|
+
const EventIcon = eventIcons[event.type];
|
|
10935
|
+
const colorClass = eventColors[event.type];
|
|
10936
|
+
return /* @__PURE__ */ jsxs(
|
|
10937
|
+
Box,
|
|
10938
|
+
{
|
|
10939
|
+
display: "flex",
|
|
10940
|
+
padding: "xs",
|
|
10941
|
+
rounded: "sm",
|
|
10942
|
+
className: cn("items-start gap-2 hover:bg-[var(--color-muted)] transition-colors", event.type === "death" && "opacity-60"),
|
|
10943
|
+
children: [
|
|
10944
|
+
/* @__PURE__ */ jsx(Box, { className: cn("flex-shrink-0 mt-0.5", colorClass), children: /* @__PURE__ */ jsx(EventIcon, { className: "h-4 w-4" }) }),
|
|
10945
|
+
/* @__PURE__ */ jsxs(Box, { className: "flex-1 min-w-0", children: [
|
|
10946
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "block", children: event.message }),
|
|
10947
|
+
event.value !== void 0 && /* @__PURE__ */ jsxs(Badge, { variant: eventBadgeVariants[event.type], size: "sm", className: "mt-1", children: [
|
|
10948
|
+
event.type === "heal" ? "+" : event.type === "attack" ? "-" : "",
|
|
10949
|
+
event.value
|
|
10950
|
+
] })
|
|
10951
|
+
] }),
|
|
10952
|
+
(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}` : "" }) })
|
|
10953
|
+
]
|
|
10954
|
+
},
|
|
10955
|
+
event.id
|
|
10956
|
+
);
|
|
10957
|
+
}) }) })
|
|
10958
|
+
] });
|
|
10959
|
+
}
|
|
10960
|
+
CombatLog.displayName = "CombatLog";
|
|
10961
|
+
|
|
10962
|
+
// components/organisms/game/types/game.ts
|
|
10963
|
+
function createInitialGameState(width, height, units, defaultTerrain = "floorStone") {
|
|
10964
|
+
const board = Array.from(
|
|
10965
|
+
{ length: height },
|
|
10966
|
+
() => Array.from({ length: width }, () => ({
|
|
10967
|
+
terrain: defaultTerrain
|
|
10968
|
+
}))
|
|
10969
|
+
);
|
|
10970
|
+
const unitsMap = {};
|
|
10971
|
+
for (const unit of units) {
|
|
10972
|
+
unitsMap[unit.id] = unit;
|
|
10973
|
+
if (unit.position.y < height && unit.position.x < width) {
|
|
10974
|
+
board[unit.position.y][unit.position.x].unitId = unit.id;
|
|
10975
|
+
}
|
|
10976
|
+
}
|
|
10977
|
+
return {
|
|
10978
|
+
board,
|
|
10979
|
+
units: unitsMap,
|
|
10980
|
+
currentPhase: "observation",
|
|
10981
|
+
currentTurn: 1,
|
|
10982
|
+
activeTeam: "player",
|
|
10983
|
+
validMoves: [],
|
|
10984
|
+
attackTargets: []
|
|
10985
|
+
};
|
|
10986
|
+
}
|
|
10987
|
+
function calculateValidMoves(state, unitId) {
|
|
10988
|
+
const unit = state.units[unitId];
|
|
10989
|
+
if (!unit) return [];
|
|
10990
|
+
const moves = [];
|
|
10991
|
+
const { x, y } = unit.position;
|
|
10992
|
+
const range = unit.movement;
|
|
10993
|
+
for (let dy = -range; dy <= range; dy++) {
|
|
10994
|
+
for (let dx = -range; dx <= range; dx++) {
|
|
10995
|
+
const nx = x + dx;
|
|
10996
|
+
const ny = y + dy;
|
|
10997
|
+
const distance = Math.abs(dx) + Math.abs(dy);
|
|
10998
|
+
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) {
|
|
10999
|
+
moves.push({ x: nx, y: ny });
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
return moves;
|
|
11004
|
+
}
|
|
11005
|
+
function calculateAttackTargets(state, unitId) {
|
|
11006
|
+
const unit = state.units[unitId];
|
|
11007
|
+
if (!unit) return [];
|
|
11008
|
+
const targets = [];
|
|
11009
|
+
const { x, y } = unit.position;
|
|
11010
|
+
const directions = [
|
|
11011
|
+
{ dx: -1, dy: 0 },
|
|
11012
|
+
{ dx: 1, dy: 0 },
|
|
11013
|
+
{ dx: 0, dy: -1 },
|
|
11014
|
+
{ dx: 0, dy: 1 }
|
|
11015
|
+
];
|
|
11016
|
+
for (const { dx, dy } of directions) {
|
|
11017
|
+
const nx = x + dx;
|
|
11018
|
+
const ny = y + dy;
|
|
11019
|
+
if (ny >= 0 && ny < state.board.length && nx >= 0 && nx < state.board[0].length) {
|
|
11020
|
+
const targetTile = state.board[ny][nx];
|
|
11021
|
+
if (targetTile.unitId) {
|
|
11022
|
+
const targetUnit = state.units[targetTile.unitId];
|
|
11023
|
+
if (targetUnit && targetUnit.team !== unit.team) {
|
|
11024
|
+
targets.push({ x: nx, y: ny });
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
}
|
|
11029
|
+
return targets;
|
|
11030
|
+
}
|
|
11031
|
+
|
|
11032
|
+
// components/organisms/game/utils/combatEffects.ts
|
|
11033
|
+
var combatAnimations = {
|
|
11034
|
+
shake: `
|
|
11035
|
+
@keyframes combat-shake {
|
|
11036
|
+
0%, 100% { transform: translateX(0); }
|
|
11037
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
|
|
11038
|
+
20%, 40%, 60%, 80% { transform: translateX(5px); }
|
|
11039
|
+
}
|
|
11040
|
+
`,
|
|
11041
|
+
flash: `
|
|
11042
|
+
@keyframes combat-flash {
|
|
11043
|
+
0%, 100% { opacity: 1; }
|
|
11044
|
+
50% { opacity: 0.3; filter: brightness(2); }
|
|
11045
|
+
}
|
|
11046
|
+
`,
|
|
11047
|
+
pulseRed: `
|
|
11048
|
+
@keyframes combat-pulse-red {
|
|
11049
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
|
11050
|
+
50% { box-shadow: 0 0 20px 5px rgba(239, 68, 68, 0.6); }
|
|
11051
|
+
}
|
|
11052
|
+
`,
|
|
11053
|
+
healGlow: `
|
|
11054
|
+
@keyframes combat-heal-glow {
|
|
11055
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
|
|
11056
|
+
50% { box-shadow: 0 0 30px 10px rgba(34, 197, 94, 0.5); }
|
|
11057
|
+
}
|
|
11058
|
+
`
|
|
11059
|
+
};
|
|
11060
|
+
var combatClasses = {
|
|
11061
|
+
shake: "animate-[combat-shake_0.3s_ease-in-out]",
|
|
11062
|
+
flash: "animate-[combat-flash_0.2s_ease-in-out_2]",
|
|
11063
|
+
pulseRed: "animate-[combat-pulse-red_0.5s_ease-in-out]",
|
|
11064
|
+
healGlow: "animate-[combat-heal-glow_0.8s_ease-in-out]",
|
|
11065
|
+
hit: "animate-pulse brightness-150",
|
|
11066
|
+
defend: "animate-bounce",
|
|
11067
|
+
critical: "animate-ping"
|
|
11068
|
+
};
|
|
11069
|
+
var combatEffects = {
|
|
11070
|
+
attack: { className: "animate-pulse brightness-125", duration: 300 },
|
|
11071
|
+
hit: { className: "animate-[shake_0.3s_ease-in-out] brightness-150", duration: 300 },
|
|
11072
|
+
critical: { className: "animate-ping scale-110", duration: 500 },
|
|
11073
|
+
defend: { className: "animate-bounce ring-4 ring-blue-400", duration: 400 },
|
|
11074
|
+
heal: { className: "brightness-125 ring-4 ring-green-400", duration: 600 },
|
|
11075
|
+
defeat: { className: "grayscale opacity-50 scale-75", duration: 800 },
|
|
11076
|
+
levelUp: { className: "animate-bounce ring-4 ring-yellow-400 brightness-150", duration: 1e3 }
|
|
11077
|
+
};
|
|
11078
|
+
function applyTemporaryEffect(element, effect, onComplete) {
|
|
11079
|
+
const originalClass = element.className;
|
|
11080
|
+
element.className = `${originalClass} ${effect.className}`;
|
|
11081
|
+
setTimeout(() => {
|
|
11082
|
+
element.className = originalClass;
|
|
11083
|
+
onComplete?.();
|
|
11084
|
+
}, effect.duration);
|
|
11085
|
+
}
|
|
11086
|
+
function calculateDamage(attack, defense, isDefending = false, criticalChance = 0.1) {
|
|
11087
|
+
const isCritical = Math.random() < criticalChance;
|
|
11088
|
+
const baseDamage = attack;
|
|
11089
|
+
const defenseMultiplier = isDefending ? 2 : 1;
|
|
11090
|
+
const effectiveDefense = defense * defenseMultiplier;
|
|
11091
|
+
let finalDamage = Math.max(1, baseDamage - effectiveDefense);
|
|
11092
|
+
if (isCritical) finalDamage = Math.floor(finalDamage * 2);
|
|
11093
|
+
const isBlocked = finalDamage <= 1 && effectiveDefense > baseDamage;
|
|
11094
|
+
const damageReduction = baseDamage - finalDamage;
|
|
11095
|
+
return { baseDamage, finalDamage, isCritical, isBlocked, damageReduction };
|
|
11096
|
+
}
|
|
11097
|
+
function generateCombatMessage(event) {
|
|
11098
|
+
return event.message;
|
|
11099
|
+
}
|
|
11100
|
+
function extractTitle(children) {
|
|
11101
|
+
if (!React42__default.isValidElement(children)) return void 0;
|
|
11102
|
+
const props = children.props;
|
|
11103
|
+
if (typeof props.title === "string") {
|
|
11104
|
+
return props.title;
|
|
11105
|
+
}
|
|
11106
|
+
return void 0;
|
|
11107
|
+
}
|
|
11108
|
+
var ModalSlot = ({
|
|
11109
|
+
children,
|
|
11110
|
+
title: overrideTitle,
|
|
11111
|
+
size = "md",
|
|
11112
|
+
className
|
|
11113
|
+
}) => {
|
|
11114
|
+
const eventBus = useEventBus();
|
|
11115
|
+
const isOpen = Boolean(children);
|
|
11116
|
+
const title = overrideTitle || extractTitle(children);
|
|
11117
|
+
const handleClose = () => {
|
|
11118
|
+
eventBus.emit("UI:CLOSE");
|
|
11119
|
+
eventBus.emit("UI:CANCEL");
|
|
11120
|
+
};
|
|
11121
|
+
if (!isOpen) return null;
|
|
11122
|
+
return /* @__PURE__ */ jsx(
|
|
11123
|
+
Modal,
|
|
11124
|
+
{
|
|
11125
|
+
isOpen,
|
|
11126
|
+
onClose: handleClose,
|
|
11127
|
+
title,
|
|
11128
|
+
size,
|
|
11129
|
+
className,
|
|
11130
|
+
children
|
|
11131
|
+
}
|
|
11132
|
+
);
|
|
11133
|
+
};
|
|
11134
|
+
ModalSlot.displayName = "ModalSlot";
|
|
11135
|
+
function extractTitle2(children) {
|
|
11136
|
+
if (!React42__default.isValidElement(children)) return void 0;
|
|
11137
|
+
const props = children.props;
|
|
11138
|
+
if (typeof props.title === "string") {
|
|
11139
|
+
return props.title;
|
|
11140
|
+
}
|
|
11141
|
+
return void 0;
|
|
11142
|
+
}
|
|
11143
|
+
var DrawerSlot = ({
|
|
11144
|
+
children,
|
|
11145
|
+
title: overrideTitle,
|
|
11146
|
+
position = "right",
|
|
11147
|
+
size = "md",
|
|
11148
|
+
className
|
|
11149
|
+
}) => {
|
|
11150
|
+
const eventBus = useEventBus();
|
|
11151
|
+
const isOpen = Boolean(children);
|
|
11152
|
+
const title = overrideTitle || extractTitle2(children);
|
|
11153
|
+
const handleClose = () => {
|
|
11154
|
+
eventBus.emit("UI:CLOSE");
|
|
11155
|
+
eventBus.emit("UI:CANCEL");
|
|
11156
|
+
};
|
|
11157
|
+
if (!isOpen) return null;
|
|
11158
|
+
return /* @__PURE__ */ jsx(
|
|
11159
|
+
Drawer,
|
|
11160
|
+
{
|
|
11161
|
+
isOpen,
|
|
11162
|
+
onClose: handleClose,
|
|
11163
|
+
title,
|
|
11164
|
+
position,
|
|
11165
|
+
width: size,
|
|
11166
|
+
className,
|
|
11167
|
+
children
|
|
11168
|
+
}
|
|
11169
|
+
);
|
|
11170
|
+
};
|
|
11171
|
+
DrawerSlot.displayName = "DrawerSlot";
|
|
11172
|
+
function extractToastProps(children) {
|
|
11173
|
+
if (!React42__default.isValidElement(children)) {
|
|
11174
|
+
if (typeof children === "string") {
|
|
11175
|
+
return { message: children };
|
|
11176
|
+
}
|
|
11177
|
+
return {};
|
|
11178
|
+
}
|
|
11179
|
+
const props = children.props;
|
|
11180
|
+
return {
|
|
11181
|
+
message: typeof props.message === "string" ? props.message : void 0,
|
|
11182
|
+
variant: props.variant,
|
|
11183
|
+
title: typeof props.title === "string" ? props.title : void 0
|
|
11184
|
+
};
|
|
11185
|
+
}
|
|
11186
|
+
var ToastSlot = ({
|
|
11187
|
+
children,
|
|
11188
|
+
variant: overrideVariant,
|
|
11189
|
+
title: overrideTitle,
|
|
11190
|
+
duration = 5e3,
|
|
11191
|
+
className
|
|
11192
|
+
}) => {
|
|
11193
|
+
const eventBus = useEventBus();
|
|
11194
|
+
const isVisible = Boolean(children);
|
|
11195
|
+
const extracted = extractToastProps(children);
|
|
11196
|
+
const variant = overrideVariant || extracted.variant || "info";
|
|
11197
|
+
const title = overrideTitle || extracted.title;
|
|
11198
|
+
const message = extracted.message || (typeof children === "string" ? children : "");
|
|
11199
|
+
const handleDismiss = () => {
|
|
11200
|
+
eventBus.emit("UI:DISMISS");
|
|
11201
|
+
eventBus.emit("UI:CLOSE");
|
|
11202
|
+
};
|
|
11203
|
+
if (!isVisible) return null;
|
|
11204
|
+
const isCustomContent = React42__default.isValidElement(children) && !message;
|
|
11205
|
+
return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
|
|
11206
|
+
Toast,
|
|
11207
|
+
{
|
|
11208
|
+
variant,
|
|
11209
|
+
title,
|
|
11210
|
+
message: message || "Notification",
|
|
11211
|
+
duration,
|
|
11212
|
+
onDismiss: handleDismiss,
|
|
11213
|
+
className
|
|
11214
|
+
}
|
|
11215
|
+
) });
|
|
11216
|
+
};
|
|
11217
|
+
ToastSlot.displayName = "ToastSlot";
|
|
11218
|
+
var CHART_COLORS = [
|
|
11219
|
+
"var(--color-primary)",
|
|
11220
|
+
"var(--color-success)",
|
|
11221
|
+
"var(--color-warning)",
|
|
8829
11222
|
"var(--color-error)",
|
|
8830
11223
|
"var(--color-info)",
|
|
8831
11224
|
"var(--color-accent)"
|
|
@@ -9279,7 +11672,7 @@ var Meter = ({
|
|
|
9279
11672
|
] }) });
|
|
9280
11673
|
};
|
|
9281
11674
|
Meter.displayName = "Meter";
|
|
9282
|
-
var
|
|
11675
|
+
var STATUS_STYLES3 = {
|
|
9283
11676
|
complete: {
|
|
9284
11677
|
dotColor: "text-[var(--color-success)]",
|
|
9285
11678
|
lineColor: "bg-[var(--color-success)]",
|
|
@@ -9321,7 +11714,7 @@ var Timeline = ({
|
|
|
9321
11714
|
},
|
|
9322
11715
|
[eventBus, entity]
|
|
9323
11716
|
);
|
|
9324
|
-
const items =
|
|
11717
|
+
const items = React42__default.useMemo(() => {
|
|
9325
11718
|
if (propItems) return propItems;
|
|
9326
11719
|
if (!data) return [];
|
|
9327
11720
|
return data.map((record, idx) => {
|
|
@@ -9369,7 +11762,7 @@ var Timeline = ({
|
|
|
9369
11762
|
title && /* @__PURE__ */ jsx(Typography, { variant: "h5", weight: "semibold", children: title }),
|
|
9370
11763
|
/* @__PURE__ */ jsx(VStack, { gap: "none", className: "relative", children: items.map((item, idx) => {
|
|
9371
11764
|
const status = item.status || "pending";
|
|
9372
|
-
const style =
|
|
11765
|
+
const style = STATUS_STYLES3[status];
|
|
9373
11766
|
const ItemIcon = item.icon || style.icon;
|
|
9374
11767
|
const isLast = idx === items.length - 1;
|
|
9375
11768
|
return /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "relative", children: [
|
|
@@ -9470,7 +11863,7 @@ var MediaGallery = ({
|
|
|
9470
11863
|
const handleUpload = useCallback(() => {
|
|
9471
11864
|
eventBus.emit("UI:MEDIA_UPLOAD", { entity });
|
|
9472
11865
|
}, [eventBus, entity]);
|
|
9473
|
-
const items =
|
|
11866
|
+
const items = React42__default.useMemo(() => {
|
|
9474
11867
|
if (propItems) return propItems;
|
|
9475
11868
|
if (!data) return [];
|
|
9476
11869
|
return data.map((record, idx) => ({
|
|
@@ -10804,7 +13197,7 @@ var NavLink = ({
|
|
|
10804
13197
|
currentPath
|
|
10805
13198
|
}) => {
|
|
10806
13199
|
const isActive = currentPath === item.href || currentPath.startsWith(item.href + "/");
|
|
10807
|
-
const
|
|
13200
|
+
const Icon3 = item.icon;
|
|
10808
13201
|
return /* @__PURE__ */ jsxs(
|
|
10809
13202
|
Link,
|
|
10810
13203
|
{
|
|
@@ -10815,7 +13208,7 @@ var NavLink = ({
|
|
|
10815
13208
|
),
|
|
10816
13209
|
children: [
|
|
10817
13210
|
/* @__PURE__ */ jsx(
|
|
10818
|
-
|
|
13211
|
+
Icon3,
|
|
10819
13212
|
{
|
|
10820
13213
|
className: cn(
|
|
10821
13214
|
"h-5 w-5",
|
|
@@ -11446,4 +13839,4 @@ function WorldMapTemplate({
|
|
|
11446
13839
|
}
|
|
11447
13840
|
WorldMapTemplate.displayName = "WorldMapTemplate";
|
|
11448
13841
|
|
|
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 };
|
|
13842
|
+
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 };
|