@almadar/ui 2.6.0 → 2.8.1

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.
@@ -2,23 +2,22 @@ import { useAuthContext } from '../chunk-2QM732NQ.js';
2
2
  export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePinchZoom, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-2QM732NQ.js';
3
3
  import { DEFAULT_CONFIG, renderStateMachineToDomData, parseContentSegments } from '../chunk-N6DJVKZ6.js';
4
4
  import '../chunk-3HJHHULT.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, StatBadge, Select, Drawer, Toast, Tabs, Input, ThemeToggle, EntityDisplayEvents, StateIndicator, Container } from '../chunk-LEWQP2UP.js';
6
- export { Accordion, ActionButtons, Card2 as ActionCard, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, Center, ChartLegend, Checkbox, CodeBlock, ConditionalWrapper, ConfettiEffect, Container, ControlButton, DPad, DataGrid, DataList, DataTable, DateRangeSelector, DayCell, DetailPanel, Divider, Drawer, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FilterGroup, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormField, FormSectionHeader, GraphView, Grid, HStack, Heading, HealthBar, Icon, InfiniteScrollSentinel, Input, InputGroup, Label, LawReferenceTooltip, Lightbox, LineChart, LoadingState, MapView, MarkdownContent, MasterDetail, Menu, Modal, NumberStepper, Overlay, PageHeader, Pagination, Popover, ProgressBar, ProgressDots, PullToRefresh, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ScaledDiagram, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, SortableList, Spacer, Spinner, Sprite, Stack, StarRating, StatBadge, StatCard, StateIndicator, StatusDot, SwipeableRow, Switch, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Toast, Tooltip, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, ViolationAlert, WizardNavigation, WizardProgress, drawSprite } from '../chunk-LEWQP2UP.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, isoToScreen, IsometricCanvas_default, Stack, Select, Drawer, Toast, Tabs, Input, ThemeToggle, TILE_WIDTH, EntityDisplayEvents, StateIndicator, Container } from '../chunk-NES4SBB7.js';
6
+ export { Accordion, ActionButton, ActionButtons, Card2 as ActionCard, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, Center, ChartLegend, Checkbox, ChoiceButton, CodeBlock, CombatLog, ComboCounter, ConditionalWrapper, ConfettiEffect, Container, ControlButton, CraftingRecipe, DIAMOND_TOP_Y, DPad, DamageNumber, DataGrid, DataList, DataTable, DateRangeSelector, DayCell, DetailPanel, DialogueBox, DialogueBubble, Divider, Drawer, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FLOOR_HEIGHT, FilterGroup, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormField, FormSectionHeader, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GraphView, Grid, HStack, Heading, HealthBar, HealthPanel, Icon, InfiniteScrollSentinel, Input, InputGroup, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, Label, LawReferenceTooltip, Lightbox, LineChart, LoadingState, MapView, MarkdownContent, MasterDetail, Menu, Meter, MiniMap, Modal, NumberStepper, Overlay, PageHeader, Pagination, PlatformerCanvas, Popover, PowerupSlots, ProgressBar, ProgressDots, PullToRefresh, QuestTracker, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ResourceBar, ResourceCounter, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, SortableList, Spacer, Spinner, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateIndicator, StatusDot, StatusEffect, SwipeableRow, Switch, TILE_HEIGHT, TILE_WIDTH, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, TimerDisplay, Toast, Tooltip, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UnitCommandBar, UploadDropZone, VStack, ViolationAlert, WaypointMarker, WizardNavigation, WizardProgress, XPBar, drawSprite, isoToScreen, screenToIso, useCamera, useImageCache } from '../chunk-NES4SBB7.js';
7
7
  import '../chunk-DKQN5FVU.js';
8
8
  import { useTranslate } from '../chunk-WGJIL4YR.js';
9
9
  export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useDragReorder, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useInfiniteScroll, useLongPress, usePullToRefresh, useQuerySingleton, useSwipeGesture, useTranslate } from '../chunk-WGJIL4YR.js';
10
10
  import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
11
11
  export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
12
12
  export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-3JGAROCW.js';
13
- import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
14
- export { cn } from '../chunk-KKCVDUK7.js';
13
+ import { cn, getNestedValue, bindCanvasCapture } from '../chunk-A5J5CNCU.js';
14
+ export { cn } from '../chunk-A5J5CNCU.js';
15
15
  import '../chunk-TSETXL2E.js';
16
16
  import '../chunk-K2D5D3WK.js';
17
17
  export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
18
18
  import { __publicField } from '../chunk-PKBMQBKP.js';
19
- import * as React from 'react';
20
- import React__default, { createContext, useState, useCallback, useMemo, useEffect, useRef, useContext } from 'react';
21
- 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';
19
+ import React, { createContext, useState, useCallback, useMemo, useEffect, useRef, useContext } from 'react';
20
+ 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, 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';
22
21
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
23
22
  import { createPortal } from 'react-dom';
24
23
  import { useLocation, Link, Outlet } from 'react-router-dom';
@@ -33,7 +32,7 @@ var FormSection = ({
33
32
  columns = 1,
34
33
  className
35
34
  }) => {
36
- const [collapsed, setCollapsed] = React__default.useState(defaultCollapsed);
35
+ const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
37
36
  const { t } = useTranslate();
38
37
  const eventBus = useEventBus();
39
38
  const gridClass = {
@@ -41,7 +40,7 @@ var FormSection = ({
41
40
  2: "grid-cols-1 md:grid-cols-2",
42
41
  3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
43
42
  }[columns];
44
- React__default.useCallback(() => {
43
+ React.useCallback(() => {
45
44
  if (collapsible) {
46
45
  setCollapsed((prev) => !prev);
47
46
  eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
@@ -1013,7 +1012,7 @@ var List = ({
1013
1012
  if (entity && typeof entity === "object" && "id" in entity) return [entity];
1014
1013
  return [];
1015
1014
  }, [entity]);
1016
- const getItemActions = React__default.useCallback(
1015
+ const getItemActions = React.useCallback(
1017
1016
  (item) => {
1018
1017
  if (!itemActions) return [];
1019
1018
  if (typeof itemActions === "function") {
@@ -1518,7 +1517,7 @@ var WizardContainer = ({
1518
1517
  const isCompleted = index < currentStep;
1519
1518
  const stepKey = step.id ?? step.tabId ?? `step-${index}`;
1520
1519
  const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
1521
- return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
1520
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1522
1521
  /* @__PURE__ */ jsx(
1523
1522
  Button,
1524
1523
  {
@@ -2742,7 +2741,7 @@ var StateMachineView = ({
2742
2741
  style: { top: title ? 30 : 0 },
2743
2742
  children: [
2744
2743
  entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
2745
- states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React__default.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
2744
+ states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
2746
2745
  StateNode,
2747
2746
  {
2748
2747
  state,
@@ -3746,1048 +3745,6 @@ var TabbedContainer = ({
3746
3745
  );
3747
3746
  };
3748
3747
  TabbedContainer.displayName = "TabbedContainer";
3749
- function useImageCache(urls) {
3750
- const cacheRef = useRef(/* @__PURE__ */ new Map());
3751
- const loadingRef = useRef(/* @__PURE__ */ new Set());
3752
- const [pendingCount, setPendingCount] = useState(0);
3753
- useEffect(() => {
3754
- const cache = cacheRef.current;
3755
- const loading = loadingRef.current;
3756
- let cancelled = false;
3757
- const newUrls = urls.filter((url) => url && !cache.has(url) && !loading.has(url));
3758
- if (newUrls.length === 0) return;
3759
- setPendingCount((prev) => prev + newUrls.length);
3760
- for (const url of newUrls) {
3761
- loading.add(url);
3762
- const img = new Image();
3763
- img.crossOrigin = "anonymous";
3764
- img.onload = () => {
3765
- if (cancelled) return;
3766
- cache.set(url, img);
3767
- loading.delete(url);
3768
- setPendingCount((prev) => Math.max(0, prev - 1));
3769
- };
3770
- img.onerror = () => {
3771
- if (cancelled) return;
3772
- loading.delete(url);
3773
- setPendingCount((prev) => Math.max(0, prev - 1));
3774
- };
3775
- img.src = url;
3776
- }
3777
- return () => {
3778
- cancelled = true;
3779
- };
3780
- }, [urls.join(",")]);
3781
- const getImage = useCallback((url) => {
3782
- return cacheRef.current.get(url);
3783
- }, []);
3784
- return {
3785
- getImage,
3786
- isLoaded: pendingCount === 0,
3787
- pendingCount
3788
- };
3789
- }
3790
- function useCamera() {
3791
- const cameraRef = useRef({ x: 0, y: 0, zoom: 1 });
3792
- const targetCameraRef = useRef(null);
3793
- const isDraggingRef = useRef(false);
3794
- const dragDistanceRef = useRef(0);
3795
- const lastMouseRef = useRef({ x: 0, y: 0 });
3796
- const handleMouseDown = useCallback((e) => {
3797
- isDraggingRef.current = true;
3798
- dragDistanceRef.current = 0;
3799
- lastMouseRef.current = { x: e.clientX, y: e.clientY };
3800
- if (e.button === 1 || e.button === 2) {
3801
- e.preventDefault();
3802
- }
3803
- }, []);
3804
- const handleMouseUp = useCallback(() => {
3805
- isDraggingRef.current = false;
3806
- }, []);
3807
- const handleMouseMove = useCallback((e, drawFn) => {
3808
- if (!isDraggingRef.current) return false;
3809
- const dx = e.clientX - lastMouseRef.current.x;
3810
- const dy = e.clientY - lastMouseRef.current.y;
3811
- dragDistanceRef.current += Math.abs(dx) + Math.abs(dy);
3812
- cameraRef.current.x -= dx;
3813
- cameraRef.current.y -= dy;
3814
- lastMouseRef.current = { x: e.clientX, y: e.clientY };
3815
- targetCameraRef.current = null;
3816
- drawFn?.();
3817
- return dragDistanceRef.current > 5;
3818
- }, []);
3819
- const handleMouseLeave = useCallback(() => {
3820
- isDraggingRef.current = false;
3821
- }, []);
3822
- const handleWheel = useCallback((e, drawFn) => {
3823
- e.preventDefault();
3824
- const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
3825
- cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
3826
- drawFn?.();
3827
- }, []);
3828
- const screenToWorld = useCallback((clientX, clientY, canvas, viewportSize) => {
3829
- const rect = canvas.getBoundingClientRect();
3830
- const screenX = clientX - rect.left;
3831
- const screenY = clientY - rect.top;
3832
- const cam = cameraRef.current;
3833
- const worldX = (screenX - viewportSize.width / 2) / cam.zoom + viewportSize.width / 2 + cam.x;
3834
- const worldY = (screenY - viewportSize.height / 2) / cam.zoom + viewportSize.height / 2 + cam.y;
3835
- return { x: worldX, y: worldY };
3836
- }, []);
3837
- const lerpToTarget = useCallback((t = 0.08) => {
3838
- if (!targetCameraRef.current) return false;
3839
- const cam = cameraRef.current;
3840
- cam.x += (targetCameraRef.current.x - cam.x) * t;
3841
- cam.y += (targetCameraRef.current.y - cam.y) * t;
3842
- if (Math.abs(cam.x - targetCameraRef.current.x) < 0.5 && Math.abs(cam.y - targetCameraRef.current.y) < 0.5) {
3843
- cam.x = targetCameraRef.current.x;
3844
- cam.y = targetCameraRef.current.y;
3845
- targetCameraRef.current = null;
3846
- }
3847
- return true;
3848
- }, []);
3849
- return {
3850
- cameraRef,
3851
- targetCameraRef,
3852
- isDragging: () => isDraggingRef.current,
3853
- dragDistance: () => dragDistanceRef.current,
3854
- handleMouseDown,
3855
- handleMouseUp,
3856
- handleMouseMove,
3857
- handleMouseLeave,
3858
- handleWheel,
3859
- screenToWorld,
3860
- lerpToTarget
3861
- };
3862
- }
3863
-
3864
- // components/organisms/game/utils/isometric.ts
3865
- var TILE_WIDTH = 256;
3866
- var TILE_HEIGHT = 512;
3867
- var FLOOR_HEIGHT = 128;
3868
- var DIAMOND_TOP_Y = 374;
3869
- var FEATURE_COLORS = {
3870
- goldMine: "#fbbf24",
3871
- resonanceCrystal: "#a78bfa",
3872
- traitCache: "#60a5fa",
3873
- salvageYard: "#6b7280",
3874
- portal: "#c084fc",
3875
- castle: "#f59e0b",
3876
- mountain: "#78716c",
3877
- default: "#9ca3af"
3878
- };
3879
- function isoToScreen(tileX, tileY, scale, baseOffsetX) {
3880
- const scaledTileWidth = TILE_WIDTH * scale;
3881
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
3882
- const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
3883
- const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
3884
- return { x: screenX, y: screenY };
3885
- }
3886
- function screenToIso(screenX, screenY, scale, baseOffsetX) {
3887
- const scaledTileWidth = TILE_WIDTH * scale;
3888
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
3889
- const adjustedX = screenX - baseOffsetX;
3890
- const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
3891
- const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
3892
- return { x: Math.round(tileX), y: Math.round(tileY) };
3893
- }
3894
- function IsometricCanvas({
3895
- // Closed-circuit
3896
- className,
3897
- isLoading = false,
3898
- error = null,
3899
- entity,
3900
- // Grid data
3901
- tiles: tilesProp = [],
3902
- units = [],
3903
- features = [],
3904
- // Interaction state
3905
- selectedUnitId = null,
3906
- validMoves = [],
3907
- attackTargets = [],
3908
- hoveredTile = null,
3909
- // Event handlers
3910
- onTileClick,
3911
- onUnitClick,
3912
- onTileHover,
3913
- onTileLeave,
3914
- // Declarative event props
3915
- tileClickEvent,
3916
- unitClickEvent,
3917
- tileHoverEvent,
3918
- tileLeaveEvent,
3919
- // Rendering options
3920
- scale = 0.4,
3921
- debug = false,
3922
- backgroundImage,
3923
- showMinimap = true,
3924
- enableCamera = true,
3925
- unitScale = 1,
3926
- // Asset resolution
3927
- getTerrainSprite,
3928
- getFeatureSprite,
3929
- getUnitSprite,
3930
- resolveUnitFrame,
3931
- effectSpriteUrls = [],
3932
- onDrawEffects,
3933
- hasActiveEffects: hasActiveEffects2 = false,
3934
- // Tuning
3935
- diamondTopY: diamondTopYProp,
3936
- // Remote asset loading
3937
- assetBaseUrl,
3938
- assetManifest
3939
- }) {
3940
- const eventBus = useEventBus();
3941
- const { t } = useTranslate();
3942
- const canvasRef = useRef(null);
3943
- const containerRef = useRef(null);
3944
- const minimapRef = useRef(null);
3945
- const animTimeRef = useRef(0);
3946
- const rafIdRef = useRef(0);
3947
- const [viewportSize, setViewportSize] = useState({ width: 800, height: 600 });
3948
- useEffect(() => {
3949
- const el = containerRef.current;
3950
- if (!el) return;
3951
- if (typeof ResizeObserver === "undefined") return;
3952
- const observer = new ResizeObserver((entries) => {
3953
- const entry = entries[0];
3954
- if (entry) {
3955
- setViewportSize({
3956
- width: entry.contentRect.width || 800,
3957
- height: entry.contentRect.height || 600
3958
- });
3959
- }
3960
- });
3961
- observer.observe(el);
3962
- return () => observer.disconnect();
3963
- }, []);
3964
- const sortedTiles = useMemo(() => {
3965
- const tiles = [...tilesProp];
3966
- tiles.sort((a, b) => {
3967
- const depthA = a.x + a.y;
3968
- const depthB = b.x + b.y;
3969
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
3970
- });
3971
- return tiles;
3972
- }, [tilesProp]);
3973
- const gridWidth = useMemo(() => {
3974
- if (sortedTiles.length === 0) return 0;
3975
- return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
3976
- }, [sortedTiles]);
3977
- const gridHeight = useMemo(() => {
3978
- if (sortedTiles.length === 0) return 0;
3979
- return Math.max(...sortedTiles.map((t2) => t2.y)) + 1;
3980
- }, [sortedTiles]);
3981
- const scaledTileWidth = TILE_WIDTH * scale;
3982
- const scaledTileHeight = TILE_HEIGHT * scale;
3983
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
3984
- const effectiveDiamondTopY = diamondTopYProp ?? DIAMOND_TOP_Y;
3985
- const scaledDiamondTopY = effectiveDiamondTopY * scale;
3986
- const baseOffsetX = useMemo(() => {
3987
- return (gridHeight - 1) * (scaledTileWidth / 2);
3988
- }, [gridHeight, scaledTileWidth]);
3989
- const validMoveSet = useMemo(() => {
3990
- return new Set(validMoves.map((p2) => `${p2.x},${p2.y}`));
3991
- }, [validMoves]);
3992
- const attackTargetSet = useMemo(() => {
3993
- return new Set(attackTargets.map((p2) => `${p2.x},${p2.y}`));
3994
- }, [attackTargets]);
3995
- const resolveManifestUrl = useCallback((relativePath) => {
3996
- if (!relativePath) return void 0;
3997
- if (assetBaseUrl) return `${assetBaseUrl.replace(/\/$/, "")}${relativePath.startsWith("/") ? "" : "/"}${relativePath}`;
3998
- return relativePath;
3999
- }, [assetBaseUrl]);
4000
- const spriteUrls = useMemo(() => {
4001
- const urls = [];
4002
- for (const tile of sortedTiles) {
4003
- if (tile.terrainSprite) urls.push(tile.terrainSprite);
4004
- else if (getTerrainSprite) {
4005
- const url = getTerrainSprite(tile.terrain ?? "");
4006
- if (url) urls.push(url);
4007
- } else {
4008
- const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
4009
- if (url) urls.push(url);
4010
- }
4011
- }
4012
- for (const feature of features) {
4013
- if (feature.sprite) urls.push(feature.sprite);
4014
- else if (getFeatureSprite) {
4015
- const url = getFeatureSprite(feature.type);
4016
- if (url) urls.push(url);
4017
- } else {
4018
- const url = resolveManifestUrl(assetManifest?.features?.[feature.type]);
4019
- if (url) urls.push(url);
4020
- }
4021
- }
4022
- for (const unit of units) {
4023
- if (unit.sprite) urls.push(unit.sprite);
4024
- else if (getUnitSprite) {
4025
- const url = getUnitSprite(unit);
4026
- if (url) urls.push(url);
4027
- } else if (unit.unitType) {
4028
- const url = resolveManifestUrl(assetManifest?.units?.[unit.unitType]);
4029
- if (url) urls.push(url);
4030
- }
4031
- }
4032
- if (assetManifest?.effects) {
4033
- for (const path of Object.values(assetManifest.effects)) {
4034
- const url = resolveManifestUrl(path);
4035
- if (url) urls.push(url);
4036
- }
4037
- }
4038
- urls.push(...effectSpriteUrls);
4039
- if (backgroundImage) urls.push(backgroundImage);
4040
- return [...new Set(urls.filter(Boolean))];
4041
- }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
4042
- const { getImage } = useImageCache(spriteUrls);
4043
- const {
4044
- cameraRef,
4045
- targetCameraRef,
4046
- isDragging,
4047
- dragDistance,
4048
- handleMouseDown,
4049
- handleMouseUp,
4050
- handleMouseMove,
4051
- handleMouseLeave,
4052
- handleWheel,
4053
- screenToWorld,
4054
- lerpToTarget
4055
- } = useCamera();
4056
- const resolveTerrainSpriteUrl = useCallback((tile) => {
4057
- return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
4058
- }, [getTerrainSprite, assetManifest, resolveManifestUrl]);
4059
- const resolveFeatureSpriteUrl = useCallback((featureType) => {
4060
- return getFeatureSprite?.(featureType) || resolveManifestUrl(assetManifest?.features?.[featureType]);
4061
- }, [getFeatureSprite, assetManifest, resolveManifestUrl]);
4062
- const resolveUnitSpriteUrl = useCallback((unit) => {
4063
- return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl(assetManifest?.units?.[unit.unitType]) : void 0);
4064
- }, [getUnitSprite, assetManifest, resolveManifestUrl]);
4065
- const drawMinimap = useCallback(() => {
4066
- if (!showMinimap) return;
4067
- const miniCanvas = minimapRef.current;
4068
- if (!miniCanvas || sortedTiles.length === 0) return;
4069
- const mCtx = miniCanvas.getContext("2d");
4070
- if (!mCtx) return;
4071
- const mW = 150;
4072
- const mH = 100;
4073
- miniCanvas.width = mW;
4074
- miniCanvas.height = mH;
4075
- mCtx.clearRect(0, 0, mW, mH);
4076
- const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
4077
- const minX = Math.min(...allScreenPos.map((p2) => p2.x));
4078
- const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
4079
- const minY = Math.min(...allScreenPos.map((p2) => p2.y));
4080
- const maxY = Math.max(...allScreenPos.map((p2) => p2.y + scaledTileHeight));
4081
- const worldW = maxX - minX;
4082
- const worldH = maxY - minY;
4083
- const scaleM = Math.min(mW / worldW, mH / worldH) * 0.9;
4084
- const offsetMx = (mW - worldW * scaleM) / 2;
4085
- const offsetMy = (mH - worldH * scaleM) / 2;
4086
- for (const tile of sortedTiles) {
4087
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
4088
- const mx = (pos.x - minX) * scaleM + offsetMx;
4089
- const my = (pos.y - minY) * scaleM + offsetMy;
4090
- const mTileW = scaledTileWidth * scaleM;
4091
- const mFloorH = scaledFloorHeight * scaleM;
4092
- mCtx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : "#4ade80";
4093
- mCtx.beginPath();
4094
- mCtx.moveTo(mx + mTileW / 2, my);
4095
- mCtx.lineTo(mx + mTileW, my + mFloorH / 2);
4096
- mCtx.lineTo(mx + mTileW / 2, my + mFloorH);
4097
- mCtx.lineTo(mx, my + mFloorH / 2);
4098
- mCtx.closePath();
4099
- mCtx.fill();
4100
- }
4101
- for (const unit of units) {
4102
- if (!unit.position) continue;
4103
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
4104
- const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
4105
- const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
4106
- mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
4107
- mCtx.beginPath();
4108
- mCtx.arc(mx, my, 3, 0, Math.PI * 2);
4109
- mCtx.fill();
4110
- }
4111
- const cam = cameraRef.current;
4112
- const vLeft = (cam.x - minX) * scaleM + offsetMx;
4113
- const vTop = (cam.y - minY) * scaleM + offsetMy;
4114
- const vW = viewportSize.width / cam.zoom * scaleM;
4115
- const vH = viewportSize.height / cam.zoom * scaleM;
4116
- mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
4117
- mCtx.lineWidth = 1;
4118
- mCtx.strokeRect(vLeft, vTop, vW, vH);
4119
- }, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
4120
- const draw = useCallback((animTime) => {
4121
- const canvas = canvasRef.current;
4122
- if (!canvas) return;
4123
- const ctx = canvas.getContext("2d");
4124
- if (!ctx) return;
4125
- const dpr = window.devicePixelRatio || 1;
4126
- canvas.width = viewportSize.width * dpr;
4127
- canvas.height = viewportSize.height * dpr;
4128
- ctx.scale(dpr, dpr);
4129
- ctx.clearRect(0, 0, viewportSize.width, viewportSize.height);
4130
- if (backgroundImage) {
4131
- const bgImg = getImage(backgroundImage);
4132
- if (bgImg) {
4133
- const cam2 = cameraRef.current;
4134
- const patW = bgImg.naturalWidth;
4135
- const patH = bgImg.naturalHeight;
4136
- const startX = -(cam2.x % patW + patW) % patW;
4137
- const startY = -(cam2.y % patH + patH) % patH;
4138
- for (let y = startY - patH; y < viewportSize.height; y += patH) {
4139
- for (let x = startX - patW; x < viewportSize.width; x += patW) {
4140
- ctx.drawImage(bgImg, x, y);
4141
- }
4142
- }
4143
- }
4144
- } else {
4145
- ctx.fillStyle = "#1a1a2e";
4146
- ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
4147
- }
4148
- if (sortedTiles.length === 0) return;
4149
- ctx.save();
4150
- const cam = cameraRef.current;
4151
- ctx.translate(viewportSize.width / 2, viewportSize.height / 2);
4152
- ctx.scale(cam.zoom, cam.zoom);
4153
- ctx.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
4154
- const visLeft = cam.x - viewportSize.width / cam.zoom;
4155
- const visRight = cam.x + viewportSize.width * 2 / cam.zoom;
4156
- const visTop = cam.y - viewportSize.height / cam.zoom;
4157
- const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
4158
- for (const tile of sortedTiles) {
4159
- const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
4160
- if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
4161
- continue;
4162
- }
4163
- const spriteUrl = resolveTerrainSpriteUrl(tile);
4164
- const img = spriteUrl ? getImage(spriteUrl) : null;
4165
- if (img) {
4166
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
4167
- } else {
4168
- const centerX = pos.x + scaledTileWidth / 2;
4169
- const topY = pos.y + scaledDiamondTopY;
4170
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
4171
- ctx.beginPath();
4172
- ctx.moveTo(centerX, topY);
4173
- ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
4174
- ctx.lineTo(centerX, topY + scaledFloorHeight);
4175
- ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
4176
- ctx.closePath();
4177
- ctx.fill();
4178
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
4179
- ctx.lineWidth = 1;
4180
- ctx.stroke();
4181
- }
4182
- if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
4183
- const centerX = pos.x + scaledTileWidth / 2;
4184
- const topY = pos.y + scaledDiamondTopY;
4185
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
4186
- ctx.beginPath();
4187
- ctx.moveTo(centerX, topY);
4188
- ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
4189
- ctx.lineTo(centerX, topY + scaledFloorHeight);
4190
- ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
4191
- ctx.closePath();
4192
- ctx.fill();
4193
- }
4194
- const tileKey = `${tile.x},${tile.y}`;
4195
- if (validMoveSet.has(tileKey)) {
4196
- const centerX = pos.x + scaledTileWidth / 2;
4197
- const topY = pos.y + scaledDiamondTopY;
4198
- const pulse = 0.15 + 0.1 * Math.sin(animTime * 4e-3);
4199
- ctx.fillStyle = `rgba(74, 222, 128, ${pulse})`;
4200
- ctx.beginPath();
4201
- ctx.moveTo(centerX, topY);
4202
- ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
4203
- ctx.lineTo(centerX, topY + scaledFloorHeight);
4204
- ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
4205
- ctx.closePath();
4206
- ctx.fill();
4207
- }
4208
- if (attackTargetSet.has(tileKey)) {
4209
- const centerX = pos.x + scaledTileWidth / 2;
4210
- const topY = pos.y + scaledDiamondTopY;
4211
- const pulse = 0.2 + 0.15 * Math.sin(animTime * 5e-3);
4212
- ctx.fillStyle = `rgba(239, 68, 68, ${pulse})`;
4213
- ctx.beginPath();
4214
- ctx.moveTo(centerX, topY);
4215
- ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
4216
- ctx.lineTo(centerX, topY + scaledFloorHeight);
4217
- ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
4218
- ctx.closePath();
4219
- ctx.fill();
4220
- }
4221
- if (debug) {
4222
- const centerX = pos.x + scaledTileWidth / 2;
4223
- const centerY = pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
4224
- ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
4225
- ctx.font = `${12 * scale * 2}px monospace`;
4226
- ctx.textAlign = "center";
4227
- ctx.fillText(`${tile.x},${tile.y}`, centerX, centerY + 4);
4228
- }
4229
- }
4230
- const sortedFeatures = [...features].sort((a, b) => {
4231
- const depthA = a.x + a.y;
4232
- const depthB = b.x + b.y;
4233
- return depthA !== depthB ? depthA - depthB : a.y - b.y;
4234
- });
4235
- for (const feature of sortedFeatures) {
4236
- const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
4237
- if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
4238
- continue;
4239
- }
4240
- const spriteUrl = feature.sprite || resolveFeatureSpriteUrl(feature.type);
4241
- const img = spriteUrl ? getImage(spriteUrl) : null;
4242
- const centerX = pos.x + scaledTileWidth / 2;
4243
- const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
4244
- const isCastle = feature.type === "castle";
4245
- const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
4246
- const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
4247
- if (img) {
4248
- const ar = img.naturalWidth / img.naturalHeight;
4249
- let drawH = featureDrawH;
4250
- let drawW = featureDrawH * ar;
4251
- if (drawW > maxFeatureW) {
4252
- drawW = maxFeatureW;
4253
- drawH = maxFeatureW / ar;
4254
- }
4255
- const drawX = centerX - drawW / 2;
4256
- const drawY = featureGroundY - drawH;
4257
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
4258
- } else {
4259
- const color = FEATURE_COLORS[feature.type] || FEATURE_COLORS.default;
4260
- ctx.beginPath();
4261
- ctx.arc(centerX, featureGroundY - 8 * scale, 16 * scale, 0, Math.PI * 2);
4262
- ctx.fillStyle = color;
4263
- ctx.fill();
4264
- ctx.strokeStyle = "rgba(0,0,0,0.5)";
4265
- ctx.lineWidth = 2;
4266
- ctx.stroke();
4267
- }
4268
- }
4269
- const unitsWithPosition = units.filter((u) => !!u.position);
4270
- const sortedUnits = [...unitsWithPosition].sort((a, b) => {
4271
- const depthA = a.position.x + a.position.y;
4272
- const depthB = b.position.x + b.position.y;
4273
- return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
4274
- });
4275
- for (const unit of sortedUnits) {
4276
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
4277
- if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
4278
- continue;
4279
- }
4280
- const isSelected = unit.id === selectedUnitId;
4281
- const centerX = pos.x + scaledTileWidth / 2;
4282
- const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
4283
- const breatheOffset = 0.8 * scale * (1 + Math.sin(animTime * 2e-3 + (unit.position.x * 3.7 + unit.position.y * 5.3)));
4284
- const unitSpriteUrl = resolveUnitSpriteUrl(unit);
4285
- const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
4286
- const unitDrawH = scaledFloorHeight * 1.5 * unitScale;
4287
- const maxUnitW = scaledTileWidth * 0.6 * unitScale;
4288
- const ar = img ? img.naturalWidth / img.naturalHeight : 0.5;
4289
- let drawH = unitDrawH;
4290
- let drawW = unitDrawH * ar;
4291
- if (drawW > maxUnitW) {
4292
- drawW = maxUnitW;
4293
- drawH = maxUnitW / ar;
4294
- }
4295
- if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
4296
- const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
4297
- const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
4298
- const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
4299
- ctx.save();
4300
- ctx.globalAlpha = 0.25;
4301
- if (img) {
4302
- ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
4303
- } else {
4304
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
4305
- ctx.beginPath();
4306
- ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
4307
- ctx.fillStyle = color;
4308
- ctx.fill();
4309
- }
4310
- ctx.restore();
4311
- }
4312
- if (isSelected) {
4313
- const ringAlpha = 0.6 + 0.3 * Math.sin(animTime * 4e-3);
4314
- ctx.beginPath();
4315
- ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
4316
- ctx.strokeStyle = `rgba(0, 200, 255, ${ringAlpha})`;
4317
- ctx.lineWidth = 3;
4318
- ctx.stroke();
4319
- }
4320
- const frame = resolveUnitFrame?.(unit.id) ?? null;
4321
- const frameImg = frame ? getImage(frame.sheetUrl) : null;
4322
- if (frame && frameImg) {
4323
- const frameAr = frame.sw / frame.sh;
4324
- let fDrawH = unitDrawH;
4325
- let fDrawW = unitDrawH * frameAr;
4326
- if (fDrawW > maxUnitW) {
4327
- fDrawW = maxUnitW;
4328
- fDrawH = maxUnitW / frameAr;
4329
- }
4330
- const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
4331
- ctx.save();
4332
- if (unit.team) {
4333
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
4334
- ctx.shadowBlur = 12 * scale;
4335
- }
4336
- if (frame.flipX) {
4337
- ctx.translate(centerX, 0);
4338
- ctx.scale(-1, 1);
4339
- ctx.drawImage(frameImg, frame.sx, frame.sy, frame.sw, frame.sh, -fDrawW / 2, spriteY, fDrawW, fDrawH);
4340
- } else {
4341
- ctx.drawImage(frameImg, frame.sx, frame.sy, frame.sw, frame.sh, centerX - fDrawW / 2, spriteY, fDrawW, fDrawH);
4342
- }
4343
- ctx.restore();
4344
- } else if (img) {
4345
- const spriteY = groundY - drawH - breatheOffset;
4346
- if (unit.team) {
4347
- ctx.save();
4348
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
4349
- ctx.shadowBlur = 12 * scale;
4350
- ctx.drawImage(img, centerX - drawW / 2, spriteY, drawW, drawH);
4351
- ctx.restore();
4352
- } else {
4353
- ctx.drawImage(img, centerX - drawW / 2, spriteY, drawW, drawH);
4354
- }
4355
- } else {
4356
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
4357
- ctx.beginPath();
4358
- ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
4359
- ctx.fillStyle = color;
4360
- ctx.fill();
4361
- ctx.strokeStyle = "rgba(255,255,255,0.8)";
4362
- ctx.lineWidth = 2;
4363
- ctx.stroke();
4364
- }
4365
- if (unit.name) {
4366
- const labelBg = unit.team === "player" ? "rgba(59, 130, 246, 0.9)" : unit.team === "enemy" ? "rgba(239, 68, 68, 0.9)" : "rgba(107, 114, 128, 0.9)";
4367
- ctx.font = `bold ${10 * scale * 2.5}px system-ui`;
4368
- ctx.textAlign = "center";
4369
- const textWidth = ctx.measureText(unit.name).width;
4370
- const labelY = groundY + 14 * scale - breatheOffset;
4371
- ctx.fillStyle = labelBg;
4372
- ctx.beginPath();
4373
- ctx.roundRect(centerX - textWidth / 2 - 6 * scale, labelY - 8 * scale, textWidth + 12 * scale, 16 * scale, 4 * scale);
4374
- ctx.fill();
4375
- ctx.fillStyle = "white";
4376
- ctx.fillText(unit.name, centerX, labelY + 4 * scale);
4377
- }
4378
- if (unit.health !== void 0 && unit.maxHealth !== void 0) {
4379
- const barWidth = 40 * scale;
4380
- const barHeight = 6 * scale;
4381
- const barX = centerX - barWidth / 2;
4382
- const barY = groundY - drawH - 2 * scale - breatheOffset;
4383
- const healthRatio = unit.health / unit.maxHealth;
4384
- const barRadius = barHeight / 2;
4385
- ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
4386
- ctx.beginPath();
4387
- ctx.roundRect(barX, barY, barWidth, barHeight, barRadius);
4388
- ctx.fill();
4389
- if (healthRatio > 0) {
4390
- const fillWidth = barWidth * healthRatio;
4391
- const gradient = ctx.createLinearGradient(barX, barY, barX, barY + barHeight);
4392
- if (healthRatio > 0.6) {
4393
- gradient.addColorStop(0, "#4ade80");
4394
- gradient.addColorStop(1, "#22c55e");
4395
- } else if (healthRatio > 0.3) {
4396
- gradient.addColorStop(0, "#fde047");
4397
- gradient.addColorStop(1, "#eab308");
4398
- } else {
4399
- gradient.addColorStop(0, "#f87171");
4400
- gradient.addColorStop(1, "#ef4444");
4401
- }
4402
- ctx.fillStyle = gradient;
4403
- ctx.save();
4404
- ctx.beginPath();
4405
- ctx.roundRect(barX, barY, fillWidth, barHeight, barRadius);
4406
- ctx.clip();
4407
- ctx.fillRect(barX, barY, fillWidth, barHeight);
4408
- ctx.restore();
4409
- }
4410
- ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
4411
- ctx.lineWidth = 1;
4412
- ctx.beginPath();
4413
- ctx.roundRect(barX, barY, barWidth, barHeight, barRadius);
4414
- ctx.stroke();
4415
- }
4416
- }
4417
- onDrawEffects?.(ctx, animTime, getImage);
4418
- ctx.restore();
4419
- drawMinimap();
4420
- }, [
4421
- sortedTiles,
4422
- units,
4423
- features,
4424
- selectedUnitId,
4425
- scale,
4426
- debug,
4427
- resolveTerrainSpriteUrl,
4428
- resolveFeatureSpriteUrl,
4429
- resolveUnitSpriteUrl,
4430
- resolveUnitFrame,
4431
- getImage,
4432
- gridWidth,
4433
- gridHeight,
4434
- baseOffsetX,
4435
- scaledTileWidth,
4436
- scaledTileHeight,
4437
- scaledFloorHeight,
4438
- scaledDiamondTopY,
4439
- validMoveSet,
4440
- attackTargetSet,
4441
- hoveredTile,
4442
- viewportSize,
4443
- drawMinimap,
4444
- onDrawEffects,
4445
- backgroundImage,
4446
- cameraRef,
4447
- unitScale
4448
- ]);
4449
- useEffect(() => {
4450
- if (!selectedUnitId) return;
4451
- const unit = units.find((u) => u.id === selectedUnitId);
4452
- if (!unit?.position) return;
4453
- const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
4454
- const centerX = pos.x + scaledTileWidth / 2;
4455
- const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
4456
- targetCameraRef.current = {
4457
- x: centerX - viewportSize.width / 2,
4458
- y: centerY - viewportSize.height / 2
4459
- };
4460
- }, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
4461
- useEffect(() => {
4462
- const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2;
4463
- draw(animTimeRef.current);
4464
- if (!hasAnimations) return;
4465
- let running = true;
4466
- const animate = (timestamp) => {
4467
- if (!running) return;
4468
- animTimeRef.current = timestamp;
4469
- lerpToTarget();
4470
- draw(timestamp);
4471
- rafIdRef.current = requestAnimationFrame(animate);
4472
- };
4473
- rafIdRef.current = requestAnimationFrame(animate);
4474
- return () => {
4475
- running = false;
4476
- cancelAnimationFrame(rafIdRef.current);
4477
- };
4478
- }, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, lerpToTarget, targetCameraRef]);
4479
- const handleMouseMoveWithCamera = useCallback((e) => {
4480
- if (enableCamera) {
4481
- const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
4482
- if (wasPanning) return;
4483
- }
4484
- if (!onTileHover && !tileHoverEvent || !canvasRef.current) return;
4485
- const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
4486
- const adjustedX = world.x - scaledTileWidth / 2;
4487
- const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
4488
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
4489
- const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
4490
- if (tileExists) {
4491
- if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
4492
- onTileHover?.(isoPos.x, isoPos.y);
4493
- }
4494
- }, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
4495
- const handleMouseLeaveWithCamera = useCallback(() => {
4496
- handleMouseLeave();
4497
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
4498
- onTileLeave?.();
4499
- }, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
4500
- const handleWheelWithCamera = useCallback((e) => {
4501
- if (enableCamera) {
4502
- handleWheel(e, () => draw(animTimeRef.current));
4503
- }
4504
- }, [enableCamera, handleWheel, draw]);
4505
- const handleClick = useCallback((e) => {
4506
- if (dragDistance() > 5) return;
4507
- if (!canvasRef.current) return;
4508
- const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
4509
- const adjustedX = world.x - scaledTileWidth / 2;
4510
- const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
4511
- const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
4512
- const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
4513
- if (clickedUnit && (onUnitClick || unitClickEvent)) {
4514
- if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
4515
- onUnitClick?.(clickedUnit.id);
4516
- } else if (onTileClick || tileClickEvent) {
4517
- const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
4518
- if (tileExists) {
4519
- if (tileClickEvent) eventBus.emit(`UI:${tileClickEvent}`, { x: isoPos.x, y: isoPos.y });
4520
- onTileClick?.(isoPos.x, isoPos.y);
4521
- }
4522
- }
4523
- }, [dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
4524
- if (error) {
4525
- return /* @__PURE__ */ jsx(ErrorState, { title: t("canvas.errorTitle"), message: error.message, className });
4526
- }
4527
- if (isLoading) {
4528
- return /* @__PURE__ */ jsx(LoadingState, { className });
4529
- }
4530
- if (sortedTiles.length === 0) {
4531
- return /* @__PURE__ */ jsx(
4532
- Box,
4533
- {
4534
- className: cn("relative w-full overflow-hidden rounded-lg", className),
4535
- style: { height: viewportSize.height },
4536
- "data-testid": "game-canvas-empty",
4537
- children: /* @__PURE__ */ jsx(Box, { className: "flex items-center justify-center h-full bg-slate-800 rounded-lg", children: /* @__PURE__ */ jsxs(Stack, { direction: "vertical", gap: "md", align: "center", children: [
4538
- /* @__PURE__ */ jsx(Icon, { name: "map", size: "xl" }),
4539
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-slate-400", children: t("canvas.emptyMessage") || "No map data loaded" })
4540
- ] }) })
4541
- }
4542
- );
4543
- }
4544
- return /* @__PURE__ */ jsxs(
4545
- Box,
4546
- {
4547
- ref: containerRef,
4548
- className: cn("relative overflow-hidden w-full h-full", className),
4549
- children: [
4550
- /* @__PURE__ */ jsx(
4551
- "canvas",
4552
- {
4553
- ref: canvasRef,
4554
- "data-testid": "game-canvas",
4555
- onClick: handleClick,
4556
- onMouseDown: enableCamera ? handleMouseDown : void 0,
4557
- onMouseMove: handleMouseMoveWithCamera,
4558
- onMouseUp: enableCamera ? handleMouseUp : void 0,
4559
- onMouseLeave: handleMouseLeaveWithCamera,
4560
- onWheel: handleWheelWithCamera,
4561
- onContextMenu: (e) => e.preventDefault(),
4562
- className: "cursor-pointer",
4563
- style: {
4564
- width: viewportSize.width,
4565
- height: viewportSize.height
4566
- }
4567
- }
4568
- ),
4569
- process.env.NODE_ENV !== "production" && /* @__PURE__ */ jsxs("div", { "data-game-actions": "", className: "sr-only", "aria-hidden": "true", children: [
4570
- tileClickEvent && /* @__PURE__ */ jsx(
4571
- "button",
4572
- {
4573
- "data-event": tileClickEvent,
4574
- "data-x": "0",
4575
- "data-y": "0",
4576
- onClick: () => eventBus.emit(`UI:${tileClickEvent}`, { x: 0, y: 0 }),
4577
- children: tileClickEvent
4578
- }
4579
- ),
4580
- unitClickEvent && units && units.length > 0 && /* @__PURE__ */ jsx(
4581
- "button",
4582
- {
4583
- "data-event": unitClickEvent,
4584
- "data-unit-id": units[0].id,
4585
- onClick: () => eventBus.emit(`UI:${unitClickEvent}`, { unitId: units[0].id }),
4586
- children: unitClickEvent
4587
- }
4588
- )
4589
- ] }),
4590
- showMinimap && /* @__PURE__ */ jsx(
4591
- "canvas",
4592
- {
4593
- ref: minimapRef,
4594
- className: "absolute bottom-2 right-2 border border-border rounded bg-background/80 pointer-events-none",
4595
- style: { width: 150, height: 100, zIndex: 10 }
4596
- }
4597
- )
4598
- ]
4599
- }
4600
- );
4601
- }
4602
- IsometricCanvas.displayName = "IsometricCanvas";
4603
- var IsometricCanvas_default = IsometricCanvas;
4604
- var PLATFORM_COLORS = {
4605
- ground: "#4a7c59",
4606
- platform: "#7c6b4a",
4607
- hazard: "#c0392b",
4608
- goal: "#f1c40f"
4609
- };
4610
- var PLAYER_COLOR = "#3498db";
4611
- var PLAYER_EYE_COLOR = "#ffffff";
4612
- var SKY_GRADIENT_TOP = "#1a1a2e";
4613
- var SKY_GRADIENT_BOTTOM = "#16213e";
4614
- var GRID_COLOR = "rgba(255, 255, 255, 0.03)";
4615
- function PlatformerCanvas({
4616
- player,
4617
- platforms = [],
4618
- worldWidth = 800,
4619
- worldHeight = 400,
4620
- canvasWidth = 800,
4621
- canvasHeight = 400,
4622
- followCamera = true,
4623
- bgColor,
4624
- leftEvent = "MOVE_LEFT",
4625
- rightEvent = "MOVE_RIGHT",
4626
- jumpEvent = "JUMP",
4627
- stopEvent = "STOP",
4628
- className,
4629
- entity
4630
- }) {
4631
- const canvasRef = useRef(null);
4632
- const eventBus = useEventBus();
4633
- const keysRef = useRef(/* @__PURE__ */ new Set());
4634
- const resolvedPlayer = player ?? {
4635
- x: entity?.x ?? 50,
4636
- y: entity?.y ?? 300,
4637
- width: entity?.width ?? 24,
4638
- height: entity?.height ?? 32,
4639
- vx: entity?.vx ?? 0,
4640
- vy: entity?.vy ?? 0,
4641
- grounded: entity?.grounded ?? false,
4642
- facingRight: entity?.facingRight ?? true
4643
- };
4644
- const handleKeyDown = useCallback((e) => {
4645
- if (keysRef.current.has(e.code)) return;
4646
- keysRef.current.add(e.code);
4647
- switch (e.code) {
4648
- case "ArrowLeft":
4649
- case "KeyA":
4650
- eventBus.emit(`UI:${leftEvent}`, { direction: -1 });
4651
- break;
4652
- case "ArrowRight":
4653
- case "KeyD":
4654
- eventBus.emit(`UI:${rightEvent}`, { direction: 1 });
4655
- break;
4656
- case "ArrowUp":
4657
- case "KeyW":
4658
- case "Space":
4659
- eventBus.emit(`UI:${jumpEvent}`, {});
4660
- e.preventDefault();
4661
- break;
4662
- }
4663
- }, [eventBus, leftEvent, rightEvent, jumpEvent]);
4664
- const handleKeyUp = useCallback((e) => {
4665
- keysRef.current.delete(e.code);
4666
- switch (e.code) {
4667
- case "ArrowLeft":
4668
- case "KeyA":
4669
- case "ArrowRight":
4670
- case "KeyD":
4671
- eventBus.emit(`UI:${stopEvent}`, {});
4672
- break;
4673
- }
4674
- }, [eventBus, stopEvent]);
4675
- useEffect(() => {
4676
- window.addEventListener("keydown", handleKeyDown);
4677
- window.addEventListener("keyup", handleKeyUp);
4678
- return () => {
4679
- window.removeEventListener("keydown", handleKeyDown);
4680
- window.removeEventListener("keyup", handleKeyUp);
4681
- };
4682
- }, [handleKeyDown, handleKeyUp]);
4683
- useEffect(() => {
4684
- const canvas = canvasRef.current;
4685
- if (!canvas) return;
4686
- const ctx = canvas.getContext("2d");
4687
- if (!ctx) return;
4688
- const dpr = window.devicePixelRatio || 1;
4689
- canvas.width = canvasWidth * dpr;
4690
- canvas.height = canvasHeight * dpr;
4691
- ctx.scale(dpr, dpr);
4692
- let camX = 0;
4693
- let camY = 0;
4694
- if (followCamera) {
4695
- camX = Math.max(0, Math.min(resolvedPlayer.x - canvasWidth / 2, worldWidth - canvasWidth));
4696
- camY = Math.max(0, Math.min(resolvedPlayer.y - canvasHeight / 2 - 50, worldHeight - canvasHeight));
4697
- }
4698
- if (bgColor) {
4699
- ctx.fillStyle = bgColor;
4700
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
4701
- } else {
4702
- const grad = ctx.createLinearGradient(0, 0, 0, canvasHeight);
4703
- grad.addColorStop(0, SKY_GRADIENT_TOP);
4704
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
4705
- ctx.fillStyle = grad;
4706
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
4707
- }
4708
- ctx.strokeStyle = GRID_COLOR;
4709
- ctx.lineWidth = 1;
4710
- const gridSize = 32;
4711
- for (let gx = -camX % gridSize; gx < canvasWidth; gx += gridSize) {
4712
- ctx.beginPath();
4713
- ctx.moveTo(gx, 0);
4714
- ctx.lineTo(gx, canvasHeight);
4715
- ctx.stroke();
4716
- }
4717
- for (let gy = -camY % gridSize; gy < canvasHeight; gy += gridSize) {
4718
- ctx.beginPath();
4719
- ctx.moveTo(0, gy);
4720
- ctx.lineTo(canvasWidth, gy);
4721
- ctx.stroke();
4722
- }
4723
- for (const plat of platforms) {
4724
- const px = plat.x - camX;
4725
- const py = plat.y - camY;
4726
- const color = PLATFORM_COLORS[plat.type ?? "ground"] ?? PLATFORM_COLORS.ground;
4727
- ctx.fillStyle = color;
4728
- ctx.fillRect(px, py, plat.width, plat.height);
4729
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
4730
- ctx.fillRect(px, py, plat.width, 3);
4731
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
4732
- ctx.fillRect(px, py + plat.height - 2, plat.width, 2);
4733
- if (plat.type === "hazard") {
4734
- ctx.strokeStyle = "#e74c3c";
4735
- ctx.lineWidth = 2;
4736
- for (let sx = px; sx < px + plat.width; sx += 12) {
4737
- ctx.beginPath();
4738
- ctx.moveTo(sx, py);
4739
- ctx.lineTo(sx + 6, py + plat.height);
4740
- ctx.stroke();
4741
- }
4742
- }
4743
- if (plat.type === "goal") {
4744
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
4745
- ctx.beginPath();
4746
- ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
4747
- ctx.fill();
4748
- }
4749
- }
4750
- const pw = resolvedPlayer.width ?? 24;
4751
- const ph = resolvedPlayer.height ?? 32;
4752
- const ppx = resolvedPlayer.x - camX;
4753
- const ppy = resolvedPlayer.y - camY;
4754
- ctx.fillStyle = PLAYER_COLOR;
4755
- const radius = Math.min(pw, ph) * 0.25;
4756
- ctx.beginPath();
4757
- ctx.moveTo(ppx + radius, ppy);
4758
- ctx.lineTo(ppx + pw - radius, ppy);
4759
- ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
4760
- ctx.lineTo(ppx + pw, ppy + ph - radius);
4761
- ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
4762
- ctx.lineTo(ppx + radius, ppy + ph);
4763
- ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
4764
- ctx.lineTo(ppx, ppy + radius);
4765
- ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
4766
- ctx.fill();
4767
- const eyeY = ppy + ph * 0.3;
4768
- const eyeSize = 3;
4769
- const facingRight = resolvedPlayer.facingRight ?? true;
4770
- const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
4771
- ctx.fillStyle = PLAYER_EYE_COLOR;
4772
- ctx.beginPath();
4773
- ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
4774
- ctx.fill();
4775
- ctx.beginPath();
4776
- ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
4777
- ctx.fill();
4778
- });
4779
- return /* @__PURE__ */ jsx(
4780
- "canvas",
4781
- {
4782
- ref: canvasRef,
4783
- style: { width: canvasWidth, height: canvasHeight },
4784
- className: cn("block rounded-lg border border-white/10", className),
4785
- "data-testid": "platformer-canvas",
4786
- tabIndex: 0
4787
- }
4788
- );
4789
- }
4790
- PlatformerCanvas.displayName = "PlatformerCanvas";
4791
3748
 
4792
3749
  // components/organisms/game/types/effects.ts
4793
3750
  var EMPTY_EFFECT_STATE = {
@@ -6745,717 +5702,94 @@ var PhysicsManager = class {
6745
5702
  /**
6746
5703
  * Reset all physics state
6747
5704
  */
6748
- reset() {
6749
- this.entities.clear();
6750
- }
6751
- };
6752
-
6753
- // components/organisms/game/hooks/usePhysics2D.ts
6754
- function usePhysics2D(options = {}) {
6755
- const physicsManagerRef = useRef(null);
6756
- const collisionCallbacksRef = useRef(/* @__PURE__ */ new Set());
6757
- if (!physicsManagerRef.current) {
6758
- physicsManagerRef.current = new PhysicsManager({
6759
- gravity: options.gravity,
6760
- friction: options.friction,
6761
- airResistance: options.airResistance,
6762
- maxVelocityY: options.maxVelocityY,
6763
- groundY: options.groundY
6764
- });
6765
- }
6766
- const manager = physicsManagerRef.current;
6767
- useEffect(() => {
6768
- if (options.onCollision) {
6769
- collisionCallbacksRef.current.add(options.onCollision);
6770
- }
6771
- return () => {
6772
- if (options.onCollision) {
6773
- collisionCallbacksRef.current.delete(options.onCollision);
6774
- }
6775
- };
6776
- }, [options.onCollision]);
6777
- const registerUnit = useCallback((unitId, initialState = {}) => {
6778
- manager.registerEntity(unitId, initialState);
6779
- }, [manager]);
6780
- const unregisterUnit = useCallback((unitId) => {
6781
- manager.unregisterEntity(unitId);
6782
- }, [manager]);
6783
- const getPosition = useCallback((unitId) => {
6784
- const state = manager.getState(unitId);
6785
- if (!state) return null;
6786
- return { x: state.x, y: state.y };
6787
- }, [manager]);
6788
- const getState = useCallback((unitId) => {
6789
- return manager.getState(unitId);
6790
- }, [manager]);
6791
- const applyForce = useCallback((unitId, fx, fy) => {
6792
- manager.applyForce(unitId, fx, fy);
6793
- }, [manager]);
6794
- const setVelocity = useCallback((unitId, vx, vy) => {
6795
- manager.setVelocity(unitId, vx, vy);
6796
- }, [manager]);
6797
- const setPosition = useCallback((unitId, x, y) => {
6798
- manager.setPosition(unitId, x, y);
6799
- }, [manager]);
6800
- const tick = useCallback((deltaTime = 16) => {
6801
- manager.tick(deltaTime);
6802
- }, [manager]);
6803
- const checkCollision = useCallback((unitIdA, unitIdB, boundsA, boundsB) => {
6804
- return manager.checkCollision(unitIdA, unitIdB, boundsA, boundsB);
6805
- }, [manager]);
6806
- const resolveCollision = useCallback((unitIdA, unitIdB) => {
6807
- manager.resolveCollision(unitIdA, unitIdB);
6808
- collisionCallbacksRef.current.forEach((callback) => {
6809
- callback(unitIdA, unitIdB);
6810
- });
6811
- }, [manager]);
6812
- const setFrozen = useCallback((unitId, frozen) => {
6813
- manager.setFrozen(unitId, frozen);
6814
- }, [manager]);
6815
- const getAllUnits = useCallback(() => {
6816
- return manager.getAllEntities();
6817
- }, [manager]);
6818
- const reset = useCallback(() => {
6819
- manager.reset();
6820
- }, [manager]);
6821
- return {
6822
- registerUnit,
6823
- unregisterUnit,
6824
- getPosition,
6825
- getState,
6826
- applyForce,
6827
- setVelocity,
6828
- setPosition,
6829
- tick,
6830
- checkCollision,
6831
- resolveCollision,
6832
- setFrozen,
6833
- getAllUnits,
6834
- reset
6835
- };
6836
- }
6837
- var positionMap = {
6838
- top: "top-0 left-0 right-0 flex justify-between items-start p-4",
6839
- bottom: "bottom-0 left-0 right-0 flex justify-between items-end p-4",
6840
- corners: "inset-0 pointer-events-none"
6841
- };
6842
- function convertElementsToStats(elements) {
6843
- return elements.map((el) => {
6844
- const [source, field] = el.bind?.split(".") ?? [];
6845
- const labelMap = {
6846
- "health-bar": "Health",
6847
- "score-display": "Score",
6848
- lives: "Lives",
6849
- timer: "Time"
6850
- };
6851
- return {
6852
- label: el.label || labelMap[el.type ?? ""] || el.type || "",
6853
- source,
6854
- field,
6855
- // Pass through direct values from compiled render-ui effects
6856
- ...el.value !== void 0 && { value: el.value },
6857
- ...el.icon !== void 0 && { icon: el.icon },
6858
- ...el.format !== void 0 && { format: el.format },
6859
- ...el.max !== void 0 && { max: el.max }
6860
- };
6861
- });
6862
- }
6863
- function GameHud({
6864
- position: propPosition,
6865
- stats: propStats,
6866
- items,
6867
- elements,
6868
- size = "md",
6869
- className,
6870
- transparent = true
6871
- }) {
6872
- const rawStats = propStats ?? items ?? (elements && Array.isArray(elements) ? convertElementsToStats(elements) : []);
6873
- const stats = Array.isArray(rawStats) ? rawStats : [];
6874
- const position = propPosition ?? "corners";
6875
- if (position === "corners") {
6876
- const leftStats = stats.slice(0, Math.ceil(stats.length / 2));
6877
- const rightStats = stats.slice(Math.ceil(stats.length / 2));
6878
- return /* @__PURE__ */ jsxs("div", { className: cn("absolute", positionMap[position], className), children: [
6879
- /* @__PURE__ */ jsx("div", { className: "absolute top-4 left-4 flex flex-col gap-2 pointer-events-auto", children: leftStats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) }),
6880
- /* @__PURE__ */ jsx("div", { className: "absolute top-4 right-4 flex flex-col gap-2 items-end pointer-events-auto", children: rightStats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) })
6881
- ] });
6882
- }
6883
- return /* @__PURE__ */ jsx(
6884
- "div",
6885
- {
6886
- className: cn(
6887
- "absolute z-10",
6888
- positionMap[position],
6889
- transparent && "bg-gradient-to-b from-black/50 to-transparent",
6890
- position === "bottom" && "bg-gradient-to-t from-black/50 to-transparent",
6891
- className
6892
- ),
6893
- children: /* @__PURE__ */ jsx("div", { className: "flex gap-4", children: stats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) })
6894
- }
6895
- );
6896
- }
6897
- GameHud.displayName = "GameHud";
6898
- var variantMap = {
6899
- primary: "bg-blue-600 hover:bg-blue-500 text-white border-blue-400 shadow-lg shadow-blue-500/25",
6900
- secondary: "bg-gray-700 hover:bg-gray-600 text-white border-gray-500",
6901
- ghost: "bg-transparent hover:bg-white/10 text-white border-white/20"
6902
- };
6903
- function GameMenu({
6904
- title,
6905
- subtitle,
6906
- options,
6907
- menuItems,
6908
- onSelect,
6909
- eventBus: eventBusProp,
6910
- background,
6911
- logo,
6912
- className
6913
- }) {
6914
- const resolvedOptions = options ?? menuItems ?? [];
6915
- let eventBusFromHook = null;
6916
- try {
6917
- eventBusFromHook = useEventBus();
6918
- } catch {
6919
- }
6920
- const eventBus = eventBusProp || eventBusFromHook;
6921
- const handleOptionClick = React.useCallback(
6922
- (option) => {
6923
- if (option.event && eventBus) {
6924
- eventBus.emit(`UI:${option.event}`, { option });
6925
- }
6926
- if (onSelect) {
6927
- onSelect(option);
6928
- }
6929
- if (option.navigatesTo && eventBus) {
6930
- eventBus.emit("UI:NAVIGATE", { url: option.navigatesTo, option });
6931
- }
6932
- },
6933
- [eventBus, onSelect]
6934
- );
6935
- return /* @__PURE__ */ jsxs(
6936
- "div",
6937
- {
6938
- className: cn(
6939
- "min-h-screen w-full flex flex-col items-center justify-center p-8",
6940
- className
6941
- ),
6942
- style: {
6943
- background: background ?? "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0e17 100%)"
6944
- },
6945
- children: [
6946
- /* @__PURE__ */ jsxs("div", { className: "text-center mb-12 animate-fade-in", children: [
6947
- logo && /* @__PURE__ */ jsx(
6948
- "img",
6949
- {
6950
- src: logo,
6951
- alt: title,
6952
- className: "h-24 w-auto mx-auto mb-6 drop-shadow-2xl"
6953
- }
6954
- ),
6955
- /* @__PURE__ */ jsx(
6956
- "h1",
6957
- {
6958
- className: "text-5xl md:text-7xl font-bold text-white tracking-tight",
6959
- style: {
6960
- textShadow: "0 4px 12px rgba(0,0,0,0.5)"
6961
- },
6962
- children: title
6963
- }
6964
- ),
6965
- subtitle && /* @__PURE__ */ jsx("p", { className: "mt-2 text-lg text-gray-400 tracking-widest uppercase", children: subtitle })
6966
- ] }),
6967
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: resolvedOptions.map((option, index) => /* @__PURE__ */ jsx(
6968
- "button",
6969
- {
6970
- onClick: () => handleOptionClick(option),
6971
- disabled: option.disabled,
6972
- className: cn(
6973
- "w-full py-4 px-8 rounded-xl border-2 font-bold text-lg",
6974
- "transition-all duration-200 transform",
6975
- "hover:scale-105 active:scale-95",
6976
- "focus:outline-none focus:ring-4 focus:ring-white/25",
6977
- variantMap[option.variant ?? "secondary"] ?? variantMap.secondary,
6978
- option.disabled && "opacity-50 cursor-not-allowed hover:scale-100"
6979
- ),
6980
- style: {
6981
- animationDelay: `${index * 100}ms`
6982
- },
6983
- children: option.label
6984
- },
6985
- index
6986
- )) }),
6987
- /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 pointer-events-none overflow-hidden", children: [
6988
- /* @__PURE__ */ jsx("div", { className: "absolute top-1/4 left-1/4 w-64 h-64 bg-blue-500/10 rounded-full blur-3xl" }),
6989
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-1/4 right-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl" })
6990
- ] })
6991
- ]
6992
- }
6993
- );
6994
- }
6995
- GameMenu.displayName = "GameMenu";
6996
- var variantColors = {
6997
- victory: {
6998
- bg: "from-green-900/90 to-emerald-950/90",
6999
- title: "text-green-400",
7000
- accent: "border-green-500"
7001
- },
7002
- defeat: {
7003
- bg: "from-red-900/90 to-gray-950/90",
7004
- title: "text-red-400",
7005
- accent: "border-red-500"
7006
- },
7007
- neutral: {
7008
- bg: "from-gray-900/90 to-gray-950/90",
7009
- title: "text-white",
7010
- accent: "border-gray-500"
7011
- }
7012
- };
7013
- var buttonVariants = {
7014
- primary: "bg-blue-600 hover:bg-blue-500 text-white border-blue-400",
7015
- secondary: "bg-gray-700 hover:bg-gray-600 text-white border-gray-500",
7016
- ghost: "bg-transparent hover:bg-white/10 text-white border-white/20"
7017
- };
7018
- function formatTime(ms) {
7019
- const seconds = Math.floor(ms / 1e3);
7020
- const minutes = Math.floor(seconds / 60);
7021
- const remainingSeconds = seconds % 60;
7022
- return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
7023
- }
7024
- function GameOverScreen({
7025
- title,
7026
- message,
7027
- stats = [],
7028
- actions,
7029
- menuItems,
7030
- onAction,
7031
- eventBus: eventBusProp,
7032
- variant = "neutral",
7033
- highScore,
7034
- currentScore,
7035
- className
7036
- }) {
7037
- const resolvedActions = actions ?? menuItems ?? [];
7038
- let eventBusFromHook = null;
7039
- try {
7040
- eventBusFromHook = useEventBus();
7041
- } catch {
7042
- }
7043
- const eventBus = eventBusProp || eventBusFromHook;
7044
- const handleActionClick = React.useCallback(
7045
- (action) => {
7046
- if (action.event && eventBus) {
7047
- eventBus.emit(`UI:${action.event}`, { action });
7048
- }
7049
- if (onAction) {
7050
- onAction(action);
7051
- }
7052
- },
7053
- [eventBus, onAction]
7054
- );
7055
- const colors = variantColors[variant];
7056
- const numericCurrentScore = typeof currentScore === "string" ? parseFloat(currentScore) : currentScore;
7057
- const numericHighScore = typeof highScore === "string" ? parseFloat(highScore) : highScore;
7058
- const isNewHighScore = numericHighScore !== void 0 && numericCurrentScore !== void 0 && !isNaN(numericCurrentScore) && !isNaN(numericHighScore) && numericCurrentScore > numericHighScore;
7059
- return /* @__PURE__ */ jsxs(
7060
- "div",
7061
- {
7062
- className: cn(
7063
- "min-h-screen w-full flex flex-col items-center justify-center p-8",
7064
- "bg-gradient-to-b",
7065
- colors.bg,
7066
- className
7067
- ),
7068
- children: [
7069
- variant === "victory" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 pointer-events-none overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(34,197,94,0.2),transparent_70%)]" }) }),
7070
- /* @__PURE__ */ jsx(
7071
- "h1",
7072
- {
7073
- className: cn(
7074
- "text-6xl md:text-8xl font-bold mb-4 tracking-tight animate-bounce-once",
7075
- colors.title
7076
- ),
7077
- style: { textShadow: "0 4px 20px rgba(0,0,0,0.5)" },
7078
- children: title
7079
- }
7080
- ),
7081
- message && /* @__PURE__ */ jsx("p", { className: "text-xl text-gray-300 mb-8 text-center max-w-md", children: message }),
7082
- isNewHighScore && /* @__PURE__ */ jsx("div", { className: "mb-6 px-6 py-2 bg-yellow-500/20 border-2 border-yellow-500 rounded-full animate-pulse", children: /* @__PURE__ */ jsx("span", { className: "text-yellow-400 font-bold text-lg tracking-wide", children: "\u{1F3C6} NEW HIGH SCORE! \u{1F3C6}" }) }),
7083
- stats.length > 0 && /* @__PURE__ */ jsx(
7084
- "div",
7085
- {
7086
- className: cn(
7087
- "mb-8 p-6 rounded-xl border-2 bg-black/30",
7088
- colors.accent
7089
- ),
7090
- children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: stats.map((stat, index) => {
7091
- let displayValue = stat.value ?? 0;
7092
- if (stat.format === "time" && typeof displayValue === "number") {
7093
- displayValue = formatTime(displayValue);
7094
- }
7095
- return /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
7096
- /* @__PURE__ */ jsx("div", { className: "text-gray-400 text-sm mb-1", children: stat.label }),
7097
- /* @__PURE__ */ jsxs("div", { className: "text-white text-2xl font-bold flex items-center justify-center gap-2", children: [
7098
- stat.icon && /* @__PURE__ */ jsx("span", { children: stat.icon }),
7099
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: displayValue })
7100
- ] })
7101
- ] }, index);
7102
- }) })
7103
- }
7104
- ),
7105
- /* @__PURE__ */ jsx("div", { className: "flex flex-col sm:flex-row gap-4", children: resolvedActions.map((action, index) => /* @__PURE__ */ jsx(
7106
- "button",
7107
- {
7108
- onClick: () => handleActionClick(action),
7109
- className: cn(
7110
- "px-8 py-4 rounded-xl border-2 font-bold text-lg",
7111
- "transition-all duration-200",
7112
- "hover:scale-105 active:scale-95",
7113
- "focus:outline-none focus:ring-4 focus:ring-white/25",
7114
- buttonVariants[action.variant ?? "secondary"]
7115
- ),
7116
- children: action.label
7117
- },
7118
- index
7119
- )) })
7120
- ]
7121
- }
7122
- );
7123
- }
7124
- GameOverScreen.displayName = "GameOverScreen";
7125
- function InventoryPanel({
7126
- items,
7127
- slots,
7128
- columns,
7129
- selectedSlot,
7130
- onSelectSlot,
7131
- onUseItem,
7132
- onDropItem,
7133
- selectSlotEvent,
7134
- useItemEvent,
7135
- dropItemEvent,
7136
- showTooltips = true,
7137
- className,
7138
- slotSize = 48
7139
- }) {
7140
- const eventBus = useEventBus();
7141
- const [hoveredSlot, setHoveredSlot] = useState(null);
7142
- const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
7143
- const safeItems = Array.isArray(items) ? items : [];
7144
- const safeSlots = typeof slots === "number" && slots > 0 ? slots : 0;
7145
- const safeColumns = typeof columns === "number" && columns > 0 ? columns : 1;
7146
- const slotArray = Array.from({ length: safeSlots }, (_, index) => {
7147
- return safeItems[index] ?? null;
7148
- });
7149
- const rows = Math.ceil(safeSlots / safeColumns);
7150
- const handleSlotClick = useCallback((index) => {
7151
- if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
7152
- onSelectSlot?.(index);
7153
- }, [onSelectSlot, selectSlotEvent, eventBus]);
7154
- const handleSlotDoubleClick = useCallback((index) => {
7155
- const item = slotArray[index];
7156
- if (item) {
7157
- if (useItemEvent) eventBus.emit(`UI:${useItemEvent}`, { item });
7158
- onUseItem?.(item);
7159
- }
7160
- }, [slotArray, onUseItem, useItemEvent, eventBus]);
7161
- const handleKeyDown = useCallback((e, index) => {
7162
- const item = slotArray[index];
7163
- switch (e.key) {
7164
- case "Enter":
7165
- case " ":
7166
- if (item) {
7167
- e.preventDefault();
7168
- if (useItemEvent) eventBus.emit(`UI:${useItemEvent}`, { item });
7169
- onUseItem?.(item);
7170
- }
7171
- break;
7172
- case "Delete":
7173
- case "Backspace":
7174
- if (item) {
7175
- e.preventDefault();
7176
- if (dropItemEvent) eventBus.emit(`UI:${dropItemEvent}`, { item });
7177
- onDropItem?.(item);
7178
- }
7179
- break;
7180
- case "ArrowRight":
7181
- e.preventDefault();
7182
- onSelectSlot?.(Math.min(index + 1, safeSlots - 1));
7183
- break;
7184
- case "ArrowLeft":
7185
- e.preventDefault();
7186
- onSelectSlot?.(Math.max(index - 1, 0));
7187
- break;
7188
- case "ArrowDown":
7189
- e.preventDefault();
7190
- onSelectSlot?.(Math.min(index + safeColumns, safeSlots - 1));
7191
- break;
7192
- case "ArrowUp":
7193
- e.preventDefault();
7194
- onSelectSlot?.(Math.max(index - safeColumns, 0));
7195
- break;
7196
- }
7197
- }, [slotArray, onUseItem, onDropItem, onSelectSlot, safeColumns, safeSlots, useItemEvent, dropItemEvent, eventBus]);
7198
- const handleMouseEnter = useCallback((e, index) => {
7199
- if (showTooltips && slotArray[index]) {
7200
- setHoveredSlot(index);
7201
- setTooltipPosition({
7202
- x: e.clientX + 10,
7203
- y: e.clientY + 10
7204
- });
7205
- }
7206
- }, [showTooltips, slotArray]);
7207
- const handleMouseLeave = useCallback(() => {
7208
- setHoveredSlot(null);
7209
- }, []);
7210
- const hoveredItem = hoveredSlot !== null ? slotArray[hoveredSlot] : null;
7211
- return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
7212
- /* @__PURE__ */ jsx(
7213
- "div",
7214
- {
7215
- className: "grid gap-1 bg-gray-900 p-2 rounded-lg border border-gray-700",
7216
- style: {
7217
- gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
7218
- gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
7219
- },
7220
- children: slotArray.map((item, index) => /* @__PURE__ */ jsx(
7221
- "button",
7222
- {
7223
- type: "button",
7224
- className: cn(
7225
- "relative flex items-center justify-center",
7226
- "bg-gray-800 border rounded transition-colors",
7227
- "hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",
7228
- selectedSlot === index ? "border-yellow-400 bg-gray-700" : "border-gray-600"
7229
- ),
7230
- style: { width: slotSize, height: slotSize },
7231
- onClick: () => handleSlotClick(index),
7232
- onDoubleClick: () => handleSlotDoubleClick(index),
7233
- onKeyDown: (e) => handleKeyDown(e, index),
7234
- onMouseEnter: (e) => handleMouseEnter(e, index),
7235
- onMouseLeave: handleMouseLeave,
7236
- tabIndex: 0,
7237
- "aria-label": item ? `${item.name || item.type}, quantity: ${item.quantity}` : `Empty slot ${index + 1}`,
7238
- children: item && /* @__PURE__ */ jsxs(Fragment, { children: [
7239
- item.sprite ? /* @__PURE__ */ jsx(
7240
- "img",
7241
- {
7242
- src: item.sprite,
7243
- alt: item.name || item.type,
7244
- className: "w-8 h-8 object-contain",
7245
- style: { imageRendering: "pixelated" }
7246
- }
7247
- ) : /* @__PURE__ */ jsx("div", { className: "w-8 h-8 bg-gray-600 rounded flex items-center justify-center text-xs text-gray-300", children: item.type.charAt(0).toUpperCase() }),
7248
- item.quantity > 1 && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 right-0 bg-black bg-opacity-70 text-white text-xs px-1 rounded-tl", children: item.quantity })
7249
- ] })
7250
- },
7251
- index
7252
- ))
7253
- }
7254
- ),
7255
- showTooltips && hoveredItem && /* @__PURE__ */ jsxs(
7256
- "div",
7257
- {
7258
- className: "fixed z-50 bg-gray-900 border border-gray-600 rounded-lg p-2 shadow-lg pointer-events-none",
7259
- style: {
7260
- left: tooltipPosition.x,
7261
- top: tooltipPosition.y,
7262
- maxWidth: 200
7263
- },
7264
- children: [
7265
- /* @__PURE__ */ jsx("div", { className: "font-semibold text-white", children: hoveredItem.name || hoveredItem.type }),
7266
- hoveredItem.description && /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-400 mt-1", children: hoveredItem.description }),
7267
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
7268
- "Quantity: ",
7269
- hoveredItem.quantity
7270
- ] })
7271
- ]
7272
- }
7273
- )
7274
- ] });
7275
- }
7276
- function DialogueBox({
7277
- dialogue,
7278
- typewriterSpeed = 30,
7279
- position = "bottom",
7280
- onComplete,
7281
- onChoice,
7282
- onAdvance,
7283
- completeEvent,
7284
- choiceEvent,
7285
- advanceEvent,
7286
- className
7287
- }) {
7288
- const eventBus = useEventBus();
7289
- const [displayedText, setDisplayedText] = useState("");
7290
- const [isTyping, setIsTyping] = useState(false);
7291
- const [selectedChoice, setSelectedChoice] = useState(0);
7292
- const textRef = useRef(dialogue.text);
7293
- const charIndexRef = useRef(0);
7294
- const autoAdvanceTimerRef = useRef(null);
5705
+ reset() {
5706
+ this.entities.clear();
5707
+ }
5708
+ };
5709
+
5710
+ // components/organisms/game/hooks/usePhysics2D.ts
5711
+ function usePhysics2D(options = {}) {
5712
+ const physicsManagerRef = useRef(null);
5713
+ const collisionCallbacksRef = useRef(/* @__PURE__ */ new Set());
5714
+ if (!physicsManagerRef.current) {
5715
+ physicsManagerRef.current = new PhysicsManager({
5716
+ gravity: options.gravity,
5717
+ friction: options.friction,
5718
+ airResistance: options.airResistance,
5719
+ maxVelocityY: options.maxVelocityY,
5720
+ groundY: options.groundY
5721
+ });
5722
+ }
5723
+ const manager = physicsManagerRef.current;
7295
5724
  useEffect(() => {
7296
- textRef.current = dialogue.text;
7297
- charIndexRef.current = 0;
7298
- setDisplayedText("");
7299
- setSelectedChoice(0);
7300
- if (typewriterSpeed === 0) {
7301
- setDisplayedText(dialogue.text);
7302
- setIsTyping(false);
7303
- if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
7304
- onComplete?.();
7305
- } else {
7306
- setIsTyping(true);
5725
+ if (options.onCollision) {
5726
+ collisionCallbacksRef.current.add(options.onCollision);
7307
5727
  }
7308
5728
  return () => {
7309
- if (autoAdvanceTimerRef.current) {
7310
- clearTimeout(autoAdvanceTimerRef.current);
5729
+ if (options.onCollision) {
5730
+ collisionCallbacksRef.current.delete(options.onCollision);
7311
5731
  }
7312
5732
  };
7313
- }, [dialogue, typewriterSpeed, onComplete]);
7314
- useEffect(() => {
7315
- if (!isTyping || typewriterSpeed === 0) return;
7316
- const interval = setInterval(() => {
7317
- if (charIndexRef.current < textRef.current.length) {
7318
- charIndexRef.current++;
7319
- setDisplayedText(textRef.current.slice(0, charIndexRef.current));
7320
- } else {
7321
- setIsTyping(false);
7322
- clearInterval(interval);
7323
- if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
7324
- onComplete?.();
7325
- if (dialogue.autoAdvance && !dialogue.choices?.length) {
7326
- autoAdvanceTimerRef.current = setTimeout(() => {
7327
- if (advanceEvent) eventBus.emit(`UI:${advanceEvent}`, {});
7328
- onAdvance?.();
7329
- }, dialogue.autoAdvance);
7330
- }
7331
- }
7332
- }, typewriterSpeed);
7333
- return () => clearInterval(interval);
7334
- }, [isTyping, typewriterSpeed, dialogue.autoAdvance, dialogue.choices, onComplete, onAdvance]);
7335
- const skipTypewriter = useCallback(() => {
7336
- if (isTyping) {
7337
- charIndexRef.current = textRef.current.length;
7338
- setDisplayedText(textRef.current);
7339
- setIsTyping(false);
7340
- if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
7341
- onComplete?.();
7342
- }
7343
- }, [isTyping, onComplete, completeEvent, eventBus]);
7344
- const handleClick = useCallback(() => {
7345
- if (isTyping) {
7346
- skipTypewriter();
7347
- } else if (!dialogue.choices?.length) {
7348
- if (advanceEvent) eventBus.emit(`UI:${advanceEvent}`, {});
7349
- onAdvance?.();
7350
- }
7351
- }, [isTyping, skipTypewriter, dialogue.choices, onAdvance, advanceEvent, eventBus]);
7352
- const handleKeyDown = useCallback((e) => {
7353
- if (isTyping) {
7354
- if (e.key === " " || e.key === "Enter") {
7355
- e.preventDefault();
7356
- skipTypewriter();
7357
- }
7358
- return;
7359
- }
7360
- if (dialogue.choices?.length) {
7361
- const enabledChoices2 = dialogue.choices.filter((c) => !c.disabled);
7362
- switch (e.key) {
7363
- case "ArrowUp":
7364
- e.preventDefault();
7365
- setSelectedChoice((prev) => Math.max(0, prev - 1));
7366
- break;
7367
- case "ArrowDown":
7368
- e.preventDefault();
7369
- setSelectedChoice((prev) => Math.min(enabledChoices2.length - 1, prev + 1));
7370
- break;
7371
- case "Enter":
7372
- case " ":
7373
- e.preventDefault();
7374
- const choice = enabledChoices2[selectedChoice];
7375
- if (choice) {
7376
- if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
7377
- onChoice?.(choice);
7378
- }
7379
- break;
7380
- case "1":
7381
- case "2":
7382
- case "3":
7383
- case "4":
7384
- const choiceIndex = parseInt(e.key) - 1;
7385
- if (choiceIndex < enabledChoices2.length) {
7386
- e.preventDefault();
7387
- if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice: enabledChoices2[choiceIndex] });
7388
- onChoice?.(enabledChoices2[choiceIndex]);
7389
- }
7390
- break;
7391
- }
7392
- } else {
7393
- if (e.key === " " || e.key === "Enter") {
7394
- e.preventDefault();
7395
- if (advanceEvent) eventBus.emit(`UI:${advanceEvent}`, {});
7396
- onAdvance?.();
7397
- }
7398
- }
7399
- }, [isTyping, skipTypewriter, dialogue.choices, selectedChoice, onChoice, onAdvance, choiceEvent, advanceEvent, eventBus]);
7400
- const enabledChoices = dialogue.choices?.filter((c) => !c.disabled) ?? [];
7401
- return /* @__PURE__ */ jsx(
7402
- "div",
7403
- {
7404
- className: cn(
7405
- "fixed left-0 right-0 z-40",
7406
- position === "top" ? "top-0" : "bottom-0",
7407
- className
7408
- ),
7409
- onClick: handleClick,
7410
- onKeyDown: handleKeyDown,
7411
- tabIndex: 0,
7412
- role: "dialog",
7413
- "aria-label": "Dialogue",
7414
- children: /* @__PURE__ */ jsx("div", { className: "mx-4 my-4 bg-gray-900 bg-opacity-95 border-2 border-gray-600 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
7415
- dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-gray-700", children: /* @__PURE__ */ jsx(
7416
- "img",
7417
- {
7418
- src: dialogue.portrait,
7419
- alt: dialogue.speaker,
7420
- className: "w-24 h-24 object-contain",
7421
- style: { imageRendering: "pixelated" }
7422
- }
7423
- ) }),
7424
- /* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
7425
- /* @__PURE__ */ jsx("div", { className: "text-yellow-400 font-bold mb-2", children: dialogue.speaker }),
7426
- /* @__PURE__ */ jsxs("div", { className: "text-white text-lg leading-relaxed min-h-[60px]", children: [
7427
- displayedText,
7428
- isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
7429
- ] }),
7430
- !isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
7431
- "button",
7432
- {
7433
- type: "button",
7434
- className: cn(
7435
- "block w-full text-left px-4 py-2 rounded transition-colors",
7436
- "hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-yellow-400",
7437
- selectedChoice === index ? "bg-gray-700 text-yellow-400" : "bg-gray-800 text-white"
7438
- ),
7439
- onClick: (e) => {
7440
- e.stopPropagation();
7441
- if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
7442
- onChoice?.(choice);
7443
- },
7444
- children: [
7445
- /* @__PURE__ */ jsxs("span", { className: "text-gray-500 mr-2", children: [
7446
- index + 1,
7447
- "."
7448
- ] }),
7449
- choice.text
7450
- ]
7451
- },
7452
- index
7453
- )) }),
7454
- !isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-gray-500 text-sm animate-pulse", children: "Press SPACE or click to continue..." })
7455
- ] })
7456
- ] }) })
7457
- }
7458
- );
5733
+ }, [options.onCollision]);
5734
+ const registerUnit = useCallback((unitId, initialState = {}) => {
5735
+ manager.registerEntity(unitId, initialState);
5736
+ }, [manager]);
5737
+ const unregisterUnit = useCallback((unitId) => {
5738
+ manager.unregisterEntity(unitId);
5739
+ }, [manager]);
5740
+ const getPosition = useCallback((unitId) => {
5741
+ const state = manager.getState(unitId);
5742
+ if (!state) return null;
5743
+ return { x: state.x, y: state.y };
5744
+ }, [manager]);
5745
+ const getState = useCallback((unitId) => {
5746
+ return manager.getState(unitId);
5747
+ }, [manager]);
5748
+ const applyForce = useCallback((unitId, fx, fy) => {
5749
+ manager.applyForce(unitId, fx, fy);
5750
+ }, [manager]);
5751
+ const setVelocity = useCallback((unitId, vx, vy) => {
5752
+ manager.setVelocity(unitId, vx, vy);
5753
+ }, [manager]);
5754
+ const setPosition = useCallback((unitId, x, y) => {
5755
+ manager.setPosition(unitId, x, y);
5756
+ }, [manager]);
5757
+ const tick = useCallback((deltaTime = 16) => {
5758
+ manager.tick(deltaTime);
5759
+ }, [manager]);
5760
+ const checkCollision = useCallback((unitIdA, unitIdB, boundsA, boundsB) => {
5761
+ return manager.checkCollision(unitIdA, unitIdB, boundsA, boundsB);
5762
+ }, [manager]);
5763
+ const resolveCollision = useCallback((unitIdA, unitIdB) => {
5764
+ manager.resolveCollision(unitIdA, unitIdB);
5765
+ collisionCallbacksRef.current.forEach((callback) => {
5766
+ callback(unitIdA, unitIdB);
5767
+ });
5768
+ }, [manager]);
5769
+ const setFrozen = useCallback((unitId, frozen) => {
5770
+ manager.setFrozen(unitId, frozen);
5771
+ }, [manager]);
5772
+ const getAllUnits = useCallback(() => {
5773
+ return manager.getAllEntities();
5774
+ }, [manager]);
5775
+ const reset = useCallback(() => {
5776
+ manager.reset();
5777
+ }, [manager]);
5778
+ return {
5779
+ registerUnit,
5780
+ unregisterUnit,
5781
+ getPosition,
5782
+ getState,
5783
+ applyForce,
5784
+ setVelocity,
5785
+ setPosition,
5786
+ tick,
5787
+ checkCollision,
5788
+ resolveCollision,
5789
+ setFrozen,
5790
+ getAllUnits,
5791
+ reset
5792
+ };
7459
5793
  }
7460
5794
  function BattleBoard({
7461
5795
  entity,
@@ -8316,7 +6650,7 @@ function LinearView({
8316
6650
  /* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
8317
6651
  const isDone = i < currentIdx;
8318
6652
  const isCurrent = i === currentIdx;
8319
- return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
6653
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
8320
6654
  i > 0 && /* @__PURE__ */ jsx(
8321
6655
  Typography,
8322
6656
  {
@@ -8970,7 +7304,7 @@ function SequenceBar({
8970
7304
  onSlotRemove(index);
8971
7305
  }, [onSlotRemove, playing]);
8972
7306
  const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
8973
- return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
7307
+ return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
8974
7308
  i > 0 && /* @__PURE__ */ jsx(
8975
7309
  Typography,
8976
7310
  {
@@ -10841,20 +9175,27 @@ function SimulationCanvas({
10841
9175
  bodiesRef.current = structuredClone(preset.bodies);
10842
9176
  }, [preset]);
10843
9177
  const step = useCallback(() => {
10844
- const dt = 1 / 60 * speed;
9178
+ const dt = Math.min(1 / 60 * speed, 1 / 15);
10845
9179
  const gx = preset.gravity?.x ?? 0;
10846
9180
  const gy = preset.gravity?.y ?? 9.81;
10847
9181
  const bodies = bodiesRef.current;
9182
+ const maxVel = Math.max(width, height) * 5;
10848
9183
  for (const body of bodies) {
10849
9184
  if (body.fixed) continue;
10850
9185
  body.vx += gx * dt;
10851
9186
  body.vy += gy * dt;
9187
+ body.vx = Math.max(-maxVel, Math.min(maxVel, body.vx));
9188
+ body.vy = Math.max(-maxVel, Math.min(maxVel, body.vy));
10852
9189
  body.x += body.vx * dt;
10853
9190
  body.y += body.vy * dt;
10854
9191
  if (body.y + body.radius > height) {
10855
9192
  body.y = height - body.radius;
10856
9193
  body.vy = -body.vy * 0.8;
10857
9194
  }
9195
+ if (body.y - body.radius < 0) {
9196
+ body.y = body.radius;
9197
+ body.vy = -body.vy * 0.8;
9198
+ }
10858
9199
  if (body.x + body.radius > width) {
10859
9200
  body.x = width - body.radius;
10860
9201
  body.vx = -body.vx * 0.8;
@@ -10940,7 +9281,24 @@ function SimulationCanvas({
10940
9281
  useEffect(() => {
10941
9282
  draw();
10942
9283
  }, [draw]);
10943
- return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded-md" }) });
9284
+ useEffect(() => {
9285
+ if (typeof window === "undefined") return;
9286
+ const canvas = canvasRef.current;
9287
+ if (!canvas) return;
9288
+ bindCanvasCapture(() => canvas.toDataURL("image/png"));
9289
+ return () => {
9290
+ bindCanvasCapture(() => null);
9291
+ };
9292
+ }, []);
9293
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex justify-center", className), children: /* @__PURE__ */ jsx(
9294
+ "canvas",
9295
+ {
9296
+ ref: canvasRef,
9297
+ width,
9298
+ height,
9299
+ className: "rounded-md block max-w-full h-auto"
9300
+ }
9301
+ ) });
10944
9302
  }
10945
9303
  SimulationCanvas.displayName = "SimulationCanvas";
10946
9304
  function SimulationControls({
@@ -11060,84 +9418,6 @@ function SimulationGraph({
11060
9418
  ] }) });
11061
9419
  }
11062
9420
  SimulationGraph.displayName = "SimulationGraph";
11063
- var eventIcons = {
11064
- attack: Sword,
11065
- defend: Shield,
11066
- heal: Heart,
11067
- move: Move,
11068
- special: Zap,
11069
- death: Sword,
11070
- spawn: Zap
11071
- };
11072
- var eventColors = {
11073
- attack: "text-error",
11074
- defend: "text-info",
11075
- heal: "text-success",
11076
- move: "text-primary",
11077
- special: "text-warning",
11078
- death: "text-muted-foreground",
11079
- spawn: "text-accent"
11080
- };
11081
- var eventBadgeVariants = {
11082
- attack: "danger",
11083
- defend: "primary",
11084
- heal: "success",
11085
- move: "warning",
11086
- special: "secondary",
11087
- death: "secondary",
11088
- spawn: "secondary"
11089
- };
11090
- function CombatLog({
11091
- events,
11092
- maxVisible = 50,
11093
- autoScroll = true,
11094
- showTimestamps = false,
11095
- className,
11096
- title = "Combat Log"
11097
- }) {
11098
- const scrollRef = useRef(null);
11099
- useEffect(() => {
11100
- if (autoScroll && scrollRef.current) {
11101
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
11102
- }
11103
- }, [events, autoScroll]);
11104
- const visibleEvents = events.slice(-maxVisible);
11105
- return /* @__PURE__ */ jsxs(Card, { variant: "default", className: cn("flex flex-col", className), children: [
11106
- /* @__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: [
11107
- /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-bold", children: title }),
11108
- /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
11109
- events.length,
11110
- " events"
11111
- ] })
11112
- ] }) }),
11113
- /* @__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) => {
11114
- const EventIcon = eventIcons[event.type];
11115
- const colorClass = eventColors[event.type];
11116
- return /* @__PURE__ */ jsxs(
11117
- Box,
11118
- {
11119
- display: "flex",
11120
- padding: "xs",
11121
- rounded: "sm",
11122
- className: cn("items-start gap-2 hover:bg-[var(--color-muted)] transition-colors", event.type === "death" && "opacity-60"),
11123
- children: [
11124
- /* @__PURE__ */ jsx(Box, { className: cn("flex-shrink-0 mt-0.5", colorClass), children: /* @__PURE__ */ jsx(EventIcon, { className: "h-4 w-4" }) }),
11125
- /* @__PURE__ */ jsxs(Box, { className: "flex-1 min-w-0", children: [
11126
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "block", children: event.message }),
11127
- event.value !== void 0 && /* @__PURE__ */ jsxs(Badge, { variant: eventBadgeVariants[event.type], size: "sm", className: "mt-1", children: [
11128
- event.type === "heal" ? "+" : event.type === "attack" ? "-" : "",
11129
- event.value
11130
- ] })
11131
- ] }),
11132
- (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}` : "" }) })
11133
- ]
11134
- },
11135
- event.id
11136
- );
11137
- }) }) })
11138
- ] });
11139
- }
11140
- CombatLog.displayName = "CombatLog";
11141
9421
 
11142
9422
  // components/organisms/game/types/game.ts
11143
9423
  function createInitialGameState(width, height, units, defaultTerrain = "floorStone") {
@@ -11278,7 +9558,7 @@ function generateCombatMessage(event) {
11278
9558
  return event.message;
11279
9559
  }
11280
9560
  function extractTitle(children) {
11281
- if (!React__default.isValidElement(children)) return void 0;
9561
+ if (!React.isValidElement(children)) return void 0;
11282
9562
  const props = children.props;
11283
9563
  if (typeof props.title === "string") {
11284
9564
  return props.title;
@@ -11313,7 +9593,7 @@ var ModalSlot = ({
11313
9593
  };
11314
9594
  ModalSlot.displayName = "ModalSlot";
11315
9595
  function extractTitle2(children) {
11316
- if (!React__default.isValidElement(children)) return void 0;
9596
+ if (!React.isValidElement(children)) return void 0;
11317
9597
  const props = children.props;
11318
9598
  if (typeof props.title === "string") {
11319
9599
  return props.title;
@@ -11350,7 +9630,7 @@ var DrawerSlot = ({
11350
9630
  };
11351
9631
  DrawerSlot.displayName = "DrawerSlot";
11352
9632
  function extractToastProps(children) {
11353
- if (!React__default.isValidElement(children)) {
9633
+ if (!React.isValidElement(children)) {
11354
9634
  if (typeof children === "string") {
11355
9635
  return { message: children };
11356
9636
  }
@@ -11381,7 +9661,7 @@ var ToastSlot = ({
11381
9661
  eventBus.emit("UI:CLOSE");
11382
9662
  };
11383
9663
  if (!isVisible) return null;
11384
- const isCustomContent = React__default.isValidElement(children) && !message;
9664
+ const isCustomContent = React.isValidElement(children) && !message;
11385
9665
  return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
11386
9666
  Toast,
11387
9667
  {
@@ -11698,209 +9978,6 @@ var Chart = ({
11698
9978
  ] }) });
11699
9979
  };
11700
9980
  Chart.displayName = "Chart";
11701
- var DEFAULT_THRESHOLDS = [
11702
- { value: 30, color: "var(--color-error)" },
11703
- { value: 70, color: "var(--color-warning)" },
11704
- { value: 100, color: "var(--color-success)" }
11705
- ];
11706
- function getColorForValue(value, max, thresholds) {
11707
- const percentage = value / max * 100;
11708
- for (const threshold of thresholds) {
11709
- if (percentage <= threshold.value) {
11710
- return threshold.color;
11711
- }
11712
- }
11713
- return thresholds[thresholds.length - 1]?.color ?? "var(--color-primary)";
11714
- }
11715
- var radialSizes = {
11716
- sm: { size: 80, stroke: 6, fontSize: "12px" },
11717
- md: { size: 120, stroke: 8, fontSize: "16px" },
11718
- lg: { size: 160, stroke: 10, fontSize: "20px" }
11719
- };
11720
- var Meter = ({
11721
- value,
11722
- min = 0,
11723
- max = 100,
11724
- label,
11725
- unit,
11726
- variant = "linear",
11727
- thresholds = DEFAULT_THRESHOLDS,
11728
- segments = 5,
11729
- showValue = true,
11730
- size = "md",
11731
- actions,
11732
- entity,
11733
- isLoading = false,
11734
- error,
11735
- className
11736
- }) => {
11737
- const eventBus = useEventBus();
11738
- const handleAction = useCallback(
11739
- (action) => {
11740
- if (action.event) {
11741
- eventBus.emit(`UI:${action.event}`, { value });
11742
- }
11743
- },
11744
- [eventBus, value]
11745
- );
11746
- const percentage = useMemo(() => {
11747
- const range = max - min;
11748
- if (range <= 0) return 0;
11749
- return Math.min(Math.max((value - min) / range * 100, 0), 100);
11750
- }, [value, min, max]);
11751
- const activeColor = useMemo(
11752
- () => getColorForValue(value, max, thresholds),
11753
- [value, max, thresholds]
11754
- );
11755
- const displayValue = useMemo(() => {
11756
- const formatted = Number.isInteger(value) ? value : value.toFixed(1);
11757
- return unit ? `${formatted}${unit}` : `${formatted}`;
11758
- }, [value, unit]);
11759
- if (isLoading) {
11760
- return /* @__PURE__ */ jsx(LoadingState, { message: "Loading meter...", className });
11761
- }
11762
- if (error) {
11763
- return /* @__PURE__ */ jsx(
11764
- ErrorState,
11765
- {
11766
- title: "Meter error",
11767
- message: error.message,
11768
- className
11769
- }
11770
- );
11771
- }
11772
- if (variant === "radial") {
11773
- const dims = radialSizes[size];
11774
- const radius = (dims.size - dims.stroke * 2) / 2;
11775
- const circumference = 2 * Math.PI * radius;
11776
- const offset = circumference - percentage / 100 * circumference;
11777
- const center = dims.size / 2;
11778
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
11779
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11780
- /* @__PURE__ */ jsxs(Box, { className: "relative inline-flex items-center justify-center", children: [
11781
- /* @__PURE__ */ jsxs(
11782
- "svg",
11783
- {
11784
- width: dims.size,
11785
- height: dims.size,
11786
- viewBox: `0 0 ${dims.size} ${dims.size}`,
11787
- className: "transform -rotate-90",
11788
- children: [
11789
- /* @__PURE__ */ jsx(
11790
- "circle",
11791
- {
11792
- cx: center,
11793
- cy: center,
11794
- r: radius,
11795
- fill: "none",
11796
- stroke: "var(--color-muted)",
11797
- strokeWidth: dims.stroke
11798
- }
11799
- ),
11800
- /* @__PURE__ */ jsx(
11801
- "circle",
11802
- {
11803
- cx: center,
11804
- cy: center,
11805
- r: radius,
11806
- fill: "none",
11807
- stroke: activeColor,
11808
- strokeWidth: dims.stroke,
11809
- strokeDasharray: circumference,
11810
- strokeDashoffset: offset,
11811
- strokeLinecap: "round",
11812
- className: "transition-all duration-500 ease-out"
11813
- }
11814
- )
11815
- ]
11816
- }
11817
- ),
11818
- showValue && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(
11819
- Typography,
11820
- {
11821
- variant: "h5",
11822
- weight: "bold",
11823
- className: "tabular-nums",
11824
- style: { fontSize: dims.fontSize },
11825
- children: displayValue
11826
- }
11827
- ) })
11828
- ] }),
11829
- actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
11830
- Badge,
11831
- {
11832
- variant: "default",
11833
- className: "cursor-pointer hover:opacity-80 transition-opacity",
11834
- onClick: () => handleAction(action),
11835
- children: action.label
11836
- },
11837
- idx
11838
- )) })
11839
- ] }) });
11840
- }
11841
- if (variant === "segmented") {
11842
- const activeSegments = Math.round(percentage / 100 * segments);
11843
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
11844
- (label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
11845
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11846
- showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
11847
- ] }),
11848
- /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "w-full", children: Array.from({ length: segments }).map((_, idx) => {
11849
- const isActive = idx < activeSegments;
11850
- const segColor = isActive ? getColorForValue((idx + 1) / segments * max, max, thresholds) : void 0;
11851
- return /* @__PURE__ */ jsx(
11852
- Box,
11853
- {
11854
- className: cn(
11855
- "h-2 flex-1 rounded-[var(--radius-sm)] transition-all duration-300",
11856
- !isActive && "bg-[var(--color-muted)]"
11857
- ),
11858
- style: isActive ? { backgroundColor: segColor } : void 0
11859
- },
11860
- idx
11861
- );
11862
- }) }),
11863
- thresholds.some((t) => t.label) && /* @__PURE__ */ jsx(HStack, { justify: "between", className: "w-full", children: thresholds.map((t, idx) => /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: t.label || "" }, idx)) })
11864
- ] }) });
11865
- }
11866
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
11867
- (label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
11868
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11869
- showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
11870
- ] }),
11871
- /* @__PURE__ */ jsx(Box, { className: "w-full h-3 bg-[var(--color-muted)] rounded-[var(--radius-full)] overflow-hidden", children: /* @__PURE__ */ jsx(
11872
- Box,
11873
- {
11874
- className: "h-full rounded-[var(--radius-full)] transition-all duration-500 ease-out",
11875
- style: {
11876
- width: `${percentage}%`,
11877
- backgroundColor: activeColor
11878
- }
11879
- }
11880
- ) }),
11881
- /* @__PURE__ */ jsxs(HStack, { justify: "between", className: "w-full", children: [
11882
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
11883
- min,
11884
- unit ? ` ${unit}` : ""
11885
- ] }),
11886
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
11887
- max,
11888
- unit ? ` ${unit}` : ""
11889
- ] })
11890
- ] }),
11891
- actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
11892
- Badge,
11893
- {
11894
- variant: "default",
11895
- className: "cursor-pointer hover:opacity-80 transition-opacity",
11896
- onClick: () => handleAction(action),
11897
- children: action.label
11898
- },
11899
- idx
11900
- )) })
11901
- ] }) });
11902
- };
11903
- Meter.displayName = "Meter";
11904
9981
  var STATUS_STYLES3 = {
11905
9982
  complete: {
11906
9983
  dotColor: "text-[var(--color-success)]",
@@ -11935,7 +10012,7 @@ var Timeline = ({
11935
10012
  }) => {
11936
10013
  const { t } = useTranslate();
11937
10014
  const entityData = Array.isArray(entity) ? entity : [];
11938
- const items = React__default.useMemo(() => {
10015
+ const items = React.useMemo(() => {
11939
10016
  if (propItems) return propItems;
11940
10017
  if (entityData.length === 0) return [];
11941
10018
  return entityData.map((record, idx) => {
@@ -12076,7 +10153,7 @@ var MediaGallery = ({
12076
10153
  [selectable, selectedItems, selectionEvent, eventBus]
12077
10154
  );
12078
10155
  const entityData = Array.isArray(entity) ? entity : [];
12079
- const items = React__default.useMemo(() => {
10156
+ const items = React.useMemo(() => {
12080
10157
  if (propItems) return propItems;
12081
10158
  if (entityData.length === 0) return [];
12082
10159
  return entityData.map((record, idx) => ({
@@ -12647,7 +10724,7 @@ var GraphCanvas = ({
12647
10724
  const handleNodeClick = useCallback(
12648
10725
  (node) => {
12649
10726
  if (nodeClickEvent) {
12650
- eventBus.emit(`UI:${nodeClickEvent}`, { row: node });
10727
+ eventBus.emit(`UI:${nodeClickEvent}`, { id: node.id, row: node });
12651
10728
  }
12652
10729
  onNodeClick?.(node);
12653
10730
  },
@@ -14064,4 +12141,4 @@ function WorldMapTemplate({
14064
12141
  }
14065
12142
  WorldMapTemplate.displayName = "WorldMapTemplate";
14066
12143
 
14067
- 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, NotifyListener, ObjectRulePanel, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, PhysicsManager, PlatformerCanvas, 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 };
12144
+ 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, ConfirmDialog, ContentRenderer, CounterTemplate, DashboardGrid, DashboardLayout, DebuggerBoard, DocumentViewer, StateMachineView as DomStateMachineVisualizer, DrawerSlot, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EventHandlerBoard, EventLog, FEATURE_TYPES, FormActions, FormLayout, FormSection, GameAudioContext, GameAudioProvider, GameAudioToggle, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Header, IDENTITY_BOOK_FIELDS, JazariStateMachine, List, MediaGallery, ModalSlot, Navigation, NegotiatorBoard, NotifyListener, 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, 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, mapBookData, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, springOscillator, tickAnimationState, transitionAnimation, useBattleState, useGameAudio, useGameAudioContext, usePhysics2D, useSpriteAnimations };