@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.
@@ -4950,6 +4950,10 @@ var init_Icon = __esm({
4950
4950
  function isTilesheet(a) {
4951
4951
  return typeof a.tileWidth === "number";
4952
4952
  }
4953
+ function isSpriteSheetAtlas(a) {
4954
+ const s = a;
4955
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
4956
+ }
4953
4957
  function getAtlas(url, onReady) {
4954
4958
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
4955
4959
  atlasCache.set(url, void 0);
@@ -4989,6 +4993,7 @@ function subRectFor(atlas, sprite) {
4989
4993
  sh: atlas.tileHeight
4990
4994
  };
4991
4995
  }
4996
+ if (isSpriteSheetAtlas(atlas)) return null;
4992
4997
  const st = atlas.subTextures[sprite];
4993
4998
  if (!st) return null;
4994
4999
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -5298,7 +5303,7 @@ var init_Button = __esm({
5298
5303
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
5299
5304
  children: [
5300
5305
  isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons2.Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
5301
- children || label,
5306
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
5302
5307
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
5303
5308
  ]
5304
5309
  }
@@ -11042,6 +11047,34 @@ var init_isometric = __esm({
11042
11047
  };
11043
11048
  }
11044
11049
  });
11050
+ function isAnimationName(name) {
11051
+ return core.ANIMATION_NAMES.includes(name);
11052
+ }
11053
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
11054
+ return {
11055
+ sx: frame % columns * frameWidth,
11056
+ sy: row * frameHeight,
11057
+ sw: frameWidth,
11058
+ sh: frameHeight
11059
+ };
11060
+ }
11061
+ function getCurrentFrameFromDef(def, elapsed) {
11062
+ const frameDuration = 1e3 / def.frameRate;
11063
+ const totalDuration = def.frames * frameDuration;
11064
+ if (def.loop) {
11065
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
11066
+ return { frame: frame2, finished: false };
11067
+ }
11068
+ if (elapsed >= totalDuration) {
11069
+ return { frame: def.frames - 1, finished: true };
11070
+ }
11071
+ const frame = Math.floor(elapsed / frameDuration);
11072
+ return { frame, finished: false };
11073
+ }
11074
+ var init_spriteAnimation = __esm({
11075
+ "lib/spriteAnimation.ts"() {
11076
+ }
11077
+ });
11045
11078
 
11046
11079
  // lib/gameShared.ts
11047
11080
  var init_gameShared = __esm({
@@ -12902,6 +12935,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
12902
12935
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
12903
12936
  painter.restore();
12904
12937
  }
12938
+ function isAnimatedSprite(node) {
12939
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
12940
+ }
12905
12941
  function DrawSprite(_props) {
12906
12942
  return null;
12907
12943
  }
@@ -12910,6 +12946,7 @@ var init_DrawSprite = __esm({
12910
12946
  "components/game/atoms/DrawSprite.tsx"() {
12911
12947
  "use client";
12912
12948
  init_atlasSlice();
12949
+ init_spriteAnimation();
12913
12950
  init_imageCache();
12914
12951
  init_contract();
12915
12952
  spriteLog = logger.createLogger("almadar:ui:draw-sprite");
@@ -12923,6 +12960,23 @@ var init_DrawSprite = __esm({
12923
12960
  return;
12924
12961
  }
12925
12962
  let src = typeof node.frame === "object" ? node.frame : void 0;
12963
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
12964
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12965
+ if (!atlas) {
12966
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
12967
+ return;
12968
+ }
12969
+ if (isSpriteSheetAtlas(atlas)) {
12970
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
12971
+ if (!def) {
12972
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
12973
+ return;
12974
+ }
12975
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
12976
+ const r2 = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
12977
+ src = { x: r2.sx, y: r2.sy, w: r2.sw, h: r2.sh };
12978
+ }
12979
+ }
12926
12980
  if (!src && isAtlasAsset(node.asset)) {
12927
12981
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12928
12982
  if (!atlas) {
@@ -13214,6 +13268,83 @@ var init_DrawText = __esm({
13214
13268
  };
13215
13269
  }
13216
13270
  });
13271
+ function isAnimatedGroup(node) {
13272
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
13273
+ }
13274
+ function DrawGroup(props) {
13275
+ const register = React94.useContext(DrawableRegistryContext);
13276
+ if (register) register({ ...props, type: "draw-group" });
13277
+ return null;
13278
+ }
13279
+ var init_DrawGroup = __esm({
13280
+ "components/game/atoms/DrawGroup.tsx"() {
13281
+ "use client";
13282
+ init_registry();
13283
+ }
13284
+ });
13285
+ function applyMeshAnimation(node, timeMs) {
13286
+ const anim = node.animation;
13287
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
13288
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
13289
+ const cycle = timeMs / anim.durationMs;
13290
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
13291
+ const trackValue = (key) => {
13292
+ const defined = frames.filter((f3) => f3[key] !== void 0);
13293
+ if (defined.length === 0) return void 0;
13294
+ let prev;
13295
+ let next;
13296
+ for (const f3 of defined) {
13297
+ if (f3.at <= t) prev = f3;
13298
+ else if (!next) next = f3;
13299
+ }
13300
+ if (!prev) return defined[0][key];
13301
+ if (!next) return prev[key];
13302
+ const span = next.at - prev.at;
13303
+ const k = span > 0 ? (t - prev.at) / span : 1;
13304
+ const a = prev[key];
13305
+ const b = next[key];
13306
+ if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
13307
+ return a;
13308
+ };
13309
+ const num = {};
13310
+ for (const key of NUMERIC_TRACKS2) {
13311
+ const v = trackValue(key);
13312
+ if (v !== void 0) num[key] = v;
13313
+ }
13314
+ return {
13315
+ offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
13316
+ rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
13317
+ scale: num.scale ?? 1,
13318
+ opacity: num.opacity,
13319
+ emissiveIntensity: num.emissiveIntensity,
13320
+ color: trackValue("color"),
13321
+ emissive: trackValue("emissive")
13322
+ };
13323
+ }
13324
+ function DrawMesh(props) {
13325
+ const register = React94.useContext(DrawableRegistryContext);
13326
+ if (register) register({ ...props, type: "draw-mesh" });
13327
+ return null;
13328
+ }
13329
+ var lerp2, NUMERIC_TRACKS2;
13330
+ var init_DrawMesh = __esm({
13331
+ "components/game/atoms/DrawMesh.tsx"() {
13332
+ "use client";
13333
+ init_registry();
13334
+ lerp2 = (a, b, k) => a + (b - a) * k;
13335
+ NUMERIC_TRACKS2 = [
13336
+ "offsetX",
13337
+ "offsetY",
13338
+ "offsetZ",
13339
+ "rotateX",
13340
+ "rotateY",
13341
+ "rotateZ",
13342
+ "scale",
13343
+ "opacity",
13344
+ "emissiveIntensity"
13345
+ ];
13346
+ }
13347
+ });
13217
13348
 
13218
13349
  // components/game/molecules/DrawSpriteLayer.tsx
13219
13350
  function DrawSpriteLayer(_props) {
@@ -13280,18 +13411,22 @@ function paintDrawable(painter, node, dctx) {
13280
13411
  if (!isValidScenePos(node.position)) break;
13281
13412
  if (!Array.isArray(node.items)) break;
13282
13413
  const p = dctx.projector.project(node.position);
13414
+ const anim = dctx.time > 0 && isAnimatedGroup(node) ? applyMeshAnimation(node, dctx.time) : null;
13415
+ const tw = dctx.projector.tileWidth;
13283
13416
  painter.save();
13284
- painter.translate(p.x, p.y);
13285
- if (node.scale !== void 0) painter.scale(node.scale, node.scale);
13286
- if (node.rotate !== void 0) painter.rotate(node.rotate);
13287
- if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
13417
+ painter.translate(p.x + (anim ? anim.offset[0] * tw : 0), p.y + (anim ? anim.offset[1] * tw : 0));
13418
+ const scale = (node.scale ?? 1) * (anim?.scale ?? 1);
13419
+ if (scale !== 1) painter.scale(scale, scale);
13420
+ const rotate = (node.rotate ?? 0) + (node.rotation?.[2] ?? 0) + (anim?.rotate[2] ?? 0);
13421
+ if (rotate !== 0) painter.rotate(rotate);
13422
+ const opacity = (node.opacity ?? 1) * (anim?.opacity ?? 1);
13423
+ if (opacity !== 1) painter.setAlpha(opacity);
13288
13424
  if (node.clip) {
13289
- const tw = dctx.projector.tileWidth;
13290
13425
  painter.scale(tw, tw);
13291
13426
  painter.clipPath(node.clip);
13292
13427
  painter.scale(1 / tw, 1 / tw);
13293
13428
  }
13294
- const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
13429
+ const childCtx = scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * scale } : dctx;
13295
13430
  for (const item of node.items) paintDrawable(painter, item, childCtx);
13296
13431
  painter.restore();
13297
13432
  break;
@@ -13317,6 +13452,8 @@ var init_paintDispatch = __esm({
13317
13452
  init_DrawSprite();
13318
13453
  init_DrawShape();
13319
13454
  init_DrawText();
13455
+ init_DrawGroup();
13456
+ init_DrawMesh();
13320
13457
  init_DrawSpriteLayer();
13321
13458
  init_DrawShapeLayer();
13322
13459
  init_DrawTextLayer();
@@ -13563,8 +13700,11 @@ function Canvas2D({
13563
13700
  const miniMapHeight = gridExtent.height || 10;
13564
13701
  const drawableIsAnimated = (node) => {
13565
13702
  if (node.type === "draw-shape") return isAnimatedShape(node);
13566
- if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13703
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
13704
+ if (node.type === "draw-group")
13705
+ return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13567
13706
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13707
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13568
13708
  return false;
13569
13709
  };
13570
13710
  const animRafRef = React94.useRef(0);
@@ -13905,6 +14045,8 @@ var init_Canvas2D = __esm({
13905
14045
  init_projector();
13906
14046
  init_paintDispatch();
13907
14047
  init_DrawShape();
14048
+ init_DrawSprite();
14049
+ init_DrawGroup();
13908
14050
  init_registry();
13909
14051
  init_hitTest();
13910
14052
  init_isometric();
@@ -13977,6 +14119,7 @@ function Canvas({
13977
14119
  cameraMode: to3DCameraMode(camera?.mode),
13978
14120
  ...zoom !== void 0 ? { scale: zoom } : {},
13979
14121
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14122
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
13980
14123
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
13981
14124
  unitScale,
13982
14125
  backgroundColor,
@@ -25246,6 +25389,14 @@ var init_DashboardLayout = __esm({
25246
25389
  NavLinkBottom.displayName = "NavLinkBottom";
25247
25390
  }
25248
25391
  });
25392
+ function downloadItemUrl(url, label) {
25393
+ const a = document.createElement("a");
25394
+ a.href = url;
25395
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
25396
+ document.body.appendChild(a);
25397
+ a.click();
25398
+ a.remove();
25399
+ }
25249
25400
  function computeMenuStyle(position, triggerRect) {
25250
25401
  const isTop = position.startsWith("top");
25251
25402
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -25298,6 +25449,7 @@ function SubMenu({
25298
25449
  onClick: () => {
25299
25450
  if (item.disabled) return;
25300
25451
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25452
+ if (item.url) downloadItemUrl(item.url, item.label);
25301
25453
  item.onClick?.();
25302
25454
  },
25303
25455
  "aria-disabled": item.disabled || void 0,
@@ -25451,6 +25603,7 @@ var init_Menu = __esm({
25451
25603
  setActiveSubMenu(itemId);
25452
25604
  } else {
25453
25605
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25606
+ if (item.url) downloadItemUrl(item.url, item.label);
25454
25607
  item.onClick?.();
25455
25608
  setIsOpen(false);
25456
25609
  }
@@ -25486,12 +25639,19 @@ var init_Menu = __esm({
25486
25639
  "bottom-end": "bottom-start"
25487
25640
  };
25488
25641
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
25489
- const triggerChild = React94__namespace.default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", as: "span", children: trigger });
25490
- const triggerElement = React94__namespace.default.cloneElement(
25491
- triggerChild,
25642
+ const triggerElement = React94__namespace.default.isValidElement(trigger) ? React94__namespace.default.cloneElement(trigger, {
25643
+ ref: triggerRef,
25644
+ onClick: handleToggle
25645
+ }) : /* @__PURE__ */ jsxRuntime.jsx(
25646
+ Box,
25492
25647
  {
25493
- ref: triggerRef,
25494
- onClick: handleToggle
25648
+ as: "span",
25649
+ ref: (el) => {
25650
+ triggerRef.current = el;
25651
+ },
25652
+ onClick: handleToggle,
25653
+ className: "inline-flex",
25654
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
25495
25655
  }
25496
25656
  );
25497
25657
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
@@ -40924,28 +41084,6 @@ var init_DetailPanel = __esm({
40924
41084
  DetailPanel.displayName = "DetailPanel";
40925
41085
  }
40926
41086
  });
40927
- function DrawGroup(props) {
40928
- const register = React94.useContext(DrawableRegistryContext);
40929
- if (register) register({ ...props, type: "draw-group" });
40930
- return null;
40931
- }
40932
- var init_DrawGroup = __esm({
40933
- "components/game/atoms/DrawGroup.tsx"() {
40934
- "use client";
40935
- init_registry();
40936
- }
40937
- });
40938
- function DrawMesh(props) {
40939
- const register = React94.useContext(DrawableRegistryContext);
40940
- if (register) register({ ...props, type: "draw-mesh" });
40941
- return null;
40942
- }
40943
- var init_DrawMesh = __esm({
40944
- "components/game/atoms/DrawMesh.tsx"() {
40945
- "use client";
40946
- init_registry();
40947
- }
40948
- });
40949
41087
  function extractTitle(children) {
40950
41088
  if (!React94__namespace.default.isValidElement(children)) return void 0;
40951
41089
  const props = children.props;
package/dist/avl/index.js CHANGED
@@ -7,7 +7,7 @@ import ELK from 'elkjs/lib/elk.bundled.js';
7
7
  import { MarkerType, useReactFlow, Handle, Position, getBezierPath, EdgeLabelRenderer, useNodeId, ReactFlowProvider, BaseEdge, useNodesState, useEdgesState, ReactFlow, Controls, Background, BackgroundVariant } from '@xyflow/react';
8
8
  import { useTranslate } from '@almadar/ui/hooks';
9
9
  import { InMemoryPersistence, StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, normalizeCallSiteConfigToValues, interpolateValue } from '@almadar/runtime';
10
- import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
10
+ import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER, ANIMATION_NAMES } from '@almadar/core';
11
11
  import * as LucideIcons2 from 'lucide-react';
12
12
  import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp, Tag, User, DollarSign } from 'lucide-react';
13
13
  import { createPortal } from 'react-dom';
@@ -4874,6 +4874,10 @@ var init_Icon = __esm({
4874
4874
  function isTilesheet(a) {
4875
4875
  return typeof a.tileWidth === "number";
4876
4876
  }
4877
+ function isSpriteSheetAtlas(a) {
4878
+ const s = a;
4879
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
4880
+ }
4877
4881
  function getAtlas(url, onReady) {
4878
4882
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
4879
4883
  atlasCache.set(url, void 0);
@@ -4913,6 +4917,7 @@ function subRectFor(atlas, sprite) {
4913
4917
  sh: atlas.tileHeight
4914
4918
  };
4915
4919
  }
4920
+ if (isSpriteSheetAtlas(atlas)) return null;
4916
4921
  const st = atlas.subTextures[sprite];
4917
4922
  if (!st) return null;
4918
4923
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -5222,7 +5227,7 @@ var init_Button = __esm({
5222
5227
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
5223
5228
  children: [
5224
5229
  isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
5225
- children || label,
5230
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
5226
5231
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
5227
5232
  ]
5228
5233
  }
@@ -10966,6 +10971,34 @@ var init_isometric = __esm({
10966
10971
  };
10967
10972
  }
10968
10973
  });
10974
+ function isAnimationName(name) {
10975
+ return ANIMATION_NAMES.includes(name);
10976
+ }
10977
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
10978
+ return {
10979
+ sx: frame % columns * frameWidth,
10980
+ sy: row * frameHeight,
10981
+ sw: frameWidth,
10982
+ sh: frameHeight
10983
+ };
10984
+ }
10985
+ function getCurrentFrameFromDef(def, elapsed) {
10986
+ const frameDuration = 1e3 / def.frameRate;
10987
+ const totalDuration = def.frames * frameDuration;
10988
+ if (def.loop) {
10989
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
10990
+ return { frame: frame2, finished: false };
10991
+ }
10992
+ if (elapsed >= totalDuration) {
10993
+ return { frame: def.frames - 1, finished: true };
10994
+ }
10995
+ const frame = Math.floor(elapsed / frameDuration);
10996
+ return { frame, finished: false };
10997
+ }
10998
+ var init_spriteAnimation = __esm({
10999
+ "lib/spriteAnimation.ts"() {
11000
+ }
11001
+ });
10969
11002
 
10970
11003
  // lib/gameShared.ts
10971
11004
  var init_gameShared = __esm({
@@ -12826,6 +12859,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
12826
12859
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
12827
12860
  painter.restore();
12828
12861
  }
12862
+ function isAnimatedSprite(node) {
12863
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
12864
+ }
12829
12865
  function DrawSprite(_props) {
12830
12866
  return null;
12831
12867
  }
@@ -12834,6 +12870,7 @@ var init_DrawSprite = __esm({
12834
12870
  "components/game/atoms/DrawSprite.tsx"() {
12835
12871
  "use client";
12836
12872
  init_atlasSlice();
12873
+ init_spriteAnimation();
12837
12874
  init_imageCache();
12838
12875
  init_contract();
12839
12876
  spriteLog = createLogger("almadar:ui:draw-sprite");
@@ -12847,6 +12884,23 @@ var init_DrawSprite = __esm({
12847
12884
  return;
12848
12885
  }
12849
12886
  let src = typeof node.frame === "object" ? node.frame : void 0;
12887
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
12888
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12889
+ if (!atlas) {
12890
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
12891
+ return;
12892
+ }
12893
+ if (isSpriteSheetAtlas(atlas)) {
12894
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
12895
+ if (!def) {
12896
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
12897
+ return;
12898
+ }
12899
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
12900
+ const r2 = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
12901
+ src = { x: r2.sx, y: r2.sy, w: r2.sw, h: r2.sh };
12902
+ }
12903
+ }
12850
12904
  if (!src && isAtlasAsset(node.asset)) {
12851
12905
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12852
12906
  if (!atlas) {
@@ -13138,6 +13192,83 @@ var init_DrawText = __esm({
13138
13192
  };
13139
13193
  }
13140
13194
  });
13195
+ function isAnimatedGroup(node) {
13196
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
13197
+ }
13198
+ function DrawGroup(props) {
13199
+ const register = useContext(DrawableRegistryContext);
13200
+ if (register) register({ ...props, type: "draw-group" });
13201
+ return null;
13202
+ }
13203
+ var init_DrawGroup = __esm({
13204
+ "components/game/atoms/DrawGroup.tsx"() {
13205
+ "use client";
13206
+ init_registry();
13207
+ }
13208
+ });
13209
+ function applyMeshAnimation(node, timeMs) {
13210
+ const anim = node.animation;
13211
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
13212
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
13213
+ const cycle = timeMs / anim.durationMs;
13214
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
13215
+ const trackValue = (key) => {
13216
+ const defined = frames.filter((f3) => f3[key] !== void 0);
13217
+ if (defined.length === 0) return void 0;
13218
+ let prev;
13219
+ let next;
13220
+ for (const f3 of defined) {
13221
+ if (f3.at <= t) prev = f3;
13222
+ else if (!next) next = f3;
13223
+ }
13224
+ if (!prev) return defined[0][key];
13225
+ if (!next) return prev[key];
13226
+ const span = next.at - prev.at;
13227
+ const k = span > 0 ? (t - prev.at) / span : 1;
13228
+ const a = prev[key];
13229
+ const b = next[key];
13230
+ if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
13231
+ return a;
13232
+ };
13233
+ const num = {};
13234
+ for (const key of NUMERIC_TRACKS2) {
13235
+ const v = trackValue(key);
13236
+ if (v !== void 0) num[key] = v;
13237
+ }
13238
+ return {
13239
+ offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
13240
+ rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
13241
+ scale: num.scale ?? 1,
13242
+ opacity: num.opacity,
13243
+ emissiveIntensity: num.emissiveIntensity,
13244
+ color: trackValue("color"),
13245
+ emissive: trackValue("emissive")
13246
+ };
13247
+ }
13248
+ function DrawMesh(props) {
13249
+ const register = useContext(DrawableRegistryContext);
13250
+ if (register) register({ ...props, type: "draw-mesh" });
13251
+ return null;
13252
+ }
13253
+ var lerp2, NUMERIC_TRACKS2;
13254
+ var init_DrawMesh = __esm({
13255
+ "components/game/atoms/DrawMesh.tsx"() {
13256
+ "use client";
13257
+ init_registry();
13258
+ lerp2 = (a, b, k) => a + (b - a) * k;
13259
+ NUMERIC_TRACKS2 = [
13260
+ "offsetX",
13261
+ "offsetY",
13262
+ "offsetZ",
13263
+ "rotateX",
13264
+ "rotateY",
13265
+ "rotateZ",
13266
+ "scale",
13267
+ "opacity",
13268
+ "emissiveIntensity"
13269
+ ];
13270
+ }
13271
+ });
13141
13272
 
13142
13273
  // components/game/molecules/DrawSpriteLayer.tsx
13143
13274
  function DrawSpriteLayer(_props) {
@@ -13204,18 +13335,22 @@ function paintDrawable(painter, node, dctx) {
13204
13335
  if (!isValidScenePos(node.position)) break;
13205
13336
  if (!Array.isArray(node.items)) break;
13206
13337
  const p = dctx.projector.project(node.position);
13338
+ const anim = dctx.time > 0 && isAnimatedGroup(node) ? applyMeshAnimation(node, dctx.time) : null;
13339
+ const tw = dctx.projector.tileWidth;
13207
13340
  painter.save();
13208
- painter.translate(p.x, p.y);
13209
- if (node.scale !== void 0) painter.scale(node.scale, node.scale);
13210
- if (node.rotate !== void 0) painter.rotate(node.rotate);
13211
- if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
13341
+ painter.translate(p.x + (anim ? anim.offset[0] * tw : 0), p.y + (anim ? anim.offset[1] * tw : 0));
13342
+ const scale = (node.scale ?? 1) * (anim?.scale ?? 1);
13343
+ if (scale !== 1) painter.scale(scale, scale);
13344
+ const rotate = (node.rotate ?? 0) + (node.rotation?.[2] ?? 0) + (anim?.rotate[2] ?? 0);
13345
+ if (rotate !== 0) painter.rotate(rotate);
13346
+ const opacity = (node.opacity ?? 1) * (anim?.opacity ?? 1);
13347
+ if (opacity !== 1) painter.setAlpha(opacity);
13212
13348
  if (node.clip) {
13213
- const tw = dctx.projector.tileWidth;
13214
13349
  painter.scale(tw, tw);
13215
13350
  painter.clipPath(node.clip);
13216
13351
  painter.scale(1 / tw, 1 / tw);
13217
13352
  }
13218
- const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
13353
+ const childCtx = scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * scale } : dctx;
13219
13354
  for (const item of node.items) paintDrawable(painter, item, childCtx);
13220
13355
  painter.restore();
13221
13356
  break;
@@ -13241,6 +13376,8 @@ var init_paintDispatch = __esm({
13241
13376
  init_DrawSprite();
13242
13377
  init_DrawShape();
13243
13378
  init_DrawText();
13379
+ init_DrawGroup();
13380
+ init_DrawMesh();
13244
13381
  init_DrawSpriteLayer();
13245
13382
  init_DrawShapeLayer();
13246
13383
  init_DrawTextLayer();
@@ -13487,8 +13624,11 @@ function Canvas2D({
13487
13624
  const miniMapHeight = gridExtent.height || 10;
13488
13625
  const drawableIsAnimated = (node) => {
13489
13626
  if (node.type === "draw-shape") return isAnimatedShape(node);
13490
- if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13627
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
13628
+ if (node.type === "draw-group")
13629
+ return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13491
13630
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13631
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13492
13632
  return false;
13493
13633
  };
13494
13634
  const animRafRef = useRef(0);
@@ -13829,6 +13969,8 @@ var init_Canvas2D = __esm({
13829
13969
  init_projector();
13830
13970
  init_paintDispatch();
13831
13971
  init_DrawShape();
13972
+ init_DrawSprite();
13973
+ init_DrawGroup();
13832
13974
  init_registry();
13833
13975
  init_hitTest();
13834
13976
  init_isometric();
@@ -13901,6 +14043,7 @@ function Canvas({
13901
14043
  cameraMode: to3DCameraMode(camera?.mode),
13902
14044
  ...zoom !== void 0 ? { scale: zoom } : {},
13903
14045
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14046
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
13904
14047
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
13905
14048
  unitScale,
13906
14049
  backgroundColor,
@@ -25170,6 +25313,14 @@ var init_DashboardLayout = __esm({
25170
25313
  NavLinkBottom.displayName = "NavLinkBottom";
25171
25314
  }
25172
25315
  });
25316
+ function downloadItemUrl(url, label) {
25317
+ const a = document.createElement("a");
25318
+ a.href = url;
25319
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
25320
+ document.body.appendChild(a);
25321
+ a.click();
25322
+ a.remove();
25323
+ }
25173
25324
  function computeMenuStyle(position, triggerRect) {
25174
25325
  const isTop = position.startsWith("top");
25175
25326
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -25222,6 +25373,7 @@ function SubMenu({
25222
25373
  onClick: () => {
25223
25374
  if (item.disabled) return;
25224
25375
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25376
+ if (item.url) downloadItemUrl(item.url, item.label);
25225
25377
  item.onClick?.();
25226
25378
  },
25227
25379
  "aria-disabled": item.disabled || void 0,
@@ -25375,6 +25527,7 @@ var init_Menu = __esm({
25375
25527
  setActiveSubMenu(itemId);
25376
25528
  } else {
25377
25529
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25530
+ if (item.url) downloadItemUrl(item.url, item.label);
25378
25531
  item.onClick?.();
25379
25532
  setIsOpen(false);
25380
25533
  }
@@ -25410,12 +25563,19 @@ var init_Menu = __esm({
25410
25563
  "bottom-end": "bottom-start"
25411
25564
  };
25412
25565
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
25413
- const triggerChild = React94__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
25414
- const triggerElement = React94__default.cloneElement(
25415
- triggerChild,
25566
+ const triggerElement = React94__default.isValidElement(trigger) ? React94__default.cloneElement(trigger, {
25567
+ ref: triggerRef,
25568
+ onClick: handleToggle
25569
+ }) : /* @__PURE__ */ jsx(
25570
+ Box,
25416
25571
  {
25417
- ref: triggerRef,
25418
- onClick: handleToggle
25572
+ as: "span",
25573
+ ref: (el) => {
25574
+ triggerRef.current = el;
25575
+ },
25576
+ onClick: handleToggle,
25577
+ className: "inline-flex",
25578
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
25419
25579
  }
25420
25580
  );
25421
25581
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
@@ -40848,28 +41008,6 @@ var init_DetailPanel = __esm({
40848
41008
  DetailPanel.displayName = "DetailPanel";
40849
41009
  }
40850
41010
  });
40851
- function DrawGroup(props) {
40852
- const register = useContext(DrawableRegistryContext);
40853
- if (register) register({ ...props, type: "draw-group" });
40854
- return null;
40855
- }
40856
- var init_DrawGroup = __esm({
40857
- "components/game/atoms/DrawGroup.tsx"() {
40858
- "use client";
40859
- init_registry();
40860
- }
40861
- });
40862
- function DrawMesh(props) {
40863
- const register = useContext(DrawableRegistryContext);
40864
- if (register) register({ ...props, type: "draw-mesh" });
40865
- return null;
40866
- }
40867
- var init_DrawMesh = __esm({
40868
- "components/game/atoms/DrawMesh.tsx"() {
40869
- "use client";
40870
- init_registry();
40871
- }
40872
- });
40873
41011
  function extractTitle(children) {
40874
41012
  if (!React94__default.isValidElement(children)) return void 0;
40875
41013
  const props = children.props;