@almadar/ui 2.9.0 → 2.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,16 +3,16 @@ import { useAuthContext } from '../chunk-GTIAVPI5.js';
3
3
  export { ENTITY_EVENTS, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePinchZoom, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-GTIAVPI5.js';
4
4
  export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
5
5
  import '../chunk-3HJHHULT.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, isoToScreen, IsometricCanvas_default, Stack, Select, Drawer, Toast, Tabs, Input, ThemeToggle, TILE_WIDTH, EntityDisplayEvents, StateIndicator, Container } from '../chunk-NES4SBB7.js';
7
- 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';
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, isoToScreen, IsometricCanvas_default, Stack, Select, Drawer, Toast, Tabs, Input, ThemeToggle, TILE_WIDTH, EntityDisplayEvents, StateIndicator, Container } from '../chunk-P6NZVIE5.js';
7
+ export { Accordion, ActionButton, ActionButtons, Card2 as ActionCard, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, CanvasEffect, 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-P6NZVIE5.js';
8
8
  import '../chunk-DKQN5FVU.js';
9
9
  import { useTranslate } from '../chunk-WGJIL4YR.js';
10
10
  export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useDragReorder, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useInfiniteScroll, useLongPress, usePullToRefresh, useQuerySingleton, useSwipeGesture, useTranslate } from '../chunk-WGJIL4YR.js';
11
11
  import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
12
12
  export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
13
13
  export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-3JGAROCW.js';
14
- import { cn, getNestedValue, bindCanvasCapture } from '../chunk-A5J5CNCU.js';
15
- export { cn } from '../chunk-A5J5CNCU.js';
14
+ import { cn, getNestedValue, bindCanvasCapture } from '../chunk-6D5QMEUS.js';
15
+ export { cn } from '../chunk-6D5QMEUS.js';
16
16
  import '../chunk-TSETXL2E.js';
17
17
  import '../chunk-K2D5D3WK.js';
18
18
  import { __publicField } from '../chunk-PKBMQBKP.js';
@@ -3745,1246 +3745,6 @@ var TabbedContainer = ({
3745
3745
  );
3746
3746
  };
3747
3747
  TabbedContainer.displayName = "TabbedContainer";
3748
-
3749
- // components/organisms/game/types/effects.ts
3750
- var EMPTY_EFFECT_STATE = {
3751
- particles: [],
3752
- sequences: [],
3753
- overlays: []
3754
- };
3755
-
3756
- // components/organisms/game/utils/canvasEffects.ts
3757
- var _offscreen = null;
3758
- var _offCtx = null;
3759
- function getOffscreenCtx(w, h) {
3760
- if (!_offscreen) {
3761
- if (typeof OffscreenCanvas !== "undefined") {
3762
- _offscreen = new OffscreenCanvas(w, h);
3763
- } else {
3764
- _offscreen = document.createElement("canvas");
3765
- }
3766
- }
3767
- if (_offscreen.width < w) _offscreen.width = w;
3768
- if (_offscreen.height < h) _offscreen.height = h;
3769
- if (!_offCtx) {
3770
- _offCtx = _offscreen.getContext("2d");
3771
- }
3772
- return _offCtx;
3773
- }
3774
- function drawTintedImage(ctx, img, x, y, w, h, tint, alpha, blendMode = "source-over") {
3775
- if (w <= 0 || h <= 0) return;
3776
- const oc = getOffscreenCtx(w, h);
3777
- oc.clearRect(0, 0, w, h);
3778
- oc.globalCompositeOperation = "source-over";
3779
- oc.drawImage(img, 0, 0, w, h);
3780
- oc.globalCompositeOperation = "source-atop";
3781
- oc.fillStyle = `rgb(${tint.r}, ${tint.g}, ${tint.b})`;
3782
- oc.fillRect(0, 0, w, h);
3783
- const prevAlpha = ctx.globalAlpha;
3784
- const prevBlend = ctx.globalCompositeOperation;
3785
- ctx.globalAlpha = alpha;
3786
- ctx.globalCompositeOperation = blendMode;
3787
- ctx.drawImage(_offscreen, 0, 0, w, h, x, y, w, h);
3788
- ctx.globalAlpha = prevAlpha;
3789
- ctx.globalCompositeOperation = prevBlend;
3790
- }
3791
- function randRange(min, max) {
3792
- return min + Math.random() * (max - min);
3793
- }
3794
- function spawnParticles(config, animTime) {
3795
- const particles = [];
3796
- for (let i = 0; i < config.count; i++) {
3797
- const angle = randRange(config.angleMin, config.angleMax);
3798
- const speed = randRange(config.velocityMin, config.velocityMax);
3799
- const spriteUrl = config.spriteUrls[Math.floor(Math.random() * config.spriteUrls.length)];
3800
- particles.push({
3801
- spriteUrl,
3802
- x: config.originX + randRange(-config.spread, config.spread),
3803
- y: config.originY + randRange(-config.spread, config.spread),
3804
- vx: Math.cos(angle) * speed,
3805
- vy: Math.sin(angle) * speed,
3806
- gravity: config.gravity,
3807
- rotation: Math.random() * Math.PI * 2,
3808
- rotationSpeed: randRange(config.rotationSpeedMin ?? -2, config.rotationSpeedMax ?? 2),
3809
- scale: randRange(config.scaleMin, config.scaleMax),
3810
- scaleSpeed: config.scaleSpeed ?? 0,
3811
- alpha: config.alpha ?? 1,
3812
- fadeRate: config.fadeRate ?? -1.5,
3813
- tint: { ...config.tint },
3814
- blendMode: config.blendMode ?? "source-over",
3815
- spawnTime: animTime,
3816
- lifetime: randRange(config.lifetimeMin, config.lifetimeMax)
3817
- });
3818
- }
3819
- return particles;
3820
- }
3821
- function spawnSequence(config, animTime) {
3822
- return {
3823
- frameUrls: config.frameUrls,
3824
- x: config.originX,
3825
- y: config.originY,
3826
- frameDuration: config.frameDuration,
3827
- startTime: animTime,
3828
- loop: config.loop ?? false,
3829
- scale: config.scale ?? 1,
3830
- tint: config.tint ?? null,
3831
- alpha: config.alpha ?? 1,
3832
- blendMode: config.blendMode ?? "source-over"
3833
- };
3834
- }
3835
- function spawnOverlay(config, animTime) {
3836
- return {
3837
- spriteUrl: config.spriteUrl,
3838
- x: config.originX,
3839
- y: config.originY,
3840
- alpha: config.alpha ?? 0.8,
3841
- fadeRate: config.fadeRate ?? -0.5,
3842
- pulseAmplitude: config.pulseAmplitude ?? 0,
3843
- pulseFrequency: config.pulseFrequency ?? 2,
3844
- scale: config.scale ?? 1,
3845
- blendMode: config.blendMode ?? "source-over",
3846
- spawnTime: animTime,
3847
- lifetime: config.lifetime ?? 2e3
3848
- };
3849
- }
3850
- function updateEffectState(state, animTime, deltaMs) {
3851
- const dt = deltaMs / 1e3;
3852
- const particles = state.particles.map((p2) => ({
3853
- ...p2,
3854
- x: p2.x + p2.vx * dt,
3855
- y: p2.y + p2.vy * dt,
3856
- vy: p2.vy + p2.gravity * dt,
3857
- rotation: p2.rotation + p2.rotationSpeed * dt,
3858
- scale: Math.max(0, p2.scale + p2.scaleSpeed * dt),
3859
- alpha: Math.max(0, p2.alpha + p2.fadeRate * dt)
3860
- })).filter((p2) => p2.alpha > 0.01 && animTime - p2.spawnTime < p2.lifetime);
3861
- const sequences = state.sequences.filter((s) => {
3862
- const elapsed = animTime - s.startTime;
3863
- const totalDuration = s.frameUrls.length * s.frameDuration;
3864
- return s.loop || elapsed < totalDuration;
3865
- });
3866
- const overlays = state.overlays.map((o) => ({
3867
- ...o,
3868
- alpha: Math.max(0, o.alpha + o.fadeRate * dt)
3869
- })).filter((o) => o.alpha > 0.01 && animTime - o.spawnTime < o.lifetime);
3870
- return { particles, sequences, overlays };
3871
- }
3872
- function drawEffectState(ctx, state, animTime, getImage) {
3873
- for (const o of state.overlays) {
3874
- const img = getImage(o.spriteUrl);
3875
- if (!img) continue;
3876
- let alpha = o.alpha;
3877
- if (o.pulseAmplitude > 0) {
3878
- const elapsed = (animTime - o.spawnTime) / 1e3;
3879
- alpha += Math.sin(elapsed * o.pulseFrequency * Math.PI * 2) * o.pulseAmplitude;
3880
- alpha = Math.max(0, Math.min(1, alpha));
3881
- }
3882
- const w = img.naturalWidth * o.scale;
3883
- const h = img.naturalHeight * o.scale;
3884
- const prevAlpha = ctx.globalAlpha;
3885
- const prevBlend = ctx.globalCompositeOperation;
3886
- ctx.globalAlpha = alpha;
3887
- ctx.globalCompositeOperation = o.blendMode;
3888
- ctx.drawImage(img, o.x - w / 2, o.y - h / 2, w, h);
3889
- ctx.globalAlpha = prevAlpha;
3890
- ctx.globalCompositeOperation = prevBlend;
3891
- }
3892
- for (const s of state.sequences) {
3893
- const elapsed = animTime - s.startTime;
3894
- let frameIndex = Math.floor(elapsed / s.frameDuration);
3895
- if (s.loop) {
3896
- frameIndex = frameIndex % s.frameUrls.length;
3897
- } else if (frameIndex >= s.frameUrls.length) {
3898
- continue;
3899
- }
3900
- const img = getImage(s.frameUrls[frameIndex]);
3901
- if (!img) continue;
3902
- const w = img.naturalWidth * s.scale;
3903
- const h = img.naturalHeight * s.scale;
3904
- if (s.tint) {
3905
- drawTintedImage(ctx, img, s.x - w / 2, s.y - h / 2, w, h, s.tint, s.alpha, s.blendMode);
3906
- } else {
3907
- const prevAlpha = ctx.globalAlpha;
3908
- const prevBlend = ctx.globalCompositeOperation;
3909
- ctx.globalAlpha = s.alpha;
3910
- ctx.globalCompositeOperation = s.blendMode;
3911
- ctx.drawImage(img, s.x - w / 2, s.y - h / 2, w, h);
3912
- ctx.globalAlpha = prevAlpha;
3913
- ctx.globalCompositeOperation = prevBlend;
3914
- }
3915
- }
3916
- for (const p2 of state.particles) {
3917
- const img = getImage(p2.spriteUrl);
3918
- if (!img) continue;
3919
- const w = img.naturalWidth * p2.scale;
3920
- const h = img.naturalHeight * p2.scale;
3921
- ctx.save();
3922
- ctx.translate(p2.x, p2.y);
3923
- ctx.rotate(p2.rotation);
3924
- drawTintedImage(ctx, img, -w / 2, -h / 2, w, h, p2.tint, p2.alpha, p2.blendMode);
3925
- ctx.restore();
3926
- }
3927
- }
3928
- function hasActiveEffects(state) {
3929
- return state.particles.length > 0 || state.sequences.length > 0 || state.overlays.length > 0;
3930
- }
3931
- function getAllEffectSpriteUrls(manifest) {
3932
- const urls = [];
3933
- const base = manifest.baseUrl;
3934
- if (manifest.particles) {
3935
- for (const value of Object.values(manifest.particles)) {
3936
- if (Array.isArray(value)) {
3937
- value.forEach((v) => urls.push(`${base}/${v}`));
3938
- } else if (typeof value === "string") {
3939
- urls.push(`${base}/${value}`);
3940
- }
3941
- }
3942
- }
3943
- if (manifest.animations) {
3944
- for (const frames of Object.values(manifest.animations)) {
3945
- if (Array.isArray(frames)) {
3946
- frames.forEach((f) => urls.push(`${base}/${f}`));
3947
- }
3948
- }
3949
- }
3950
- return urls;
3951
- }
3952
-
3953
- // components/organisms/game/utils/combatPresets.ts
3954
- var PI = Math.PI;
3955
- function p(manifest, key) {
3956
- const particles = manifest.particles;
3957
- if (!particles) return [];
3958
- const val = particles[key];
3959
- if (Array.isArray(val)) return val.map((v) => `${manifest.baseUrl}/${v}`);
3960
- if (typeof val === "string") return [`${manifest.baseUrl}/${val}`];
3961
- return [];
3962
- }
3963
- function anim(manifest, key) {
3964
- const animations = manifest.animations;
3965
- if (!animations) return [];
3966
- const val = animations[key];
3967
- if (Array.isArray(val)) return val.map((v) => `${manifest.baseUrl}/${v}`);
3968
- return [];
3969
- }
3970
- function createCombatPresets(manifest) {
3971
- return {
3972
- // =====================================================================
3973
- // MELEE — slash (red) + dirt + scratch + flash sequence
3974
- // =====================================================================
3975
- melee: (originX, originY) => {
3976
- const particles = [
3977
- {
3978
- spriteUrls: p(manifest, "slash"),
3979
- count: 6,
3980
- originX,
3981
- originY,
3982
- spread: 8,
3983
- velocityMin: 40,
3984
- velocityMax: 120,
3985
- angleMin: -PI * 0.8,
3986
- angleMax: -PI * 0.2,
3987
- gravity: 0,
3988
- tint: { r: 255, g: 60, b: 40 },
3989
- scaleMin: 0.3,
3990
- scaleMax: 0.6,
3991
- lifetimeMin: 300,
3992
- lifetimeMax: 500,
3993
- fadeRate: -2.5
3994
- },
3995
- {
3996
- spriteUrls: p(manifest, "dirt"),
3997
- count: 4,
3998
- originX,
3999
- originY: originY + 10,
4000
- spread: 12,
4001
- velocityMin: 20,
4002
- velocityMax: 60,
4003
- angleMin: -PI * 0.9,
4004
- angleMax: -PI * 0.1,
4005
- gravity: 120,
4006
- tint: { r: 180, g: 140, b: 90 },
4007
- scaleMin: 0.15,
4008
- scaleMax: 0.3,
4009
- lifetimeMin: 400,
4010
- lifetimeMax: 700,
4011
- fadeRate: -1.8
4012
- },
4013
- {
4014
- spriteUrls: p(manifest, "scratch"),
4015
- count: 2,
4016
- originX,
4017
- originY,
4018
- spread: 5,
4019
- velocityMin: 10,
4020
- velocityMax: 30,
4021
- angleMin: -PI * 0.7,
4022
- angleMax: -PI * 0.3,
4023
- gravity: 0,
4024
- tint: { r: 255, g: 200, b: 150 },
4025
- scaleMin: 0.25,
4026
- scaleMax: 0.4,
4027
- lifetimeMin: 200,
4028
- lifetimeMax: 400,
4029
- fadeRate: -3
4030
- }
4031
- ];
4032
- const sequences = [];
4033
- const flashFrames = anim(manifest, "flash");
4034
- if (flashFrames.length > 0) {
4035
- sequences.push({
4036
- frameUrls: flashFrames,
4037
- originX,
4038
- originY,
4039
- frameDuration: 35,
4040
- scale: 0.4
4041
- });
4042
- }
4043
- return {
4044
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4045
- sequences,
4046
- overlays: [],
4047
- screenShake: 4,
4048
- screenFlash: null
4049
- };
4050
- },
4051
- // =====================================================================
4052
- // RANGED — muzzle + trace + smoke + explosion sequence
4053
- // =====================================================================
4054
- ranged: (originX, originY) => {
4055
- const particles = [
4056
- {
4057
- spriteUrls: p(manifest, "muzzle"),
4058
- count: 3,
4059
- originX,
4060
- originY,
4061
- spread: 4,
4062
- velocityMin: 60,
4063
- velocityMax: 150,
4064
- angleMin: -PI * 0.6,
4065
- angleMax: -PI * 0.4,
4066
- gravity: 0,
4067
- tint: { r: 255, g: 220, b: 100 },
4068
- scaleMin: 0.2,
4069
- scaleMax: 0.4,
4070
- lifetimeMin: 200,
4071
- lifetimeMax: 400,
4072
- fadeRate: -3
4073
- },
4074
- {
4075
- spriteUrls: p(manifest, "trace"),
4076
- count: 5,
4077
- originX,
4078
- originY,
4079
- spread: 3,
4080
- velocityMin: 100,
4081
- velocityMax: 200,
4082
- angleMin: -PI * 0.55,
4083
- angleMax: -PI * 0.45,
4084
- gravity: 0,
4085
- tint: { r: 255, g: 200, b: 80 },
4086
- scaleMin: 0.15,
4087
- scaleMax: 0.3,
4088
- lifetimeMin: 150,
4089
- lifetimeMax: 300,
4090
- fadeRate: -4
4091
- },
4092
- {
4093
- spriteUrls: p(manifest, "smoke").slice(0, 3),
4094
- count: 3,
4095
- originX,
4096
- originY: originY + 5,
4097
- spread: 6,
4098
- velocityMin: 10,
4099
- velocityMax: 30,
4100
- angleMin: -PI * 0.8,
4101
- angleMax: -PI * 0.2,
4102
- gravity: -20,
4103
- tint: { r: 200, g: 200, b: 200 },
4104
- scaleMin: 0.2,
4105
- scaleMax: 0.35,
4106
- lifetimeMin: 500,
4107
- lifetimeMax: 800,
4108
- fadeRate: -1.5
4109
- }
4110
- ];
4111
- const sequences = [];
4112
- const explosionFrames = anim(manifest, "smokeExplosion");
4113
- if (explosionFrames.length > 0) {
4114
- sequences.push({
4115
- frameUrls: explosionFrames,
4116
- originX,
4117
- originY,
4118
- frameDuration: 50,
4119
- scale: 0.35
4120
- });
4121
- }
4122
- return {
4123
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4124
- sequences,
4125
- overlays: [],
4126
- screenShake: 2,
4127
- screenFlash: null
4128
- };
4129
- },
4130
- // =====================================================================
4131
- // MAGIC — twirl (purple) + spark (purple) + star
4132
- // =====================================================================
4133
- magic: (originX, originY) => {
4134
- const particles = [
4135
- {
4136
- spriteUrls: p(manifest, "twirl"),
4137
- count: 5,
4138
- originX,
4139
- originY,
4140
- spread: 15,
4141
- velocityMin: 20,
4142
- velocityMax: 80,
4143
- angleMin: 0,
4144
- angleMax: PI * 2,
4145
- gravity: -30,
4146
- tint: { r: 180, g: 80, b: 255 },
4147
- scaleMin: 0.2,
4148
- scaleMax: 0.5,
4149
- lifetimeMin: 500,
4150
- lifetimeMax: 900,
4151
- fadeRate: -1.2,
4152
- blendMode: "lighter",
4153
- rotationSpeedMin: -4,
4154
- rotationSpeedMax: 4
4155
- },
4156
- {
4157
- spriteUrls: p(manifest, "spark"),
4158
- count: 8,
4159
- originX,
4160
- originY,
4161
- spread: 20,
4162
- velocityMin: 30,
4163
- velocityMax: 100,
4164
- angleMin: 0,
4165
- angleMax: PI * 2,
4166
- gravity: -15,
4167
- tint: { r: 200, g: 120, b: 255 },
4168
- scaleMin: 0.1,
4169
- scaleMax: 0.25,
4170
- lifetimeMin: 300,
4171
- lifetimeMax: 600,
4172
- fadeRate: -2,
4173
- blendMode: "lighter"
4174
- },
4175
- {
4176
- spriteUrls: p(manifest, "star"),
4177
- count: 4,
4178
- originX,
4179
- originY,
4180
- spread: 10,
4181
- velocityMin: 15,
4182
- velocityMax: 50,
4183
- angleMin: -PI,
4184
- angleMax: 0,
4185
- gravity: -40,
4186
- tint: { r: 220, g: 180, b: 255 },
4187
- scaleMin: 0.15,
4188
- scaleMax: 0.3,
4189
- lifetimeMin: 600,
4190
- lifetimeMax: 1e3,
4191
- fadeRate: -1,
4192
- blendMode: "lighter"
4193
- }
4194
- ];
4195
- const overlays = [];
4196
- const circleUrls = p(manifest, "circle");
4197
- if (circleUrls.length > 0) {
4198
- overlays.push({
4199
- spriteUrl: circleUrls[0],
4200
- originX,
4201
- originY,
4202
- alpha: 0.5,
4203
- fadeRate: -0.6,
4204
- pulseAmplitude: 0.2,
4205
- pulseFrequency: 3,
4206
- scale: 0.5,
4207
- blendMode: "lighter",
4208
- lifetime: 1200
4209
- });
4210
- }
4211
- return {
4212
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4213
- sequences: [],
4214
- overlays,
4215
- screenShake: 0,
4216
- screenFlash: null
4217
- };
4218
- },
4219
- // =====================================================================
4220
- // HEAL — circle (green) + star (green) + light (green, pulse)
4221
- // =====================================================================
4222
- heal: (originX, originY) => {
4223
- const particles = [
4224
- {
4225
- spriteUrls: p(manifest, "circle"),
4226
- count: 6,
4227
- originX,
4228
- originY,
4229
- spread: 15,
4230
- velocityMin: 10,
4231
- velocityMax: 40,
4232
- angleMin: -PI,
4233
- angleMax: -PI * 0.3,
4234
- gravity: -50,
4235
- tint: { r: 80, g: 255, b: 120 },
4236
- scaleMin: 0.15,
4237
- scaleMax: 0.35,
4238
- lifetimeMin: 600,
4239
- lifetimeMax: 1e3,
4240
- fadeRate: -0.8,
4241
- blendMode: "lighter"
4242
- },
4243
- {
4244
- spriteUrls: p(manifest, "star"),
4245
- count: 5,
4246
- originX,
4247
- originY,
4248
- spread: 12,
4249
- velocityMin: 15,
4250
- velocityMax: 50,
4251
- angleMin: -PI * 0.9,
4252
- angleMax: -PI * 0.1,
4253
- gravity: -60,
4254
- tint: { r: 100, g: 255, b: 140 },
4255
- scaleMin: 0.1,
4256
- scaleMax: 0.2,
4257
- lifetimeMin: 500,
4258
- lifetimeMax: 800,
4259
- fadeRate: -1.2,
4260
- blendMode: "lighter"
4261
- }
4262
- ];
4263
- const overlays = [];
4264
- const lightUrls = p(manifest, "light");
4265
- if (lightUrls.length > 0) {
4266
- overlays.push({
4267
- spriteUrl: lightUrls[0],
4268
- originX,
4269
- originY,
4270
- alpha: 0.6,
4271
- fadeRate: -0.4,
4272
- pulseAmplitude: 0.25,
4273
- pulseFrequency: 2.5,
4274
- scale: 0.6,
4275
- blendMode: "lighter",
4276
- lifetime: 1500
4277
- });
4278
- }
4279
- return {
4280
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4281
- sequences: [],
4282
- overlays,
4283
- screenShake: 0,
4284
- screenFlash: null
4285
- };
4286
- },
4287
- // =====================================================================
4288
- // DEFEND / SHIELD — star (blue) + circle (blue, pulse)
4289
- // =====================================================================
4290
- defend: (originX, originY) => {
4291
- const particles = [
4292
- {
4293
- spriteUrls: p(manifest, "star"),
4294
- count: 8,
4295
- originX,
4296
- originY,
4297
- spread: 18,
4298
- velocityMin: 10,
4299
- velocityMax: 35,
4300
- angleMin: 0,
4301
- angleMax: PI * 2,
4302
- gravity: 0,
4303
- tint: { r: 80, g: 160, b: 255 },
4304
- scaleMin: 0.12,
4305
- scaleMax: 0.25,
4306
- lifetimeMin: 600,
4307
- lifetimeMax: 1e3,
4308
- fadeRate: -0.8,
4309
- blendMode: "lighter",
4310
- rotationSpeedMin: -1,
4311
- rotationSpeedMax: 1
4312
- }
4313
- ];
4314
- const overlays = [];
4315
- const circleUrls = p(manifest, "circle");
4316
- if (circleUrls.length > 0) {
4317
- overlays.push({
4318
- spriteUrl: circleUrls[0],
4319
- originX,
4320
- originY,
4321
- alpha: 0.6,
4322
- fadeRate: -0.3,
4323
- pulseAmplitude: 0.2,
4324
- pulseFrequency: 2,
4325
- scale: 0.6,
4326
- blendMode: "lighter",
4327
- lifetime: 1500
4328
- });
4329
- }
4330
- return {
4331
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4332
- sequences: [],
4333
- overlays,
4334
- screenShake: 0,
4335
- screenFlash: null
4336
- };
4337
- },
4338
- // shield aliases to defend
4339
- shield: (originX, originY) => {
4340
- const particles = [
4341
- {
4342
- spriteUrls: p(manifest, "star"),
4343
- count: 10,
4344
- originX,
4345
- originY,
4346
- spread: 20,
4347
- velocityMin: 8,
4348
- velocityMax: 30,
4349
- angleMin: 0,
4350
- angleMax: PI * 2,
4351
- gravity: 0,
4352
- tint: { r: 60, g: 180, b: 255 },
4353
- scaleMin: 0.1,
4354
- scaleMax: 0.22,
4355
- lifetimeMin: 700,
4356
- lifetimeMax: 1200,
4357
- fadeRate: -0.7,
4358
- blendMode: "lighter",
4359
- rotationSpeedMin: -0.8,
4360
- rotationSpeedMax: 0.8
4361
- }
4362
- ];
4363
- const overlays = [];
4364
- const circleUrls = p(manifest, "circle");
4365
- if (circleUrls.length > 0) {
4366
- overlays.push({
4367
- spriteUrl: circleUrls[0],
4368
- originX,
4369
- originY,
4370
- alpha: 0.7,
4371
- fadeRate: -0.25,
4372
- pulseAmplitude: 0.25,
4373
- pulseFrequency: 1.8,
4374
- scale: 0.7,
4375
- blendMode: "lighter",
4376
- lifetime: 1800
4377
- });
4378
- }
4379
- return {
4380
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4381
- sequences: [],
4382
- overlays,
4383
- screenShake: 0,
4384
- screenFlash: null
4385
- };
4386
- },
4387
- // =====================================================================
4388
- // HIT — spark (orange) + flash (5 frames) + screen shake/flash
4389
- // =====================================================================
4390
- hit: (originX, originY) => {
4391
- const particles = [
4392
- {
4393
- spriteUrls: p(manifest, "spark"),
4394
- count: 10,
4395
- originX,
4396
- originY,
4397
- spread: 8,
4398
- velocityMin: 50,
4399
- velocityMax: 150,
4400
- angleMin: 0,
4401
- angleMax: PI * 2,
4402
- gravity: 80,
4403
- tint: { r: 255, g: 180, b: 50 },
4404
- scaleMin: 0.08,
4405
- scaleMax: 0.2,
4406
- lifetimeMin: 200,
4407
- lifetimeMax: 500,
4408
- fadeRate: -2.5
4409
- }
4410
- ];
4411
- const sequences = [];
4412
- const flashFrames = anim(manifest, "flash");
4413
- if (flashFrames.length > 0) {
4414
- sequences.push({
4415
- frameUrls: flashFrames.slice(0, 5),
4416
- originX,
4417
- originY,
4418
- frameDuration: 40,
4419
- scale: 0.3
4420
- });
4421
- }
4422
- return {
4423
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4424
- sequences,
4425
- overlays: [],
4426
- screenShake: 3,
4427
- screenFlash: { r: 255, g: 50, b: 50, duration: 150 }
4428
- };
4429
- },
4430
- // critical aliases to hit with bigger shake
4431
- critical: (originX, originY) => {
4432
- const particles = [
4433
- {
4434
- spriteUrls: p(manifest, "flame"),
4435
- count: 8,
4436
- originX,
4437
- originY,
4438
- spread: 12,
4439
- velocityMin: 60,
4440
- velocityMax: 180,
4441
- angleMin: 0,
4442
- angleMax: PI * 2,
4443
- gravity: 60,
4444
- tint: { r: 255, g: 120, b: 30 },
4445
- scaleMin: 0.15,
4446
- scaleMax: 0.4,
4447
- lifetimeMin: 300,
4448
- lifetimeMax: 600,
4449
- fadeRate: -2
4450
- },
4451
- {
4452
- spriteUrls: p(manifest, "spark"),
4453
- count: 12,
4454
- originX,
4455
- originY,
4456
- spread: 10,
4457
- velocityMin: 80,
4458
- velocityMax: 200,
4459
- angleMin: 0,
4460
- angleMax: PI * 2,
4461
- gravity: 100,
4462
- tint: { r: 255, g: 200, b: 60 },
4463
- scaleMin: 0.06,
4464
- scaleMax: 0.18,
4465
- lifetimeMin: 200,
4466
- lifetimeMax: 400,
4467
- fadeRate: -3
4468
- }
4469
- ];
4470
- const sequences = [];
4471
- const flashFrames = anim(manifest, "flash");
4472
- if (flashFrames.length > 0) {
4473
- sequences.push({
4474
- frameUrls: flashFrames,
4475
- originX,
4476
- originY,
4477
- frameDuration: 30,
4478
- scale: 0.5
4479
- });
4480
- }
4481
- return {
4482
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4483
- sequences,
4484
- overlays: [],
4485
- screenShake: 6,
4486
- screenFlash: { r: 255, g: 80, b: 0, duration: 200 }
4487
- };
4488
- },
4489
- // =====================================================================
4490
- // DEATH — dirt (gray) + explosion + black smoke + scorch (ground)
4491
- // =====================================================================
4492
- death: (originX, originY) => {
4493
- const particles = [
4494
- {
4495
- spriteUrls: p(manifest, "dirt"),
4496
- count: 8,
4497
- originX,
4498
- originY,
4499
- spread: 10,
4500
- velocityMin: 30,
4501
- velocityMax: 100,
4502
- angleMin: 0,
4503
- angleMax: PI * 2,
4504
- gravity: 100,
4505
- tint: { r: 140, g: 140, b: 140 },
4506
- scaleMin: 0.15,
4507
- scaleMax: 0.35,
4508
- lifetimeMin: 500,
4509
- lifetimeMax: 900,
4510
- fadeRate: -1.2
4511
- }
4512
- ];
4513
- const sequences = [];
4514
- const explosionFrames = anim(manifest, "explosion");
4515
- if (explosionFrames.length > 0) {
4516
- sequences.push({
4517
- frameUrls: explosionFrames,
4518
- originX,
4519
- originY,
4520
- frameDuration: 60,
4521
- scale: 0.5
4522
- });
4523
- }
4524
- const blackSmokeFrames = anim(manifest, "blackSmoke");
4525
- if (blackSmokeFrames.length > 0) {
4526
- sequences.push({
4527
- frameUrls: blackSmokeFrames,
4528
- originX,
4529
- originY: originY - 10,
4530
- frameDuration: 50,
4531
- scale: 0.4,
4532
- alpha: 0.7
4533
- });
4534
- }
4535
- const overlays = [];
4536
- const scorchUrls = p(manifest, "scorch");
4537
- if (scorchUrls.length > 0) {
4538
- overlays.push({
4539
- spriteUrl: scorchUrls[0],
4540
- originX,
4541
- originY: originY + 10,
4542
- alpha: 0.6,
4543
- fadeRate: -0.15,
4544
- scale: 0.4,
4545
- lifetime: 4e3
4546
- });
4547
- }
4548
- return {
4549
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4550
- sequences,
4551
- overlays,
4552
- screenShake: 0,
4553
- screenFlash: null
4554
- };
4555
- },
4556
- // =====================================================================
4557
- // BUFF — star (gold) + symbol + flare (gold, pulse)
4558
- // =====================================================================
4559
- buff: (originX, originY) => {
4560
- const particles = [
4561
- {
4562
- spriteUrls: p(manifest, "star"),
4563
- count: 6,
4564
- originX,
4565
- originY,
4566
- spread: 15,
4567
- velocityMin: 15,
4568
- velocityMax: 50,
4569
- angleMin: -PI,
4570
- angleMax: 0,
4571
- gravity: -30,
4572
- tint: { r: 255, g: 215, b: 50 },
4573
- scaleMin: 0.12,
4574
- scaleMax: 0.25,
4575
- lifetimeMin: 600,
4576
- lifetimeMax: 1e3,
4577
- fadeRate: -0.8,
4578
- blendMode: "lighter"
4579
- },
4580
- {
4581
- spriteUrls: p(manifest, "symbol"),
4582
- count: 2,
4583
- originX,
4584
- originY: originY - 10,
4585
- spread: 8,
4586
- velocityMin: 5,
4587
- velocityMax: 20,
4588
- angleMin: -PI * 0.7,
4589
- angleMax: -PI * 0.3,
4590
- gravity: -20,
4591
- tint: { r: 255, g: 230, b: 100 },
4592
- scaleMin: 0.2,
4593
- scaleMax: 0.35,
4594
- lifetimeMin: 800,
4595
- lifetimeMax: 1200,
4596
- fadeRate: -0.6,
4597
- blendMode: "lighter"
4598
- }
4599
- ];
4600
- const overlays = [];
4601
- const flareUrls = p(manifest, "flare");
4602
- if (flareUrls.length > 0) {
4603
- overlays.push({
4604
- spriteUrl: flareUrls[0],
4605
- originX,
4606
- originY,
4607
- alpha: 0.5,
4608
- fadeRate: -0.3,
4609
- pulseAmplitude: 0.3,
4610
- pulseFrequency: 2,
4611
- scale: 0.5,
4612
- blendMode: "lighter",
4613
- lifetime: 1500
4614
- });
4615
- }
4616
- return {
4617
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4618
- sequences: [],
4619
- overlays,
4620
- screenShake: 0,
4621
- screenFlash: null
4622
- };
4623
- },
4624
- // =====================================================================
4625
- // DEBUFF — scorch (dark) + smoke (purple tint)
4626
- // =====================================================================
4627
- debuff: (originX, originY) => {
4628
- const particles = [
4629
- {
4630
- spriteUrls: p(manifest, "scorch"),
4631
- count: 4,
4632
- originX,
4633
- originY,
4634
- spread: 12,
4635
- velocityMin: 15,
4636
- velocityMax: 40,
4637
- angleMin: -PI,
4638
- angleMax: 0,
4639
- gravity: -20,
4640
- tint: { r: 120, g: 40, b: 160 },
4641
- scaleMin: 0.15,
4642
- scaleMax: 0.3,
4643
- lifetimeMin: 500,
4644
- lifetimeMax: 800,
4645
- fadeRate: -1
4646
- },
4647
- {
4648
- spriteUrls: p(manifest, "smoke").slice(0, 3),
4649
- count: 3,
4650
- originX,
4651
- originY,
4652
- spread: 10,
4653
- velocityMin: 8,
4654
- velocityMax: 25,
4655
- angleMin: -PI * 0.8,
4656
- angleMax: -PI * 0.2,
4657
- gravity: -15,
4658
- tint: { r: 100, g: 50, b: 140 },
4659
- scaleMin: 0.2,
4660
- scaleMax: 0.35,
4661
- lifetimeMin: 600,
4662
- lifetimeMax: 1e3,
4663
- fadeRate: -0.8
4664
- }
4665
- ];
4666
- const overlays = [];
4667
- const circleUrls = p(manifest, "circle");
4668
- if (circleUrls.length > 0) {
4669
- overlays.push({
4670
- spriteUrl: circleUrls[0],
4671
- originX,
4672
- originY,
4673
- alpha: 0.4,
4674
- fadeRate: -0.4,
4675
- pulseAmplitude: 0.15,
4676
- pulseFrequency: 2,
4677
- scale: 0.45,
4678
- lifetime: 1200
4679
- });
4680
- }
4681
- return {
4682
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4683
- sequences: [],
4684
- overlays,
4685
- screenShake: 0,
4686
- screenFlash: null
4687
- };
4688
- },
4689
- // =====================================================================
4690
- // AOE — explosion (large) + flame + spark (radial) + screen shake
4691
- // =====================================================================
4692
- aoe: (originX, originY) => {
4693
- const particles = [
4694
- {
4695
- spriteUrls: p(manifest, "flame"),
4696
- count: 10,
4697
- originX,
4698
- originY,
4699
- spread: 20,
4700
- velocityMin: 40,
4701
- velocityMax: 140,
4702
- angleMin: 0,
4703
- angleMax: PI * 2,
4704
- gravity: 40,
4705
- tint: { r: 255, g: 140, b: 30 },
4706
- scaleMin: 0.2,
4707
- scaleMax: 0.5,
4708
- lifetimeMin: 400,
4709
- lifetimeMax: 800,
4710
- fadeRate: -1.5
4711
- },
4712
- {
4713
- spriteUrls: p(manifest, "spark"),
4714
- count: 15,
4715
- originX,
4716
- originY,
4717
- spread: 15,
4718
- velocityMin: 60,
4719
- velocityMax: 200,
4720
- angleMin: 0,
4721
- angleMax: PI * 2,
4722
- gravity: 60,
4723
- tint: { r: 255, g: 180, b: 60 },
4724
- scaleMin: 0.06,
4725
- scaleMax: 0.15,
4726
- lifetimeMin: 200,
4727
- lifetimeMax: 500,
4728
- fadeRate: -2.5
4729
- }
4730
- ];
4731
- const sequences = [];
4732
- const explosionFrames = anim(manifest, "explosion");
4733
- if (explosionFrames.length > 0) {
4734
- sequences.push({
4735
- frameUrls: explosionFrames,
4736
- originX,
4737
- originY,
4738
- frameDuration: 50,
4739
- scale: 0.6
4740
- });
4741
- }
4742
- return {
4743
- particles: particles.filter((pc) => pc.spriteUrls.length > 0),
4744
- sequences,
4745
- overlays: [],
4746
- screenShake: 5,
4747
- screenFlash: { r: 255, g: 160, b: 0, duration: 180 }
4748
- };
4749
- }
4750
- };
4751
- }
4752
- var ACTION_EMOJI = {
4753
- melee: { emoji: "\u2694\uFE0F", color: "var(--color-error)", label: "Slash" },
4754
- ranged: { emoji: "\u{1F3F9}", color: "var(--color-warning)", label: "Arrow" },
4755
- magic: { emoji: "\u2728", color: "var(--color-primary)", label: "Spell" },
4756
- heal: { emoji: "\u{1F49A}", color: "var(--color-success)", label: "Heal" },
4757
- buff: { emoji: "\u2B06\uFE0F", color: "var(--color-info)", label: "Buff" },
4758
- debuff: { emoji: "\u2B07\uFE0F", color: "var(--color-warning)", label: "Debuff" },
4759
- shield: { emoji: "\u{1F6E1}\uFE0F", color: "var(--color-info)", label: "Shield" },
4760
- aoe: { emoji: "\u{1F4A5}", color: "var(--color-error)", label: "Explosion" },
4761
- critical: { emoji: "\u{1F525}", color: "var(--color-error)", label: "Critical" },
4762
- defend: { emoji: "\u{1F6E1}\uFE0F", color: "var(--color-info)", label: "Defend" },
4763
- hit: { emoji: "\u{1F4A5}", color: "var(--color-error)", label: "Hit" },
4764
- death: { emoji: "\u{1F480}", color: "var(--color-error)", label: "Death" }
4765
- };
4766
- function CanvasEffectEngine({
4767
- actionType,
4768
- x,
4769
- y,
4770
- duration = 2e3,
4771
- intensity = 1,
4772
- onComplete,
4773
- className,
4774
- assetManifest,
4775
- width = 400,
4776
- height = 300
4777
- }) {
4778
- const canvasRef = useRef(null);
4779
- const stateRef = useRef({ ...EMPTY_EFFECT_STATE });
4780
- const lastTimeRef = useRef(0);
4781
- const rafRef = useRef(0);
4782
- const imageCacheRef = useRef(/* @__PURE__ */ new Map());
4783
- const [shakeOffset, setShakeOffset] = useState({ x: 0, y: 0 });
4784
- const [flash, setFlash] = useState(null);
4785
- const shakeRef = useRef({ x: 0, y: 0, intensity: 0 });
4786
- const presets = useMemo(() => createCombatPresets(assetManifest), [assetManifest]);
4787
- const spriteUrls = useMemo(() => getAllEffectSpriteUrls(assetManifest), [assetManifest]);
4788
- useEffect(() => {
4789
- const cache = imageCacheRef.current;
4790
- for (const url of spriteUrls) {
4791
- if (!cache.has(url)) {
4792
- const img = new Image();
4793
- img.crossOrigin = "anonymous";
4794
- img.src = url;
4795
- cache.set(url, img);
4796
- }
4797
- }
4798
- }, [spriteUrls]);
4799
- const getImage = useCallback((url) => {
4800
- const img = imageCacheRef.current.get(url);
4801
- return img?.complete ? img : void 0;
4802
- }, []);
4803
- useEffect(() => {
4804
- const now = performance.now();
4805
- const effectX = x || width / 2;
4806
- const effectY = y || height / 2;
4807
- const preset = presets[actionType](effectX, effectY);
4808
- const state = stateRef.current;
4809
- for (const emitter of preset.particles) {
4810
- const scaledEmitter = { ...emitter, count: Math.round(emitter.count * intensity) };
4811
- state.particles.push(...spawnParticles(scaledEmitter, now));
4812
- }
4813
- for (const seqConfig of preset.sequences) {
4814
- state.sequences.push(spawnSequence(seqConfig, now));
4815
- }
4816
- for (const ovConfig of preset.overlays) {
4817
- state.overlays.push(spawnOverlay(ovConfig, now));
4818
- }
4819
- if (preset.screenShake > 0) {
4820
- shakeRef.current.intensity = preset.screenShake * intensity;
4821
- }
4822
- if (preset.screenFlash) {
4823
- const { r, g, b, duration: flashDur } = preset.screenFlash;
4824
- setFlash({ color: `rgb(${r}, ${g}, ${b})`, alpha: 0.3 });
4825
- setTimeout(() => setFlash(null), flashDur);
4826
- }
4827
- const timer = setTimeout(() => {
4828
- onComplete?.();
4829
- }, duration);
4830
- return () => clearTimeout(timer);
4831
- }, []);
4832
- useEffect(() => {
4833
- const canvas = canvasRef.current;
4834
- if (!canvas) return;
4835
- const ctx = canvas.getContext("2d");
4836
- if (!ctx) return;
4837
- function loop(animTime) {
4838
- const delta = lastTimeRef.current > 0 ? animTime - lastTimeRef.current : 16;
4839
- lastTimeRef.current = animTime;
4840
- stateRef.current = updateEffectState(stateRef.current, animTime, delta);
4841
- if (shakeRef.current.intensity > 0.2) {
4842
- const i = shakeRef.current.intensity;
4843
- shakeRef.current.x = (Math.random() - 0.5) * i * 2;
4844
- shakeRef.current.y = (Math.random() - 0.5) * i * 2;
4845
- shakeRef.current.intensity *= 0.85;
4846
- setShakeOffset({ x: shakeRef.current.x, y: shakeRef.current.y });
4847
- } else if (shakeRef.current.intensity > 0) {
4848
- shakeRef.current = { x: 0, y: 0, intensity: 0 };
4849
- setShakeOffset({ x: 0, y: 0 });
4850
- }
4851
- ctx.clearRect(0, 0, width, height);
4852
- drawEffectState(ctx, stateRef.current, animTime, getImage);
4853
- if (hasActiveEffects(stateRef.current)) {
4854
- rafRef.current = requestAnimationFrame(loop);
4855
- }
4856
- }
4857
- rafRef.current = requestAnimationFrame(loop);
4858
- return () => cancelAnimationFrame(rafRef.current);
4859
- }, [width, height, getImage]);
4860
- const shakeStyle = shakeOffset.x !== 0 || shakeOffset.y !== 0 ? { transform: `translate(${shakeOffset.x}px, ${shakeOffset.y}px)` } : {};
4861
- return /* @__PURE__ */ jsxs(
4862
- Box,
4863
- {
4864
- className: cn("absolute inset-0 pointer-events-none z-10", className),
4865
- style: shakeStyle,
4866
- children: [
4867
- flash && /* @__PURE__ */ jsx(
4868
- Box,
4869
- {
4870
- className: "absolute inset-0 z-20 pointer-events-none rounded-lg",
4871
- style: { backgroundColor: flash.color, opacity: flash.alpha }
4872
- }
4873
- ),
4874
- /* @__PURE__ */ jsx(
4875
- "canvas",
4876
- {
4877
- ref: canvasRef,
4878
- width,
4879
- height,
4880
- className: "absolute inset-0 w-full h-full",
4881
- style: { imageRendering: "pixelated" }
4882
- }
4883
- )
4884
- ]
4885
- }
4886
- );
4887
- }
4888
- function EmojiEffect({
4889
- actionType,
4890
- x,
4891
- y,
4892
- duration = 800,
4893
- intensity = 1,
4894
- onComplete,
4895
- className,
4896
- effectSpriteUrl,
4897
- assetBaseUrl
4898
- }) {
4899
- const [visible, setVisible] = useState(true);
4900
- const [phase, setPhase] = useState("enter");
4901
- useEffect(() => {
4902
- const enterTimer = setTimeout(() => setPhase("active"), 100);
4903
- const exitTimer = setTimeout(() => setPhase("exit"), duration * 0.7);
4904
- const doneTimer = setTimeout(() => {
4905
- setVisible(false);
4906
- onComplete?.();
4907
- }, duration);
4908
- return () => {
4909
- clearTimeout(enterTimer);
4910
- clearTimeout(exitTimer);
4911
- clearTimeout(doneTimer);
4912
- };
4913
- }, [duration, onComplete]);
4914
- if (!visible) return null;
4915
- const config = ACTION_EMOJI[actionType] ?? ACTION_EMOJI.melee;
4916
- const scaleVal = phase === "enter" ? 0.3 : phase === "active" ? intensity : 0.5;
4917
- const opacity = phase === "exit" ? 0 : 1;
4918
- const resolvedSpriteUrl = effectSpriteUrl ? effectSpriteUrl.startsWith("http") || effectSpriteUrl.startsWith("/") ? effectSpriteUrl : assetBaseUrl ? `${assetBaseUrl.replace(/\/$/, "")}/${effectSpriteUrl}` : effectSpriteUrl : void 0;
4919
- return /* @__PURE__ */ jsxs(
4920
- Box,
4921
- {
4922
- className: cn(
4923
- "fixed pointer-events-none z-50 flex items-center justify-center",
4924
- "transition-all ease-out",
4925
- className
4926
- ),
4927
- style: {
4928
- left: x,
4929
- top: y,
4930
- transform: `translate(-50%, -50%) scale(${scaleVal})`,
4931
- opacity,
4932
- transitionDuration: phase === "enter" ? "100ms" : "300ms"
4933
- },
4934
- children: [
4935
- /* @__PURE__ */ jsx(
4936
- Box,
4937
- {
4938
- className: "absolute rounded-full animate-ping",
4939
- style: {
4940
- width: 48 * intensity,
4941
- height: 48 * intensity,
4942
- backgroundColor: config.color,
4943
- opacity: 0.25
4944
- }
4945
- }
4946
- ),
4947
- resolvedSpriteUrl ? /* @__PURE__ */ jsx(
4948
- "img",
4949
- {
4950
- src: resolvedSpriteUrl,
4951
- alt: config.label,
4952
- className: "relative drop-shadow-lg",
4953
- style: {
4954
- width: `${3 * intensity}rem`,
4955
- height: `${3 * intensity}rem`,
4956
- objectFit: "contain",
4957
- imageRendering: "pixelated"
4958
- }
4959
- }
4960
- ) : /* @__PURE__ */ jsx(
4961
- "span",
4962
- {
4963
- className: "relative text-3xl drop-shadow-lg",
4964
- style: { fontSize: `${2 * intensity}rem` },
4965
- role: "img",
4966
- "aria-label": config.label,
4967
- children: config.emoji
4968
- }
4969
- )
4970
- ]
4971
- }
4972
- );
4973
- }
4974
- function CanvasEffect(props) {
4975
- const eventBus = useEventBus();
4976
- const { completeEvent, onComplete, ...rest } = props;
4977
- const handleComplete = useCallback(() => {
4978
- if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
4979
- onComplete?.();
4980
- }, [completeEvent, eventBus, onComplete]);
4981
- const enhancedProps = { ...rest, onComplete: handleComplete };
4982
- if (props.assetManifest) {
4983
- return /* @__PURE__ */ jsx(CanvasEffectEngine, { ...enhancedProps, assetManifest: props.assetManifest });
4984
- }
4985
- return /* @__PURE__ */ jsx(EmojiEffect, { ...enhancedProps });
4986
- }
4987
- CanvasEffect.displayName = "CanvasEffect";
4988
3748
  function pickPath(entry) {
4989
3749
  if (Array.isArray(entry.path)) {
4990
3750
  return entry.path[Math.floor(Math.random() * entry.path.length)];
@@ -5805,7 +4565,7 @@ function BattleBoard({
5805
4565
  onUnitMove,
5806
4566
  calculateDamage: calculateDamage2,
5807
4567
  onDrawEffects,
5808
- hasActiveEffects: hasActiveEffects2 = false,
4568
+ hasActiveEffects = false,
5809
4569
  effectSpriteUrls = [],
5810
4570
  resolveUnitFrame,
5811
4571
  tileClickEvent,
@@ -5880,25 +4640,25 @@ function BattleBoard({
5880
4640
  }, []);
5881
4641
  useEffect(() => {
5882
4642
  const interval = setInterval(() => {
5883
- const anim2 = movementAnimRef.current;
5884
- if (!anim2) return;
5885
- anim2.elapsed += 16;
5886
- const t2 = Math.min(anim2.elapsed / anim2.duration, 1);
4643
+ const anim = movementAnimRef.current;
4644
+ if (!anim) return;
4645
+ anim.elapsed += 16;
4646
+ const t2 = Math.min(anim.elapsed / anim.duration, 1);
5887
4647
  const eased = 1 - (1 - t2) * (1 - t2);
5888
- const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
5889
- const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
4648
+ const cx = anim.from.x + (anim.to.x - anim.from.x) * eased;
4649
+ const cy = anim.from.y + (anim.to.y - anim.from.y) * eased;
5890
4650
  if (t2 >= 1) {
5891
4651
  movementAnimRef.current = null;
5892
4652
  setMovingPositions((prev) => {
5893
4653
  const next = new Map(prev);
5894
- next.delete(anim2.unitId);
4654
+ next.delete(anim.unitId);
5895
4655
  return next;
5896
4656
  });
5897
- anim2.onComplete();
4657
+ anim.onComplete();
5898
4658
  } else {
5899
4659
  setMovingPositions((prev) => {
5900
4660
  const next = new Map(prev);
5901
- next.set(anim2.unitId, { x: cx, y: cy });
4661
+ next.set(anim.unitId, { x: cx, y: cy });
5902
4662
  return next;
5903
4663
  });
5904
4664
  }
@@ -6069,7 +4829,7 @@ function BattleBoard({
6069
4829
  assetManifest,
6070
4830
  backgroundImage,
6071
4831
  onDrawEffects,
6072
- hasActiveEffects: hasActiveEffects2,
4832
+ hasActiveEffects,
6073
4833
  effectSpriteUrls,
6074
4834
  resolveUnitFrame,
6075
4835
  unitScale
@@ -6365,25 +5125,25 @@ function WorldMapBoard({
6365
5125
  }, []);
6366
5126
  useEffect(() => {
6367
5127
  const interval = setInterval(() => {
6368
- const anim2 = movementAnimRef.current;
6369
- if (!anim2) return;
6370
- anim2.elapsed += 16;
6371
- const t = Math.min(anim2.elapsed / anim2.duration, 1);
5128
+ const anim = movementAnimRef.current;
5129
+ if (!anim) return;
5130
+ anim.elapsed += 16;
5131
+ const t = Math.min(anim.elapsed / anim.duration, 1);
6372
5132
  const eased = 1 - (1 - t) * (1 - t);
6373
- const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
6374
- const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
5133
+ const cx = anim.from.x + (anim.to.x - anim.from.x) * eased;
5134
+ const cy = anim.from.y + (anim.to.y - anim.from.y) * eased;
6375
5135
  if (t >= 1) {
6376
5136
  movementAnimRef.current = null;
6377
5137
  setMovingPositions((prev) => {
6378
5138
  const n = new Map(prev);
6379
- n.delete(anim2.heroId);
5139
+ n.delete(anim.heroId);
6380
5140
  return n;
6381
5141
  });
6382
- anim2.onComplete();
5142
+ anim.onComplete();
6383
5143
  } else {
6384
5144
  setMovingPositions((prev) => {
6385
5145
  const n = new Map(prev);
6386
- n.set(anim2.heroId, { x: cx, y: cy });
5146
+ n.set(anim.heroId, { x: cx, y: cy });
6387
5147
  return n;
6388
5148
  });
6389
5149
  }
@@ -8398,8 +7158,8 @@ function SimulatorBoard({
8398
7158
  const { t } = useTranslate();
8399
7159
  const [values, setValues] = useState(() => {
8400
7160
  const init = {};
8401
- for (const p2 of entity.parameters) {
8402
- init[p2.id] = p2.initial;
7161
+ for (const p of entity.parameters) {
7162
+ init[p.id] = p.initial;
8403
7163
  }
8404
7164
  return init;
8405
7165
  });
@@ -8436,8 +7196,8 @@ function SimulatorBoard({
8436
7196
  };
8437
7197
  const handleFullReset = () => {
8438
7198
  const init = {};
8439
- for (const p2 of entity.parameters) {
8440
- init[p2.id] = p2.initial;
7199
+ for (const p of entity.parameters) {
7200
+ init[p.id] = p.initial;
8441
7201
  }
8442
7202
  setValues(init);
8443
7203
  setSubmitted(false);
@@ -8983,7 +7743,7 @@ function NegotiatorBoard({
8983
7743
  if (isComplete) return;
8984
7744
  const opponentAction = getOpponentAction(entity.opponentStrategy, entity.actions, history);
8985
7745
  const payoff = entity.payoffMatrix.find(
8986
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
7746
+ (p) => p.playerAction === actionId && p.opponentAction === opponentAction
8987
7747
  );
8988
7748
  const result = {
8989
7749
  round: currentRound + 1,
@@ -9157,7 +7917,7 @@ function resolvePreset(preset) {
9157
7917
  if (typeof preset !== "string") return preset;
9158
7918
  const needle = preset.toLowerCase();
9159
7919
  return ALL_PRESETS.find(
9160
- (p2) => p2.id === preset || p2.id.includes(needle) || p2.name.toLowerCase().includes(needle)
7920
+ (p) => p.id === preset || p.id.includes(needle) || p.name.toLowerCase().includes(needle)
9161
7921
  ) ?? projectileMotion;
9162
7922
  }
9163
7923
  function SimulationCanvas({
@@ -9857,7 +8617,7 @@ var LineChart2 = ({ data, height, showValues, fill = false }) => {
9857
8617
  ...point
9858
8618
  }));
9859
8619
  }, [data, maxValue, chartWidth, chartHeight, padding]);
9860
- const linePath = points.map((p2, i) => `${i === 0 ? "M" : "L"} ${p2.x} ${p2.y}`).join(" ");
8620
+ const linePath = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
9861
8621
  const areaPath = `${linePath} L ${points[points.length - 1]?.x ?? 0} ${padding.top + chartHeight} L ${padding.left} ${padding.top + chartHeight} Z`;
9862
8622
  return /* @__PURE__ */ jsxs("svg", { width: "100%", height, viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "xMidYMid meet", children: [
9863
8623
  [0, 0.25, 0.5, 0.75, 1].map((frac) => {
@@ -9878,18 +8638,18 @@ var LineChart2 = ({ data, height, showValues, fill = false }) => {
9878
8638
  }),
9879
8639
  fill && /* @__PURE__ */ jsx("path", { d: areaPath, fill: "var(--color-primary)", opacity: 0.1 }),
9880
8640
  /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: "var(--color-primary)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
9881
- points.map((p2, idx) => /* @__PURE__ */ jsxs("g", { children: [
9882
- /* @__PURE__ */ jsx("circle", { cx: p2.x, cy: p2.y, r: "4", fill: "var(--color-card)", stroke: "var(--color-primary)", strokeWidth: "2" }),
9883
- showValues && /* @__PURE__ */ jsx("text", { x: p2.x, y: p2.y - 10, textAnchor: "middle", fill: "var(--color-foreground)", fontSize: "10", fontWeight: "500", children: p2.value }),
8641
+ points.map((p, idx) => /* @__PURE__ */ jsxs("g", { children: [
8642
+ /* @__PURE__ */ jsx("circle", { cx: p.x, cy: p.y, r: "4", fill: "var(--color-card)", stroke: "var(--color-primary)", strokeWidth: "2" }),
8643
+ showValues && /* @__PURE__ */ jsx("text", { x: p.x, y: p.y - 10, textAnchor: "middle", fill: "var(--color-foreground)", fontSize: "10", fontWeight: "500", children: p.value }),
9884
8644
  /* @__PURE__ */ jsx(
9885
8645
  "text",
9886
8646
  {
9887
- x: p2.x,
8647
+ x: p.x,
9888
8648
  y: height - 8,
9889
8649
  textAnchor: "middle",
9890
8650
  fill: "var(--color-muted-foreground)",
9891
8651
  fontSize: "9",
9892
- children: p2.label
8652
+ children: p.label
9893
8653
  }
9894
8654
  )
9895
8655
  ] }, idx))
@@ -10534,12 +9294,12 @@ var DocumentViewer = ({
10534
9294
  const handleZoomIn = useCallback(() => setZoom((z) => Math.min(z + 25, 200)), []);
10535
9295
  const handleZoomOut = useCallback(() => setZoom((z) => Math.max(z - 25, 50)), []);
10536
9296
  const handlePagePrev = useCallback(() => {
10537
- setCurrentPage((p2) => Math.max(p2 - 1, 1));
9297
+ setCurrentPage((p) => Math.max(p - 1, 1));
10538
9298
  eventBus.emit("UI:DOCUMENT_PAGE_CHANGE", { page: currentPage - 1 });
10539
9299
  }, [eventBus, currentPage]);
10540
9300
  const handlePageNext = useCallback(() => {
10541
9301
  if (totalPages) {
10542
- setCurrentPage((p2) => Math.min(p2 + 1, totalPages));
9302
+ setCurrentPage((p) => Math.min(p + 1, totalPages));
10543
9303
  eventBus.emit("UI:DOCUMENT_PAGE_CHANGE", { page: currentPage + 1 });
10544
9304
  }
10545
9305
  }, [totalPages, eventBus, currentPage]);
@@ -12141,4 +10901,4 @@ function WorldMapTemplate({
12141
10901
  }
12142
10902
  WorldMapTemplate.displayName = "WorldMapTemplate";
12143
10903
 
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 };
10904
+ export { ALL_PRESETS, AR_BOOK_FIELDS, ActionPalette, ActionTile, AuthLayout, BattleBoard, BattleTemplate, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, BuilderBoard, 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 };