@almadar/ui 2.5.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,20 @@
1
- import { useAuthContext } from '../chunk-2UMN2BLO.js';
2
- export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-2UMN2BLO.js';
1
+ import { useAuthContext } from '../chunk-2QM732NQ.js';
2
+ export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, 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
- export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
6
- import { VStack, HStack, Typography, Button, Icon, Box, Card, Avatar, Badge, SearchInput, Checkbox, Menu as Menu$1, Pagination, LoadingState, EmptyState, Modal, ErrorState, QuizBlock, CodeBlock, ScaledDiagram, MarkdownContent, Divider, ProgressBar, Stack, StatBadge, Select, Drawer, Toast, Tabs, Input, ThemeToggle, EntityDisplayEvents, StateIndicator, Container } from '../chunk-AL6BLHMG.js';
7
- export { Accordion, ActionButtons, Card2 as ActionCard, Alert, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Center, ChartLegend, Checkbox, CodeBlock, ConditionalWrapper, 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, Input, InputGroup, Label, LawReferenceTooltip, LineChart, LoadingState, MapView, MarkdownContent, MasterDetail, Menu, Modal, Overlay, PageHeader, Pagination, Popover, ProgressBar, ProgressDots, QuizBlock, Radio, RelationSelect, RepeatableFormSection, ScaledDiagram, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, Spacer, Spinner, Sprite, Stack, StatBadge, StatCard, StateIndicator, Switch, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Toast, Tooltip, Typography, UISlotComponent, UISlotRenderer, VStack, ViolationAlert, WizardNavigation, WizardProgress, drawSprite } from '../chunk-AL6BLHMG.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-VJP2HCLY.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, Meter, 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, StatDisplay, 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-VJP2HCLY.js';
8
7
  import '../chunk-DKQN5FVU.js';
9
- import { useTranslate } from '../chunk-GOZKH7QW.js';
10
- export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useQuerySingleton, useTranslate } from '../chunk-GOZKH7QW.js';
8
+ import { useTranslate } from '../chunk-WGJIL4YR.js';
9
+ export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useDragReorder, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useInfiniteScroll, useLongPress, usePullToRefresh, useQuerySingleton, useSwipeGesture, useTranslate } from '../chunk-WGJIL4YR.js';
11
10
  import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
12
11
  export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
13
12
  export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-3JGAROCW.js';
13
+ import { cn, getNestedValue, updateAssetStatus, bindCanvasCapture } from '../chunk-RPYMP7ZC.js';
14
+ export { cn } from '../chunk-RPYMP7ZC.js';
14
15
  import '../chunk-TSETXL2E.js';
15
- import { cn, getNestedValue } from '../chunk-KKCVDUK7.js';
16
- export { cn } from '../chunk-KKCVDUK7.js';
17
16
  import '../chunk-K2D5D3WK.js';
17
+ export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
18
18
  import { __publicField } from '../chunk-PKBMQBKP.js';
19
19
  import * as React from 'react';
20
20
  import React__default, { createContext, useState, useCallback, useMemo, useEffect, useRef, useContext } from 'react';
@@ -3766,12 +3766,15 @@ function useImageCache(urls) {
3766
3766
  cache.set(url, img);
3767
3767
  loading.delete(url);
3768
3768
  setPendingCount((prev) => Math.max(0, prev - 1));
3769
+ updateAssetStatus(url, "loaded");
3769
3770
  };
3770
3771
  img.onerror = () => {
3771
3772
  if (cancelled) return;
3772
3773
  loading.delete(url);
3773
3774
  setPendingCount((prev) => Math.max(0, prev - 1));
3775
+ updateAssetStatus(url, "failed");
3774
3776
  };
3777
+ updateAssetStatus(url, "pending");
3775
3778
  img.src = url;
3776
3779
  }
3777
3780
  return () => {
@@ -4040,6 +4043,15 @@ function IsometricCanvas({
4040
4043
  return [...new Set(urls.filter(Boolean))];
4041
4044
  }, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
4042
4045
  const { getImage } = useImageCache(spriteUrls);
4046
+ useEffect(() => {
4047
+ if (typeof window === "undefined") return;
4048
+ const canvas = canvasRef.current;
4049
+ if (!canvas) return;
4050
+ bindCanvasCapture(() => canvas.toDataURL("image/png"));
4051
+ return () => {
4052
+ bindCanvasCapture(() => null);
4053
+ };
4054
+ }, []);
4043
4055
  const {
4044
4056
  cameraRef,
4045
4057
  targetCameraRef,
@@ -4621,6 +4633,10 @@ function PlatformerCanvas({
4621
4633
  canvasHeight = 400,
4622
4634
  followCamera = true,
4623
4635
  bgColor,
4636
+ playerSprite,
4637
+ tileSprites,
4638
+ backgroundImage,
4639
+ assetBaseUrl = "",
4624
4640
  leftEvent = "MOVE_LEFT",
4625
4641
  rightEvent = "MOVE_RIGHT",
4626
4642
  jumpEvent = "JUMP",
@@ -4631,6 +4647,35 @@ function PlatformerCanvas({
4631
4647
  const canvasRef = useRef(null);
4632
4648
  const eventBus = useEventBus();
4633
4649
  const keysRef = useRef(/* @__PURE__ */ new Set());
4650
+ const imageCache = useRef(/* @__PURE__ */ new Map());
4651
+ const loadImage = useCallback((url) => {
4652
+ const fullUrl = url.startsWith("http") ? url : `${assetBaseUrl}${url}`;
4653
+ const cached = imageCache.current.get(fullUrl);
4654
+ if (cached?.complete && cached.naturalWidth > 0) return cached;
4655
+ if (!cached) {
4656
+ const img = new Image();
4657
+ img.crossOrigin = "anonymous";
4658
+ img.src = fullUrl;
4659
+ img.onload = () => {
4660
+ updateAssetStatus(fullUrl, "loaded");
4661
+ };
4662
+ img.onerror = () => {
4663
+ updateAssetStatus(fullUrl, "failed");
4664
+ };
4665
+ imageCache.current.set(fullUrl, img);
4666
+ updateAssetStatus(fullUrl, "pending");
4667
+ }
4668
+ return null;
4669
+ }, [assetBaseUrl]);
4670
+ useEffect(() => {
4671
+ if (typeof window === "undefined") return;
4672
+ const canvas = canvasRef.current;
4673
+ if (!canvas) return;
4674
+ bindCanvasCapture(() => canvas.toDataURL("image/png"));
4675
+ return () => {
4676
+ bindCanvasCapture(() => null);
4677
+ };
4678
+ }, []);
4634
4679
  const resolvedPlayer = player ?? {
4635
4680
  x: entity?.x ?? 50,
4636
4681
  y: entity?.y ?? 300,
@@ -4695,7 +4740,10 @@ function PlatformerCanvas({
4695
4740
  camX = Math.max(0, Math.min(resolvedPlayer.x - canvasWidth / 2, worldWidth - canvasWidth));
4696
4741
  camY = Math.max(0, Math.min(resolvedPlayer.y - canvasHeight / 2 - 50, worldHeight - canvasHeight));
4697
4742
  }
4698
- if (bgColor) {
4743
+ const bgImg = backgroundImage ? loadImage(backgroundImage) : null;
4744
+ if (bgImg) {
4745
+ ctx.drawImage(bgImg, 0, 0, canvasWidth, canvasHeight);
4746
+ } else if (bgColor) {
4699
4747
  ctx.fillStyle = bgColor;
4700
4748
  ctx.fillRect(0, 0, canvasWidth, canvasHeight);
4701
4749
  } else {
@@ -4723,58 +4771,86 @@ function PlatformerCanvas({
4723
4771
  for (const plat of platforms) {
4724
4772
  const px = plat.x - camX;
4725
4773
  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) {
4774
+ const platType = plat.type ?? "ground";
4775
+ const spriteUrl = tileSprites?.[platType];
4776
+ const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
4777
+ if (tileImg) {
4778
+ const tileW = tileImg.naturalWidth;
4779
+ const tileH = tileImg.naturalHeight;
4780
+ const scaleH = plat.height / tileH;
4781
+ const scaledW = tileW * scaleH;
4782
+ for (let tx = 0; tx < plat.width; tx += scaledW) {
4783
+ const drawW = Math.min(scaledW, plat.width - tx);
4784
+ const srcW = drawW / scaleH;
4785
+ ctx.drawImage(tileImg, 0, 0, srcW, tileH, px + tx, py, drawW, plat.height);
4786
+ }
4787
+ } else {
4788
+ const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
4789
+ ctx.fillStyle = color;
4790
+ ctx.fillRect(px, py, plat.width, plat.height);
4791
+ ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
4792
+ ctx.fillRect(px, py, plat.width, 3);
4793
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
4794
+ ctx.fillRect(px, py + plat.height - 2, plat.width, 2);
4795
+ if (platType === "hazard") {
4796
+ ctx.strokeStyle = "#e74c3c";
4797
+ ctx.lineWidth = 2;
4798
+ for (let sx = px; sx < px + plat.width; sx += 12) {
4799
+ ctx.beginPath();
4800
+ ctx.moveTo(sx, py);
4801
+ ctx.lineTo(sx + 6, py + plat.height);
4802
+ ctx.stroke();
4803
+ }
4804
+ }
4805
+ if (platType === "goal") {
4806
+ ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
4737
4807
  ctx.beginPath();
4738
- ctx.moveTo(sx, py);
4739
- ctx.lineTo(sx + 6, py + plat.height);
4740
- ctx.stroke();
4808
+ ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
4809
+ ctx.fill();
4741
4810
  }
4742
4811
  }
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
4812
  }
4750
4813
  const pw = resolvedPlayer.width ?? 24;
4751
4814
  const ph = resolvedPlayer.height ?? 32;
4752
4815
  const ppx = resolvedPlayer.x - camX;
4753
4816
  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
4817
  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();
4818
+ const playerImg = playerSprite ? loadImage(playerSprite) : null;
4819
+ if (playerImg) {
4820
+ ctx.save();
4821
+ if (!facingRight) {
4822
+ ctx.translate(ppx + pw, ppy);
4823
+ ctx.scale(-1, 1);
4824
+ ctx.drawImage(playerImg, 0, 0, pw, ph);
4825
+ } else {
4826
+ ctx.drawImage(playerImg, ppx, ppy, pw, ph);
4827
+ }
4828
+ ctx.restore();
4829
+ } else {
4830
+ ctx.fillStyle = PLAYER_COLOR;
4831
+ const radius = Math.min(pw, ph) * 0.25;
4832
+ ctx.beginPath();
4833
+ ctx.moveTo(ppx + radius, ppy);
4834
+ ctx.lineTo(ppx + pw - radius, ppy);
4835
+ ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
4836
+ ctx.lineTo(ppx + pw, ppy + ph - radius);
4837
+ ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
4838
+ ctx.lineTo(ppx + radius, ppy + ph);
4839
+ ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
4840
+ ctx.lineTo(ppx, ppy + radius);
4841
+ ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
4842
+ ctx.fill();
4843
+ const eyeY = ppy + ph * 0.3;
4844
+ const eyeSize = 3;
4845
+ const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
4846
+ ctx.fillStyle = PLAYER_EYE_COLOR;
4847
+ ctx.beginPath();
4848
+ ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
4849
+ ctx.fill();
4850
+ ctx.beginPath();
4851
+ ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
4852
+ ctx.fill();
4853
+ }
4778
4854
  });
4779
4855
  return /* @__PURE__ */ jsx(
4780
4856
  "canvas",
@@ -10759,34 +10835,109 @@ function NegotiatorBoard({
10759
10835
  );
10760
10836
  }
10761
10837
  NegotiatorBoard.displayName = "NegotiatorBoard";
10838
+
10839
+ // components/organisms/game/physics-sim/presets/mechanics.ts
10840
+ var projectileMotion = {
10841
+ id: "mechanics-projectile",
10842
+ name: "Projectile Motion",
10843
+ description: "Launch a ball and observe parabolic trajectory under gravity.",
10844
+ domain: "natural",
10845
+ gravity: { x: 0, y: 9.81 },
10846
+ bodies: [
10847
+ { id: "ball", x: 50, y: 350, vx: 80, vy: -120, mass: 1, radius: 10, color: "#e94560", fixed: false },
10848
+ { id: "ground", x: 300, y: 390, vx: 0, vy: 0, mass: 1e3, radius: 400, color: "#333", fixed: true }
10849
+ ],
10850
+ showVelocity: true,
10851
+ parameters: {
10852
+ angle: { value: 45, min: 0, max: 90, step: 1, label: "Launch angle (deg)" },
10853
+ velocity: { value: 100, min: 10, max: 200, step: 5, label: "Initial velocity (m/s)" },
10854
+ gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
10855
+ }
10856
+ };
10857
+ var pendulum = {
10858
+ id: "mechanics-pendulum",
10859
+ name: "Simple Pendulum",
10860
+ description: "A mass on a string swinging under gravity.",
10861
+ domain: "natural",
10862
+ gravity: { x: 0, y: 9.81 },
10863
+ bodies: [
10864
+ { id: "pivot", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 5, color: "#888", fixed: true },
10865
+ { id: "bob", x: 400, y: 200, vx: 0, vy: 0, mass: 5, radius: 15, color: "#e94560", fixed: false }
10866
+ ],
10867
+ constraints: [{ bodyA: 0, bodyB: 1, length: 180, stiffness: 1 }],
10868
+ parameters: {
10869
+ length: { value: 180, min: 50, max: 300, step: 10, label: "String length (px)" },
10870
+ mass: { value: 5, min: 1, max: 20, step: 0.5, label: "Mass (kg)" },
10871
+ gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
10872
+ }
10873
+ };
10874
+ var springOscillator = {
10875
+ id: "mechanics-spring",
10876
+ name: "Spring Oscillator",
10877
+ description: "A mass bouncing on a spring \u2014 simple harmonic motion.",
10878
+ domain: "natural",
10879
+ gravity: { x: 0, y: 0 },
10880
+ bodies: [
10881
+ { id: "anchor", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 8, color: "#888", fixed: true },
10882
+ { id: "mass", x: 300, y: 250, vx: 0, vy: 0, mass: 2, radius: 20, color: "#0f3460", fixed: false }
10883
+ ],
10884
+ constraints: [{ bodyA: 0, bodyB: 1, length: 150, stiffness: 0.5 }],
10885
+ parameters: {
10886
+ stiffness: { value: 0.5, min: 0.1, max: 2, step: 0.05, label: "Spring stiffness (k)" },
10887
+ mass: { value: 2, min: 0.5, max: 10, step: 0.5, label: "Mass (kg)" },
10888
+ damping: { value: 0, min: 0, max: 0.5, step: 0.01, label: "Damping" }
10889
+ }
10890
+ };
10891
+
10892
+ // components/organisms/game/physics-sim/presets/index.ts
10893
+ var ALL_PRESETS = [
10894
+ projectileMotion,
10895
+ pendulum,
10896
+ springOscillator
10897
+ ];
10898
+ function resolvePreset(preset) {
10899
+ if (typeof preset !== "string") return preset;
10900
+ const needle = preset.toLowerCase();
10901
+ return ALL_PRESETS.find(
10902
+ (p2) => p2.id === preset || p2.id.includes(needle) || p2.name.toLowerCase().includes(needle)
10903
+ ) ?? projectileMotion;
10904
+ }
10762
10905
  function SimulationCanvas({
10763
- preset,
10906
+ preset: presetProp,
10764
10907
  width = 600,
10765
10908
  height = 400,
10766
10909
  running,
10767
10910
  speed = 1,
10768
10911
  className
10769
10912
  }) {
10913
+ const preset = useMemo(() => resolvePreset(presetProp), [presetProp]);
10770
10914
  const canvasRef = useRef(null);
10771
10915
  const bodiesRef = useRef(structuredClone(preset.bodies));
10772
10916
  useEffect(() => {
10773
10917
  bodiesRef.current = structuredClone(preset.bodies);
10774
10918
  }, [preset]);
10775
10919
  const step = useCallback(() => {
10776
- const dt = 1 / 60 * speed;
10920
+ const dt = Math.min(1 / 60 * speed, 1 / 15);
10777
10921
  const gx = preset.gravity?.x ?? 0;
10778
10922
  const gy = preset.gravity?.y ?? 9.81;
10779
10923
  const bodies = bodiesRef.current;
10924
+ const maxVel = Math.max(width, height) * 5;
10780
10925
  for (const body of bodies) {
10781
10926
  if (body.fixed) continue;
10782
10927
  body.vx += gx * dt;
10783
10928
  body.vy += gy * dt;
10929
+ body.vx = Math.max(-maxVel, Math.min(maxVel, body.vx));
10930
+ body.vy = Math.max(-maxVel, Math.min(maxVel, body.vy));
10784
10931
  body.x += body.vx * dt;
10785
10932
  body.y += body.vy * dt;
10786
10933
  if (body.y + body.radius > height) {
10787
10934
  body.y = height - body.radius;
10788
10935
  body.vy = -body.vy * 0.8;
10789
10936
  }
10937
+ if (body.y - body.radius < 0) {
10938
+ body.y = body.radius;
10939
+ body.vy = -body.vy * 0.8;
10940
+ }
10790
10941
  if (body.x + body.radius > width) {
10791
10942
  body.x = width - body.radius;
10792
10943
  body.vx = -body.vx * 0.8;
@@ -10872,7 +11023,24 @@ function SimulationCanvas({
10872
11023
  useEffect(() => {
10873
11024
  draw();
10874
11025
  }, [draw]);
10875
- return /* @__PURE__ */ jsx(Box, { className, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, width, height, className: "rounded-md" }) });
11026
+ useEffect(() => {
11027
+ if (typeof window === "undefined") return;
11028
+ const canvas = canvasRef.current;
11029
+ if (!canvas) return;
11030
+ bindCanvasCapture(() => canvas.toDataURL("image/png"));
11031
+ return () => {
11032
+ bindCanvasCapture(() => null);
11033
+ };
11034
+ }, []);
11035
+ return /* @__PURE__ */ jsx(Box, { className: cn("flex justify-center", className), children: /* @__PURE__ */ jsx(
11036
+ "canvas",
11037
+ {
11038
+ ref: canvasRef,
11039
+ width,
11040
+ height,
11041
+ className: "rounded-md block max-w-full h-auto"
11042
+ }
11043
+ ) });
10876
11044
  }
10877
11045
  SimulationCanvas.displayName = "SimulationCanvas";
10878
11046
  function SimulationControls({
@@ -10992,66 +11160,6 @@ function SimulationGraph({
10992
11160
  ] }) });
10993
11161
  }
10994
11162
  SimulationGraph.displayName = "SimulationGraph";
10995
-
10996
- // components/organisms/game/physics-sim/presets/mechanics.ts
10997
- var projectileMotion = {
10998
- id: "mechanics-projectile",
10999
- name: "Projectile Motion",
11000
- description: "Launch a ball and observe parabolic trajectory under gravity.",
11001
- domain: "natural",
11002
- gravity: { x: 0, y: 9.81 },
11003
- bodies: [
11004
- { id: "ball", x: 50, y: 350, vx: 80, vy: -120, mass: 1, radius: 10, color: "#e94560", fixed: false },
11005
- { id: "ground", x: 300, y: 390, vx: 0, vy: 0, mass: 1e3, radius: 400, color: "#333", fixed: true }
11006
- ],
11007
- showVelocity: true,
11008
- parameters: {
11009
- angle: { value: 45, min: 0, max: 90, step: 1, label: "Launch angle (deg)" },
11010
- velocity: { value: 100, min: 10, max: 200, step: 5, label: "Initial velocity (m/s)" },
11011
- gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
11012
- }
11013
- };
11014
- var pendulum = {
11015
- id: "mechanics-pendulum",
11016
- name: "Simple Pendulum",
11017
- description: "A mass on a string swinging under gravity.",
11018
- domain: "natural",
11019
- gravity: { x: 0, y: 9.81 },
11020
- bodies: [
11021
- { id: "pivot", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 5, color: "#888", fixed: true },
11022
- { id: "bob", x: 400, y: 200, vx: 0, vy: 0, mass: 5, radius: 15, color: "#e94560", fixed: false }
11023
- ],
11024
- constraints: [{ bodyA: 0, bodyB: 1, length: 180, stiffness: 1 }],
11025
- parameters: {
11026
- length: { value: 180, min: 50, max: 300, step: 10, label: "String length (px)" },
11027
- mass: { value: 5, min: 1, max: 20, step: 0.5, label: "Mass (kg)" },
11028
- gravity: { value: 9.81, min: 0, max: 20, step: 0.1, label: "Gravity (m/s\xB2)" }
11029
- }
11030
- };
11031
- var springOscillator = {
11032
- id: "mechanics-spring",
11033
- name: "Spring Oscillator",
11034
- description: "A mass bouncing on a spring \u2014 simple harmonic motion.",
11035
- domain: "natural",
11036
- gravity: { x: 0, y: 0 },
11037
- bodies: [
11038
- { id: "anchor", x: 300, y: 50, vx: 0, vy: 0, mass: 1e3, radius: 8, color: "#888", fixed: true },
11039
- { id: "mass", x: 300, y: 250, vx: 0, vy: 0, mass: 2, radius: 20, color: "#0f3460", fixed: false }
11040
- ],
11041
- constraints: [{ bodyA: 0, bodyB: 1, length: 150, stiffness: 0.5 }],
11042
- parameters: {
11043
- stiffness: { value: 0.5, min: 0.1, max: 2, step: 0.05, label: "Spring stiffness (k)" },
11044
- mass: { value: 2, min: 0.5, max: 10, step: 0.5, label: "Mass (kg)" },
11045
- damping: { value: 0, min: 0, max: 0.5, step: 0.01, label: "Damping" }
11046
- }
11047
- };
11048
-
11049
- // components/organisms/game/physics-sim/presets/index.ts
11050
- var ALL_PRESETS = [
11051
- projectileMotion,
11052
- pendulum,
11053
- springOscillator
11054
- ];
11055
11163
  var eventIcons = {
11056
11164
  attack: Sword,
11057
11165
  defend: Shield,
@@ -11690,209 +11798,6 @@ var Chart = ({
11690
11798
  ] }) });
11691
11799
  };
11692
11800
  Chart.displayName = "Chart";
11693
- var DEFAULT_THRESHOLDS = [
11694
- { value: 30, color: "var(--color-error)" },
11695
- { value: 70, color: "var(--color-warning)" },
11696
- { value: 100, color: "var(--color-success)" }
11697
- ];
11698
- function getColorForValue(value, max, thresholds) {
11699
- const percentage = value / max * 100;
11700
- for (const threshold of thresholds) {
11701
- if (percentage <= threshold.value) {
11702
- return threshold.color;
11703
- }
11704
- }
11705
- return thresholds[thresholds.length - 1]?.color ?? "var(--color-primary)";
11706
- }
11707
- var radialSizes = {
11708
- sm: { size: 80, stroke: 6, fontSize: "12px" },
11709
- md: { size: 120, stroke: 8, fontSize: "16px" },
11710
- lg: { size: 160, stroke: 10, fontSize: "20px" }
11711
- };
11712
- var Meter = ({
11713
- value,
11714
- min = 0,
11715
- max = 100,
11716
- label,
11717
- unit,
11718
- variant = "linear",
11719
- thresholds = DEFAULT_THRESHOLDS,
11720
- segments = 5,
11721
- showValue = true,
11722
- size = "md",
11723
- actions,
11724
- entity,
11725
- isLoading = false,
11726
- error,
11727
- className
11728
- }) => {
11729
- const eventBus = useEventBus();
11730
- const handleAction = useCallback(
11731
- (action) => {
11732
- if (action.event) {
11733
- eventBus.emit(`UI:${action.event}`, { value });
11734
- }
11735
- },
11736
- [eventBus, value]
11737
- );
11738
- const percentage = useMemo(() => {
11739
- const range = max - min;
11740
- if (range <= 0) return 0;
11741
- return Math.min(Math.max((value - min) / range * 100, 0), 100);
11742
- }, [value, min, max]);
11743
- const activeColor = useMemo(
11744
- () => getColorForValue(value, max, thresholds),
11745
- [value, max, thresholds]
11746
- );
11747
- const displayValue = useMemo(() => {
11748
- const formatted = Number.isInteger(value) ? value : value.toFixed(1);
11749
- return unit ? `${formatted}${unit}` : `${formatted}`;
11750
- }, [value, unit]);
11751
- if (isLoading) {
11752
- return /* @__PURE__ */ jsx(LoadingState, { message: "Loading meter...", className });
11753
- }
11754
- if (error) {
11755
- return /* @__PURE__ */ jsx(
11756
- ErrorState,
11757
- {
11758
- title: "Meter error",
11759
- message: error.message,
11760
- className
11761
- }
11762
- );
11763
- }
11764
- if (variant === "radial") {
11765
- const dims = radialSizes[size];
11766
- const radius = (dims.size - dims.stroke * 2) / 2;
11767
- const circumference = 2 * Math.PI * radius;
11768
- const offset = circumference - percentage / 100 * circumference;
11769
- const center = dims.size / 2;
11770
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
11771
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11772
- /* @__PURE__ */ jsxs(Box, { className: "relative inline-flex items-center justify-center", children: [
11773
- /* @__PURE__ */ jsxs(
11774
- "svg",
11775
- {
11776
- width: dims.size,
11777
- height: dims.size,
11778
- viewBox: `0 0 ${dims.size} ${dims.size}`,
11779
- className: "transform -rotate-90",
11780
- children: [
11781
- /* @__PURE__ */ jsx(
11782
- "circle",
11783
- {
11784
- cx: center,
11785
- cy: center,
11786
- r: radius,
11787
- fill: "none",
11788
- stroke: "var(--color-muted)",
11789
- strokeWidth: dims.stroke
11790
- }
11791
- ),
11792
- /* @__PURE__ */ jsx(
11793
- "circle",
11794
- {
11795
- cx: center,
11796
- cy: center,
11797
- r: radius,
11798
- fill: "none",
11799
- stroke: activeColor,
11800
- strokeWidth: dims.stroke,
11801
- strokeDasharray: circumference,
11802
- strokeDashoffset: offset,
11803
- strokeLinecap: "round",
11804
- className: "transition-all duration-500 ease-out"
11805
- }
11806
- )
11807
- ]
11808
- }
11809
- ),
11810
- showValue && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(
11811
- Typography,
11812
- {
11813
- variant: "h5",
11814
- weight: "bold",
11815
- className: "tabular-nums",
11816
- style: { fontSize: dims.fontSize },
11817
- children: displayValue
11818
- }
11819
- ) })
11820
- ] }),
11821
- actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
11822
- Badge,
11823
- {
11824
- variant: "default",
11825
- className: "cursor-pointer hover:opacity-80 transition-opacity",
11826
- onClick: () => handleAction(action),
11827
- children: action.label
11828
- },
11829
- idx
11830
- )) })
11831
- ] }) });
11832
- }
11833
- if (variant === "segmented") {
11834
- const activeSegments = Math.round(percentage / 100 * segments);
11835
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
11836
- (label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
11837
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11838
- showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
11839
- ] }),
11840
- /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "w-full", children: Array.from({ length: segments }).map((_, idx) => {
11841
- const isActive = idx < activeSegments;
11842
- const segColor = isActive ? getColorForValue((idx + 1) / segments * max, max, thresholds) : void 0;
11843
- return /* @__PURE__ */ jsx(
11844
- Box,
11845
- {
11846
- className: cn(
11847
- "h-2 flex-1 rounded-[var(--radius-sm)] transition-all duration-300",
11848
- !isActive && "bg-[var(--color-muted)]"
11849
- ),
11850
- style: isActive ? { backgroundColor: segColor } : void 0
11851
- },
11852
- idx
11853
- );
11854
- }) }),
11855
- 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)) })
11856
- ] }) });
11857
- }
11858
- return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
11859
- (label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
11860
- label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
11861
- showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
11862
- ] }),
11863
- /* @__PURE__ */ jsx(Box, { className: "w-full h-3 bg-[var(--color-muted)] rounded-[var(--radius-full)] overflow-hidden", children: /* @__PURE__ */ jsx(
11864
- Box,
11865
- {
11866
- className: "h-full rounded-[var(--radius-full)] transition-all duration-500 ease-out",
11867
- style: {
11868
- width: `${percentage}%`,
11869
- backgroundColor: activeColor
11870
- }
11871
- }
11872
- ) }),
11873
- /* @__PURE__ */ jsxs(HStack, { justify: "between", className: "w-full", children: [
11874
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
11875
- min,
11876
- unit ? ` ${unit}` : ""
11877
- ] }),
11878
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
11879
- max,
11880
- unit ? ` ${unit}` : ""
11881
- ] })
11882
- ] }),
11883
- actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
11884
- Badge,
11885
- {
11886
- variant: "default",
11887
- className: "cursor-pointer hover:opacity-80 transition-opacity",
11888
- onClick: () => handleAction(action),
11889
- children: action.label
11890
- },
11891
- idx
11892
- )) })
11893
- ] }) });
11894
- };
11895
- Meter.displayName = "Meter";
11896
11801
  var STATUS_STYLES3 = {
11897
11802
  complete: {
11898
11803
  dotColor: "text-[var(--color-success)]",
@@ -12639,7 +12544,7 @@ var GraphCanvas = ({
12639
12544
  const handleNodeClick = useCallback(
12640
12545
  (node) => {
12641
12546
  if (nodeClickEvent) {
12642
- eventBus.emit(`UI:${nodeClickEvent}`, { row: node });
12547
+ eventBus.emit(`UI:${nodeClickEvent}`, { id: node.id, row: node });
12643
12548
  }
12644
12549
  onNodeClick?.(node);
12645
12550
  },
@@ -14056,4 +13961,4 @@ function WorldMapTemplate({
14056
13961
  }
14057
13962
  WorldMapTemplate.displayName = "WorldMapTemplate";
14058
13963
 
14059
- 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 };
13964
+ 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, 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 };