@almadar/ui 5.142.0 → 5.144.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.
@@ -6,11 +6,11 @@ import { LucideIcon } from 'lucide-react';
6
6
  import { C as ColorToken, U as UiError, P as Point, L as LinkAction, I as ImageSource } from '../GameAudioProvider-CPGwD49P.js';
7
7
  export { A as AudioManifest, G as GameAudioContext, a as GameAudioContextValue, e as GameAudioControls, b as GameAudioProvider, c as GameAudioProviderProps, R as Rect, S as SoundEntry, f as UseGameAudioOptions, g as useGameAudio, u as useGameAudioContext } from '../GameAudioProvider-CPGwD49P.js';
8
8
  import { SExpr } from '@almadar/evaluator';
9
- import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, g as SpriteFrameDims, R as ResolvedFrame, h as CanvasLighting, i as CanvasPost, a as IsometricUnit, j as CameraState, k as FieldInfo, l as OrbitalTraitInfo, m as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-DAs12kxP.js';
10
- export { B as BoardTile, G as GameAction, n as GamePhase, o as GameState, p as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, q as UnitTrait, r as calculateAttackTargets, s as calculateValidMoves, t as createInitialGameState } from '../avl-schema-parser-DAs12kxP.js';
11
- import { D as DrawableNode } from '../paintDispatch-BQn5Lyx1.js';
12
- import { C as ContentSegment, b as DomLayoutData, d as DomStateNode, V as VisualizerConfig, e as DomTransitionLabel } from '../cn-BDwC4QpH.js';
13
- export { n as cn } from '../cn-BDwC4QpH.js';
9
+ import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, g as SpriteFrameDims, R as ResolvedFrame, h as CanvasLighting, i as CanvasPost, a as IsometricUnit, j as CameraState, k as FieldInfo, l as OrbitalTraitInfo, m as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-BRJ77Yze.js';
10
+ export { B as BoardTile, G as GameAction, n as GamePhase, o as GameState, p as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, q as UnitTrait, r as calculateAttackTargets, s as calculateValidMoves, t as createInitialGameState } from '../avl-schema-parser-BRJ77Yze.js';
11
+ import { D as DrawableNode } from '../paintDispatch-Cb_hQj4Y.js';
12
+ import { C as ContentSegment, b as DomLayoutData, d as DomStateNode, V as VisualizerConfig, e as DomTransitionLabel } from '../cn-DNkqAWZK.js';
13
+ export { n as cn } from '../cn-DNkqAWZK.js';
14
14
  import { b as SlotContent } from '../useUISlots-BesZYMks.js';
15
15
  export { D as DEFAULT_SLOTS, S as SlotAnimation, a as SlotChangeCallback, c as SlotRenderConfig, U as UISlotManager, u as useUISlotManager } from '../useUISlots-BesZYMks.js';
16
16
  export { ALMADAR_DND_MIME, AuthContextValue, AuthUser, CanvasGestureCallbacks, CanvasGestureHandlers, CompileResult, CompileStage, DragReorderResult, DraggablePayload, Extension, ExtensionManifest, FileSystemFile, FileSystemStatus, GitHubRepo, GitHubStatus, HistoryChangeSummary, HistoryTimelineItem, I18nContextValue, I18nProvider, InfiniteScrollOptions, InfiniteScrollResult, LongPressHandlers, LongPressOptions, OpenFile, Positioned, PullToRefreshOptions, PullToRefreshResult, QuerySingletonEntity, QuerySingletonResult, QuerySingletonState, QueryState, RenderInterpolationHandle, RenderInterpolationOptions, RevertResult, SelectedFile, SharedEntityStore, SharedEntityStoreContext, SharedEntitySubscriber, SharedEntityWriter, SwipeGestureOptions, SwipeGestureResult, SwipeHandlers, TapRevealOptions, TapRevealResult, TraitListenSpec, TranslateFunction, UseCanvasGesturesOptions, UseCompileResult, UseDraggableOptions, UseDraggableResult, UseDropZoneOptions, UseDropZoneResult, UseExtensionsOptions, UseExtensionsResult, UseFileEditorOptions, UseFileEditorResult, UseFileSystemResult, UseOrbitalHistoryOptions, UseOrbitalHistoryResult, createSharedEntityStore, createTranslate, parseQueryBinding, runTickFrame, useAgentChat, useAuthContext, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate, useUIEvents, useValidation } from '../hooks/index.js';
@@ -4367,6 +4367,8 @@ interface MenuItem {
4367
4367
  onClick?: () => void;
4368
4368
  /** Event name for pattern compatibility */
4369
4369
  event?: string;
4370
+ /** File URL this item downloads on pick (gesture-driven, `DocumentViewer` precedent). The item's `event` still emits on the bus. */
4371
+ url?: string;
4370
4372
  /** Variant for styling (pattern compatibility) */
4371
4373
  variant?: "default" | "danger";
4372
4374
  /** Sub-menu items */
@@ -6519,7 +6521,7 @@ interface DataGridProps extends DataDndProps {
6519
6521
  */
6520
6522
  look?: "dense" | "spacious" | "striped" | "borderless" | "card-rows";
6521
6523
  }
6522
- declare function DataGrid({ entity, fields, columns, itemActions, maxInlineActions, scrollX, cols, gap, minCardWidth, className, isLoading, error, imageField, selectable, selectionEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, renderItem: schemaRenderItem, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataGridProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
6524
+ declare function DataGrid({ entity, fields, columns, itemActions, maxInlineActions, scrollX, cols, gap, minCardWidth, className, isLoading, error, imageField, selectable, selectionEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, renderItem: schemaRenderItem, dragGroup, accepts, sortable, dropEvent, reorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataGridProps): string | number | bigint | boolean | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | React__default.JSX.Element | null | undefined;
6523
6525
  declare namespace DataGrid {
6524
6526
  var displayName: string;
6525
6527
  }
@@ -6672,7 +6674,7 @@ interface DataListProps extends DataDndProps {
6672
6674
  */
6673
6675
  look?: "dense" | "spacious" | "striped" | "borderless" | "card-rows";
6674
6676
  }
6675
- declare function DataList({ entity, fields, columns, itemActions, maxInlineActions, itemClickEvent, gap, variant, groupBy, senderField, senderLabelField, currentUser, emptyMessage, className, isLoading, error, reorderable: _reorderable, reorderEvent: _reorderEvent, swipeLeftEvent: _swipeLeftEvent, swipeLeftActions: _swipeLeftActions, swipeRightEvent: _swipeRightEvent, swipeRightActions: _swipeRightActions, longPressEvent: _longPressEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, sortBy, sortDirection, renderItem: schemaRenderItem, dragGroup, accepts, sortable: sortableProp, dropEvent, reorderEvent: dndReorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataListProps): string | number | bigint | boolean | React__default.JSX.Element | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | null | undefined;
6677
+ declare function DataList({ entity, fields, columns, itemActions, maxInlineActions, itemClickEvent, gap, variant, groupBy, senderField, senderLabelField, currentUser, emptyMessage, className, isLoading, error, reorderable: _reorderable, reorderEvent: _reorderEvent, swipeLeftEvent: _swipeLeftEvent, swipeLeftActions: _swipeLeftActions, swipeRightEvent: _swipeRightEvent, swipeRightActions: _swipeRightActions, longPressEvent: _longPressEvent, infiniteScroll, loadMoreEvent, hasMore, children, pageSize, sortBy, sortDirection, renderItem: schemaRenderItem, dragGroup, accepts, sortable: sortableProp, dropEvent, reorderEvent: dndReorderEvent, positionEvent, dndItemIdField, dndRoot, look, }: DataListProps): string | number | bigint | boolean | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | React__default.JSX.Element | null | undefined;
6676
6678
  declare namespace DataList {
6677
6679
  var displayName: string;
6678
6680
  }
@@ -12,7 +12,7 @@ import { useTranslate } from '@almadar/ui/hooks';
12
12
  import { useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
14
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
15
- import { mergeEntityFrame, isInlineTrait, isRenderBindingMarker } from '@almadar/core';
15
+ import { mergeEntityFrame, isInlineTrait, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
16
16
  import { createPortal } from 'react-dom';
17
17
  import { wrapCallbackForEvent } from '@almadar/runtime/ui';
18
18
  import { Link, Outlet, useLocation } from 'react-router-dom';
@@ -1451,6 +1451,10 @@ var init_Icon = __esm({
1451
1451
  function isTilesheet(a) {
1452
1452
  return typeof a.tileWidth === "number";
1453
1453
  }
1454
+ function isSpriteSheetAtlas(a) {
1455
+ const s = a;
1456
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
1457
+ }
1454
1458
  function getAtlas(url, onReady) {
1455
1459
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
1456
1460
  atlasCache.set(url, void 0);
@@ -1490,6 +1494,7 @@ function subRectFor(atlas, sprite) {
1490
1494
  sh: atlas.tileHeight
1491
1495
  };
1492
1496
  }
1497
+ if (isSpriteSheetAtlas(atlas)) return null;
1493
1498
  const st = atlas.subTextures[sprite];
1494
1499
  if (!st) return null;
1495
1500
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -1799,7 +1804,7 @@ var init_Button = __esm({
1799
1804
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
1800
1805
  children: [
1801
1806
  isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
1802
- children || label,
1807
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
1803
1808
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
1804
1809
  ]
1805
1810
  }
@@ -15946,6 +15951,138 @@ var init_contract = __esm({
15946
15951
  "lib/drawable/contract.ts"() {
15947
15952
  }
15948
15953
  });
15954
+
15955
+ // lib/spriteSheetConstants.ts
15956
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
15957
+ var init_spriteSheetConstants = __esm({
15958
+ "lib/spriteSheetConstants.ts"() {
15959
+ SHEET_COLUMNS = 8;
15960
+ SPRITE_SHEET_LAYOUT = {
15961
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
15962
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
15963
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
15964
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
15965
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
15966
+ };
15967
+ }
15968
+ });
15969
+ function isAnimationName(name) {
15970
+ return ANIMATION_NAMES.includes(name);
15971
+ }
15972
+ function inferDirection(dx, dy) {
15973
+ if (dx === 0 && dy === 0) return "se";
15974
+ if (dx >= 0 && dy >= 0) return "se";
15975
+ if (dx <= 0 && dy >= 0) return "sw";
15976
+ if (dx >= 0 && dy <= 0) return "ne";
15977
+ return "nw";
15978
+ }
15979
+ function resolveSheetDirection(facing) {
15980
+ switch (facing) {
15981
+ case "se":
15982
+ return { sheetDir: "se", flipX: false };
15983
+ case "sw":
15984
+ return { sheetDir: "sw", flipX: false };
15985
+ case "ne":
15986
+ return { sheetDir: "sw", flipX: true };
15987
+ case "nw":
15988
+ return { sheetDir: "se", flipX: true };
15989
+ }
15990
+ }
15991
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
15992
+ return {
15993
+ sx: frame % columns * frameWidth,
15994
+ sy: row * frameHeight,
15995
+ sw: frameWidth,
15996
+ sh: frameHeight
15997
+ };
15998
+ }
15999
+ function getCurrentFrameFromDef(def, elapsed) {
16000
+ const frameDuration = 1e3 / def.frameRate;
16001
+ const totalDuration = def.frames * frameDuration;
16002
+ if (def.loop) {
16003
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
16004
+ return { frame: frame2, finished: false };
16005
+ }
16006
+ if (elapsed >= totalDuration) {
16007
+ return { frame: def.frames - 1, finished: true };
16008
+ }
16009
+ const frame = Math.floor(elapsed / frameDuration);
16010
+ return { frame, finished: false };
16011
+ }
16012
+ function getCurrentFrame(animName, elapsed) {
16013
+ return getCurrentFrameFromDef(SPRITE_SHEET_LAYOUT[animName], elapsed);
16014
+ }
16015
+ function resolveFrame(sheetUrls, frameDims, animState) {
16016
+ if (!sheetUrls) return null;
16017
+ const real = sheetUrls[animState.direction];
16018
+ const { sheetUrl, flipX } = real ? { sheetUrl: real, flipX: false } : (() => {
16019
+ const legacy = resolveSheetDirection(animState.direction);
16020
+ return { sheetUrl: sheetUrls[legacy.sheetDir], flipX: legacy.flipX };
16021
+ })();
16022
+ if (!sheetUrl) return null;
16023
+ const def = SPRITE_SHEET_LAYOUT[animState.animation];
16024
+ const { frame } = getCurrentFrame(animState.animation, animState.elapsed);
16025
+ const rect = frameRect(frame, def.row, def.frames, frameDims.width, frameDims.height);
16026
+ return {
16027
+ sheetUrl,
16028
+ sx: rect.sx,
16029
+ sy: rect.sy,
16030
+ sw: rect.sw,
16031
+ sh: rect.sh,
16032
+ flipX
16033
+ };
16034
+ }
16035
+ function createUnitAnimationState(unitId) {
16036
+ return {
16037
+ unitId,
16038
+ animation: "idle",
16039
+ direction: "se",
16040
+ frame: 0,
16041
+ elapsed: 0,
16042
+ queuedAnimation: null,
16043
+ finished: false
16044
+ };
16045
+ }
16046
+ function transitionAnimation(state, newAnim, direction) {
16047
+ if (state.animation === "death" && state.finished) return state;
16048
+ if (state.animation === newAnim && SPRITE_SHEET_LAYOUT[newAnim].loop) {
16049
+ return direction ? { ...state, direction } : state;
16050
+ }
16051
+ return {
16052
+ ...state,
16053
+ animation: newAnim,
16054
+ direction: direction ?? state.direction,
16055
+ frame: 0,
16056
+ elapsed: 0,
16057
+ queuedAnimation: null,
16058
+ finished: false
16059
+ };
16060
+ }
16061
+ function tickAnimationState(state, deltaMs) {
16062
+ const newElapsed = state.elapsed + deltaMs;
16063
+ const { frame, finished } = getCurrentFrame(state.animation, newElapsed);
16064
+ const def = SPRITE_SHEET_LAYOUT[state.animation];
16065
+ if (finished && !def.loop && !state.finished) {
16066
+ if (state.animation === "death") {
16067
+ return { ...state, elapsed: newElapsed, frame, finished: true };
16068
+ }
16069
+ const nextAnim = state.queuedAnimation ?? "idle";
16070
+ return {
16071
+ ...state,
16072
+ animation: nextAnim,
16073
+ elapsed: 0,
16074
+ frame: 0,
16075
+ queuedAnimation: null,
16076
+ finished: false
16077
+ };
16078
+ }
16079
+ return { ...state, elapsed: newElapsed, frame, finished };
16080
+ }
16081
+ var init_spriteAnimation = __esm({
16082
+ "lib/spriteAnimation.ts"() {
16083
+ init_spriteSheetConstants();
16084
+ }
16085
+ });
15949
16086
  function warnMissingOnce(reason, node) {
15950
16087
  const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
15951
16088
  if (loggedMissing.has(key)) return;
@@ -15963,6 +16100,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
15963
16100
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
15964
16101
  painter.restore();
15965
16102
  }
16103
+ function isAnimatedSprite(node) {
16104
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
16105
+ }
15966
16106
  function DrawSprite(_props) {
15967
16107
  return null;
15968
16108
  }
@@ -15971,6 +16111,7 @@ var init_DrawSprite = __esm({
15971
16111
  "components/game/atoms/DrawSprite.tsx"() {
15972
16112
  "use client";
15973
16113
  init_atlasSlice();
16114
+ init_spriteAnimation();
15974
16115
  init_imageCache();
15975
16116
  init_contract();
15976
16117
  spriteLog = createLogger("almadar:ui:draw-sprite");
@@ -15984,6 +16125,23 @@ var init_DrawSprite = __esm({
15984
16125
  return;
15985
16126
  }
15986
16127
  let src = typeof node.frame === "object" ? node.frame : void 0;
16128
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
16129
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
16130
+ if (!atlas) {
16131
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
16132
+ return;
16133
+ }
16134
+ if (isSpriteSheetAtlas(atlas)) {
16135
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
16136
+ if (!def) {
16137
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
16138
+ return;
16139
+ }
16140
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
16141
+ const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
16142
+ src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
16143
+ }
16144
+ }
15987
16145
  if (!src && isAtlasAsset(node.asset)) {
15988
16146
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
15989
16147
  if (!atlas) {
@@ -16269,6 +16427,83 @@ var init_DrawText = __esm({
16269
16427
  };
16270
16428
  }
16271
16429
  });
16430
+ function isAnimatedGroup(node) {
16431
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
16432
+ }
16433
+ function DrawGroup(props) {
16434
+ const register = useContext(DrawableRegistryContext);
16435
+ if (register) register({ ...props, type: "draw-group" });
16436
+ return null;
16437
+ }
16438
+ var init_DrawGroup = __esm({
16439
+ "components/game/atoms/DrawGroup.tsx"() {
16440
+ "use client";
16441
+ init_registry();
16442
+ }
16443
+ });
16444
+ function applyMeshAnimation(node, timeMs) {
16445
+ const anim = node.animation;
16446
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
16447
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
16448
+ const cycle = timeMs / anim.durationMs;
16449
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
16450
+ const trackValue = (key) => {
16451
+ const defined = frames.filter((f3) => f3[key] !== void 0);
16452
+ if (defined.length === 0) return void 0;
16453
+ let prev;
16454
+ let next;
16455
+ for (const f3 of defined) {
16456
+ if (f3.at <= t) prev = f3;
16457
+ else if (!next) next = f3;
16458
+ }
16459
+ if (!prev) return defined[0][key];
16460
+ if (!next) return prev[key];
16461
+ const span = next.at - prev.at;
16462
+ const k = span > 0 ? (t - prev.at) / span : 1;
16463
+ const a = prev[key];
16464
+ const b = next[key];
16465
+ if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
16466
+ return a;
16467
+ };
16468
+ const num2 = {};
16469
+ for (const key of NUMERIC_TRACKS2) {
16470
+ const v = trackValue(key);
16471
+ if (v !== void 0) num2[key] = v;
16472
+ }
16473
+ return {
16474
+ offset: [num2.offsetX ?? 0, num2.offsetY ?? 0, num2.offsetZ ?? 0],
16475
+ rotate: [num2.rotateX ?? 0, num2.rotateY ?? 0, num2.rotateZ ?? 0],
16476
+ scale: num2.scale ?? 1,
16477
+ opacity: num2.opacity,
16478
+ emissiveIntensity: num2.emissiveIntensity,
16479
+ color: trackValue("color"),
16480
+ emissive: trackValue("emissive")
16481
+ };
16482
+ }
16483
+ function DrawMesh(props) {
16484
+ const register = useContext(DrawableRegistryContext);
16485
+ if (register) register({ ...props, type: "draw-mesh" });
16486
+ return null;
16487
+ }
16488
+ var lerp2, NUMERIC_TRACKS2;
16489
+ var init_DrawMesh = __esm({
16490
+ "components/game/atoms/DrawMesh.tsx"() {
16491
+ "use client";
16492
+ init_registry();
16493
+ lerp2 = (a, b, k) => a + (b - a) * k;
16494
+ NUMERIC_TRACKS2 = [
16495
+ "offsetX",
16496
+ "offsetY",
16497
+ "offsetZ",
16498
+ "rotateX",
16499
+ "rotateY",
16500
+ "rotateZ",
16501
+ "scale",
16502
+ "opacity",
16503
+ "emissiveIntensity"
16504
+ ];
16505
+ }
16506
+ });
16272
16507
 
16273
16508
  // components/game/molecules/DrawSpriteLayer.tsx
16274
16509
  function DrawSpriteLayer(_props) {
@@ -16335,18 +16570,22 @@ function paintDrawable(painter, node, dctx) {
16335
16570
  if (!isValidScenePos(node.position)) break;
16336
16571
  if (!Array.isArray(node.items)) break;
16337
16572
  const p = dctx.projector.project(node.position);
16573
+ const anim = dctx.time > 0 && isAnimatedGroup(node) ? applyMeshAnimation(node, dctx.time) : null;
16574
+ const tw = dctx.projector.tileWidth;
16338
16575
  painter.save();
16339
- painter.translate(p.x, p.y);
16340
- if (node.scale !== void 0) painter.scale(node.scale, node.scale);
16341
- if (node.rotate !== void 0) painter.rotate(node.rotate);
16342
- if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
16576
+ painter.translate(p.x + (anim ? anim.offset[0] * tw : 0), p.y + (anim ? anim.offset[1] * tw : 0));
16577
+ const scale = (node.scale ?? 1) * (anim?.scale ?? 1);
16578
+ if (scale !== 1) painter.scale(scale, scale);
16579
+ const rotate = (node.rotate ?? 0) + (node.rotation?.[2] ?? 0) + (anim?.rotate[2] ?? 0);
16580
+ if (rotate !== 0) painter.rotate(rotate);
16581
+ const opacity = (node.opacity ?? 1) * (anim?.opacity ?? 1);
16582
+ if (opacity !== 1) painter.setAlpha(opacity);
16343
16583
  if (node.clip) {
16344
- const tw = dctx.projector.tileWidth;
16345
16584
  painter.scale(tw, tw);
16346
16585
  painter.clipPath(node.clip);
16347
16586
  painter.scale(1 / tw, 1 / tw);
16348
16587
  }
16349
- const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
16588
+ const childCtx = scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * scale } : dctx;
16350
16589
  for (const item of node.items) paintDrawable(painter, item, childCtx);
16351
16590
  painter.restore();
16352
16591
  break;
@@ -16372,6 +16611,8 @@ var init_paintDispatch = __esm({
16372
16611
  init_DrawSprite();
16373
16612
  init_DrawShape();
16374
16613
  init_DrawText();
16614
+ init_DrawGroup();
16615
+ init_DrawMesh();
16375
16616
  init_DrawSpriteLayer();
16376
16617
  init_DrawShapeLayer();
16377
16618
  init_DrawTextLayer();
@@ -16618,8 +16859,11 @@ function Canvas2D({
16618
16859
  const miniMapHeight = gridExtent.height || 10;
16619
16860
  const drawableIsAnimated = (node) => {
16620
16861
  if (node.type === "draw-shape") return isAnimatedShape(node);
16621
- if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
16862
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
16863
+ if (node.type === "draw-group")
16864
+ return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
16622
16865
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
16866
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
16623
16867
  return false;
16624
16868
  };
16625
16869
  const animRafRef = useRef(0);
@@ -16960,6 +17204,8 @@ var init_Canvas2D = __esm({
16960
17204
  init_projector();
16961
17205
  init_paintDispatch();
16962
17206
  init_DrawShape();
17207
+ init_DrawSprite();
17208
+ init_DrawGroup();
16963
17209
  init_registry();
16964
17210
  init_hitTest();
16965
17211
  init_isometric();
@@ -17032,6 +17278,7 @@ function Canvas({
17032
17278
  cameraMode: to3DCameraMode(camera?.mode),
17033
17279
  ...zoom !== void 0 ? { scale: zoom } : {},
17034
17280
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17281
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17035
17282
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17036
17283
  unitScale,
17037
17284
  backgroundColor,
@@ -20487,6 +20734,14 @@ var init_DashboardLayout = __esm({
20487
20734
  NavLinkBottom.displayName = "NavLinkBottom";
20488
20735
  }
20489
20736
  });
20737
+ function downloadItemUrl(url, label) {
20738
+ const a = document.createElement("a");
20739
+ a.href = url;
20740
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
20741
+ document.body.appendChild(a);
20742
+ a.click();
20743
+ a.remove();
20744
+ }
20490
20745
  function computeMenuStyle(position, triggerRect) {
20491
20746
  const isTop = position.startsWith("top");
20492
20747
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -20539,6 +20794,7 @@ function SubMenu({
20539
20794
  onClick: () => {
20540
20795
  if (item.disabled) return;
20541
20796
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20797
+ if (item.url) downloadItemUrl(item.url, item.label);
20542
20798
  item.onClick?.();
20543
20799
  },
20544
20800
  "aria-disabled": item.disabled || void 0,
@@ -20692,6 +20948,7 @@ var init_Menu = __esm({
20692
20948
  setActiveSubMenu(itemId);
20693
20949
  } else {
20694
20950
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
20951
+ if (item.url) downloadItemUrl(item.url, item.label);
20695
20952
  item.onClick?.();
20696
20953
  setIsOpen(false);
20697
20954
  }
@@ -20727,12 +20984,19 @@ var init_Menu = __esm({
20727
20984
  "bottom-end": "bottom-start"
20728
20985
  };
20729
20986
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
20730
- const triggerChild = React77__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
20731
- const triggerElement = React77__default.cloneElement(
20732
- triggerChild,
20987
+ const triggerElement = React77__default.isValidElement(trigger) ? React77__default.cloneElement(trigger, {
20988
+ ref: triggerRef,
20989
+ onClick: handleToggle
20990
+ }) : /* @__PURE__ */ jsx(
20991
+ Box,
20733
20992
  {
20734
- ref: triggerRef,
20735
- onClick: handleToggle
20993
+ as: "span",
20994
+ ref: (el) => {
20995
+ triggerRef.current = el;
20996
+ },
20997
+ onClick: handleToggle,
20998
+ className: "inline-flex",
20999
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
20736
21000
  }
20737
21001
  );
20738
21002
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
@@ -26417,134 +26681,6 @@ var init_makeAsset = __esm({
26417
26681
  }
26418
26682
  });
26419
26683
 
26420
- // lib/spriteSheetConstants.ts
26421
- var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
26422
- var init_spriteSheetConstants = __esm({
26423
- "lib/spriteSheetConstants.ts"() {
26424
- SHEET_COLUMNS = 8;
26425
- SPRITE_SHEET_LAYOUT = {
26426
- idle: { row: 0, frames: 4, frameRate: 6, loop: true },
26427
- walk: { row: 1, frames: 8, frameRate: 10, loop: true },
26428
- attack: { row: 2, frames: 6, frameRate: 12, loop: false },
26429
- hit: { row: 3, frames: 3, frameRate: 8, loop: false },
26430
- death: { row: 4, frames: 6, frameRate: 8, loop: false }
26431
- };
26432
- }
26433
- });
26434
-
26435
- // lib/spriteAnimation.ts
26436
- function inferDirection(dx, dy) {
26437
- if (dx === 0 && dy === 0) return "se";
26438
- if (dx >= 0 && dy >= 0) return "se";
26439
- if (dx <= 0 && dy >= 0) return "sw";
26440
- if (dx >= 0 && dy <= 0) return "ne";
26441
- return "nw";
26442
- }
26443
- function resolveSheetDirection(facing) {
26444
- switch (facing) {
26445
- case "se":
26446
- return { sheetDir: "se", flipX: false };
26447
- case "sw":
26448
- return { sheetDir: "sw", flipX: false };
26449
- case "ne":
26450
- return { sheetDir: "sw", flipX: true };
26451
- case "nw":
26452
- return { sheetDir: "se", flipX: true };
26453
- }
26454
- }
26455
- function frameRect(frame, row, columns, frameWidth, frameHeight) {
26456
- return {
26457
- sx: frame % columns * frameWidth,
26458
- sy: row * frameHeight,
26459
- sw: frameWidth,
26460
- sh: frameHeight
26461
- };
26462
- }
26463
- function getCurrentFrameFromDef(def, elapsed) {
26464
- const frameDuration = 1e3 / def.frameRate;
26465
- const totalDuration = def.frames * frameDuration;
26466
- if (def.loop) {
26467
- const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
26468
- return { frame: frame2, finished: false };
26469
- }
26470
- if (elapsed >= totalDuration) {
26471
- return { frame: def.frames - 1, finished: true };
26472
- }
26473
- const frame = Math.floor(elapsed / frameDuration);
26474
- return { frame, finished: false };
26475
- }
26476
- function getCurrentFrame(animName, elapsed) {
26477
- return getCurrentFrameFromDef(SPRITE_SHEET_LAYOUT[animName], elapsed);
26478
- }
26479
- function resolveFrame(sheetUrls, frameDims, animState) {
26480
- if (!sheetUrls) return null;
26481
- const { sheetDir, flipX } = resolveSheetDirection(animState.direction);
26482
- const sheetUrl = sheetUrls[sheetDir];
26483
- if (!sheetUrl) return null;
26484
- const def = SPRITE_SHEET_LAYOUT[animState.animation];
26485
- const { frame } = getCurrentFrame(animState.animation, animState.elapsed);
26486
- const rect = frameRect(frame, def.row, def.frames, frameDims.width, frameDims.height);
26487
- return {
26488
- sheetUrl,
26489
- sx: rect.sx,
26490
- sy: rect.sy,
26491
- sw: rect.sw,
26492
- sh: rect.sh,
26493
- flipX
26494
- };
26495
- }
26496
- function createUnitAnimationState(unitId) {
26497
- return {
26498
- unitId,
26499
- animation: "idle",
26500
- direction: "se",
26501
- frame: 0,
26502
- elapsed: 0,
26503
- queuedAnimation: null,
26504
- finished: false
26505
- };
26506
- }
26507
- function transitionAnimation(state, newAnim, direction) {
26508
- if (state.animation === "death" && state.finished) return state;
26509
- if (state.animation === newAnim && SPRITE_SHEET_LAYOUT[newAnim].loop) {
26510
- return direction ? { ...state, direction } : state;
26511
- }
26512
- return {
26513
- ...state,
26514
- animation: newAnim,
26515
- direction: direction ?? state.direction,
26516
- frame: 0,
26517
- elapsed: 0,
26518
- queuedAnimation: null,
26519
- finished: false
26520
- };
26521
- }
26522
- function tickAnimationState(state, deltaMs) {
26523
- const newElapsed = state.elapsed + deltaMs;
26524
- const { frame, finished } = getCurrentFrame(state.animation, newElapsed);
26525
- const def = SPRITE_SHEET_LAYOUT[state.animation];
26526
- if (finished && !def.loop && !state.finished) {
26527
- if (state.animation === "death") {
26528
- return { ...state, elapsed: newElapsed, frame, finished: true };
26529
- }
26530
- const nextAnim = state.queuedAnimation ?? "idle";
26531
- return {
26532
- ...state,
26533
- animation: nextAnim,
26534
- elapsed: 0,
26535
- frame: 0,
26536
- queuedAnimation: null,
26537
- finished: false
26538
- };
26539
- }
26540
- return { ...state, elapsed: newElapsed, frame, finished };
26541
- }
26542
- var init_spriteAnimation = __esm({
26543
- "lib/spriteAnimation.ts"() {
26544
- init_spriteSheetConstants();
26545
- }
26546
- });
26547
-
26548
26684
  // lib/gameShared.ts
26549
26685
  var init_gameShared = __esm({
26550
26686
  "lib/gameShared.ts"() {
@@ -40274,28 +40410,6 @@ var init_DetailPanel = __esm({
40274
40410
  DetailPanel.displayName = "DetailPanel";
40275
40411
  }
40276
40412
  });
40277
- function DrawGroup(props) {
40278
- const register = useContext(DrawableRegistryContext);
40279
- if (register) register({ ...props, type: "draw-group" });
40280
- return null;
40281
- }
40282
- var init_DrawGroup = __esm({
40283
- "components/game/atoms/DrawGroup.tsx"() {
40284
- "use client";
40285
- init_registry();
40286
- }
40287
- });
40288
- function DrawMesh(props) {
40289
- const register = useContext(DrawableRegistryContext);
40290
- if (register) register({ ...props, type: "draw-mesh" });
40291
- return null;
40292
- }
40293
- var init_DrawMesh = __esm({
40294
- "components/game/atoms/DrawMesh.tsx"() {
40295
- "use client";
40296
- init_registry();
40297
- }
40298
- });
40299
40413
  function extractTitle(children) {
40300
40414
  if (!React77__default.isValidElement(children)) return void 0;
40301
40415
  const props = children.props;