@almadar/ui 5.139.0 → 5.141.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.
@@ -8,9 +8,9 @@ export { A as AudioManifest, G as GameAudioContext, a as GameAudioContextValue,
8
8
  import { SExpr } from '@almadar/evaluator';
9
9
  import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, d as SpriteFrameDims, R as ResolvedFrame, a as IsometricUnit, C as CameraState, e as FieldInfo, f as OrbitalTraitInfo, g as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-B8Onmfsu.js';
10
10
  export { B as BoardTile, G as GameAction, h as GamePhase, i as GameState, j as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, k as UnitTrait, l as calculateAttackTargets, m as calculateValidMoves, n as createInitialGameState } from '../avl-schema-parser-B8Onmfsu.js';
11
- import { D as DrawableNode } from '../paintDispatch-DXygiK7M.js';
12
- import { C as ContentSegment, b as DomLayoutData, d as DomStateNode, V as VisualizerConfig, e as DomTransitionLabel } from '../cn-CCaph5o9.js';
13
- export { n as cn } from '../cn-CCaph5o9.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';
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';
@@ -15622,9 +15622,26 @@ var init_imageCache = __esm({
15622
15622
  });
15623
15623
 
15624
15624
  // lib/webPainter2d.ts
15625
+ function makeNoiseTile(alpha, color) {
15626
+ const tile = document.createElement("canvas");
15627
+ tile.width = NOISE_CELLS;
15628
+ tile.height = NOISE_CELLS;
15629
+ const t = tile.getContext("2d");
15630
+ if (t) {
15631
+ t.fillStyle = color;
15632
+ for (let y = 0; y < NOISE_CELLS; y++) {
15633
+ for (let x = 0; x < NOISE_CELLS; x++) {
15634
+ t.globalAlpha = noiseHash(x, y) * alpha;
15635
+ t.fillRect(x, y, 1, 1);
15636
+ }
15637
+ }
15638
+ }
15639
+ return tile;
15640
+ }
15625
15641
  function createWebPainter(ctx, onAssetLoad) {
15626
15642
  let vw = 0;
15627
15643
  let vh = 0;
15644
+ const patternCache = /* @__PURE__ */ new Map();
15628
15645
  const tracePoly = (points, closed) => {
15629
15646
  if (points.length === 0) return;
15630
15647
  ctx.beginPath();
@@ -15632,6 +15649,31 @@ function createWebPainter(ctx, onAssetLoad) {
15632
15649
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
15633
15650
  if (closed) ctx.closePath();
15634
15651
  };
15652
+ const toCanvasPattern = (style) => {
15653
+ const key = JSON.stringify(style);
15654
+ let pattern = patternCache.get(key);
15655
+ if (pattern === void 0) {
15656
+ if (style.kind === "noise") {
15657
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
15658
+ } else {
15659
+ const img = getOrLoadImage(style.url, onAssetLoad);
15660
+ if (!img) return "rgba(0,0,0,0)";
15661
+ pattern = ctx.createPattern(img, "repeat");
15662
+ }
15663
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
15664
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
15665
+ }
15666
+ patternCache.set(key, pattern);
15667
+ }
15668
+ return pattern ?? "rgba(0,0,0,0)";
15669
+ };
15670
+ const toCanvasStyle = (style) => {
15671
+ if (typeof style === "string") return style;
15672
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
15673
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : style.kind === "conic" ? ctx.createConicGradient(style.angle, style.cx, style.cy) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
15674
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
15675
+ return g;
15676
+ };
15635
15677
  return {
15636
15678
  setViewport(width, height, dpr) {
15637
15679
  vw = width;
@@ -15663,6 +15705,19 @@ function createWebPainter(ctx, onAssetLoad) {
15663
15705
  ctx.shadowColor = shadow ? shadow.color : "transparent";
15664
15706
  ctx.shadowBlur = shadow ? shadow.blur : 0;
15665
15707
  },
15708
+ setBlend(mode) {
15709
+ ctx.globalCompositeOperation = mode ?? "source-over";
15710
+ },
15711
+ setLineDash(pattern, offset = 0) {
15712
+ ctx.setLineDash(pattern ? [...pattern] : []);
15713
+ ctx.lineDashOffset = offset;
15714
+ },
15715
+ setBlur(px) {
15716
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
15717
+ },
15718
+ clipPath(d) {
15719
+ ctx.clip(new Path2D(d));
15720
+ },
15666
15721
  resolveTexture(url) {
15667
15722
  const img = getOrLoadImage(url, onAssetLoad);
15668
15723
  if (!img) return null;
@@ -15685,47 +15740,47 @@ function createWebPainter(ctx, onAssetLoad) {
15685
15740
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
15686
15741
  }
15687
15742
  },
15688
- fillRect(x, y, w, h, color) {
15689
- ctx.fillStyle = color;
15743
+ fillRect(x, y, w, h, style) {
15744
+ ctx.fillStyle = toCanvasStyle(style);
15690
15745
  ctx.fillRect(x, y, w, h);
15691
15746
  },
15692
- strokeRect(x, y, w, h, color, lineWidth = 1) {
15693
- ctx.strokeStyle = color;
15747
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
15748
+ ctx.strokeStyle = toCanvasStyle(style);
15694
15749
  ctx.lineWidth = lineWidth;
15695
15750
  ctx.strokeRect(x, y, w, h);
15696
15751
  },
15697
- fillPoly(points, color) {
15752
+ fillPoly(points, style) {
15698
15753
  if (points.length === 0) return;
15699
15754
  tracePoly(points, true);
15700
- ctx.fillStyle = color;
15755
+ ctx.fillStyle = toCanvasStyle(style);
15701
15756
  ctx.fill();
15702
15757
  },
15703
- strokePoly(points, color, lineWidth = 1, closed = false) {
15758
+ strokePoly(points, style, lineWidth = 1, closed = false) {
15704
15759
  if (points.length === 0) return;
15705
15760
  tracePoly(points, closed);
15706
- ctx.strokeStyle = color;
15761
+ ctx.strokeStyle = toCanvasStyle(style);
15707
15762
  ctx.lineWidth = lineWidth;
15708
15763
  ctx.stroke();
15709
15764
  },
15710
- fillEllipse(cx, cy, rx, ry, color) {
15765
+ fillEllipse(cx, cy, rx, ry, style) {
15711
15766
  ctx.beginPath();
15712
15767
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
15713
- ctx.fillStyle = color;
15768
+ ctx.fillStyle = toCanvasStyle(style);
15714
15769
  ctx.fill();
15715
15770
  },
15716
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
15771
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
15717
15772
  ctx.beginPath();
15718
15773
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
15719
- ctx.strokeStyle = color;
15774
+ ctx.strokeStyle = toCanvasStyle(style);
15720
15775
  ctx.lineWidth = lineWidth;
15721
15776
  ctx.stroke();
15722
15777
  },
15723
- fillPath(d, color) {
15724
- ctx.fillStyle = color;
15778
+ fillPath(d, style) {
15779
+ ctx.fillStyle = toCanvasStyle(style);
15725
15780
  ctx.fill(new Path2D(d));
15726
15781
  },
15727
- strokePath(d, color, lineWidth = 1) {
15728
- ctx.strokeStyle = color;
15782
+ strokePath(d, style, lineWidth = 1) {
15783
+ ctx.strokeStyle = toCanvasStyle(style);
15729
15784
  ctx.lineWidth = lineWidth;
15730
15785
  ctx.stroke(new Path2D(d));
15731
15786
  },
@@ -15738,13 +15793,18 @@ function createWebPainter(ctx, onAssetLoad) {
15738
15793
  }
15739
15794
  };
15740
15795
  }
15741
- var handleByImage, imageByHandle;
15796
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
15742
15797
  var init_webPainter2d = __esm({
15743
15798
  "lib/webPainter2d.ts"() {
15744
15799
  "use client";
15745
15800
  init_imageCache();
15746
15801
  handleByImage = /* @__PURE__ */ new WeakMap();
15747
15802
  imageByHandle = /* @__PURE__ */ new WeakMap();
15803
+ noiseHash = (x, y) => {
15804
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
15805
+ return s - Math.floor(s);
15806
+ };
15807
+ NOISE_CELLS = 32;
15748
15808
  }
15749
15809
  });
15750
15810
 
@@ -15962,6 +16022,101 @@ var init_registry = __esm({
15962
16022
  DrawableRegistryContext = createContext(null);
15963
16023
  }
15964
16024
  });
16025
+ function isAnimatedShape(node) {
16026
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
16027
+ }
16028
+ function applyShapeAnimation(node, timeMs) {
16029
+ const anim = node.animation;
16030
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
16031
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
16032
+ const cycle = timeMs / anim.durationMs;
16033
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
16034
+ const out = { ...node };
16035
+ const trackValue = (key) => {
16036
+ const defined = frames.filter((f3) => f3[key] !== void 0);
16037
+ if (defined.length === 0) return void 0;
16038
+ let prev;
16039
+ let next;
16040
+ for (const f3 of defined) {
16041
+ if (f3.at <= t) prev = f3;
16042
+ else if (!next) next = f3;
16043
+ }
16044
+ if (!prev) return defined[0][key];
16045
+ if (!next) return prev[key];
16046
+ const span = next.at - prev.at;
16047
+ const k = span > 0 ? (t - prev.at) / span : 1;
16048
+ const a = prev[key];
16049
+ const b = next[key];
16050
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
16051
+ return a;
16052
+ };
16053
+ for (const key of NUMERIC_TRACKS) {
16054
+ const v = trackValue(key);
16055
+ if (v !== void 0) out[key] = v;
16056
+ }
16057
+ const fill = trackValue("fill");
16058
+ if (fill !== void 0) out.fill = fill;
16059
+ const stroke = trackValue("stroke");
16060
+ if (stroke !== void 0) out.stroke = stroke;
16061
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
16062
+ if (shadowFrames.length > 0) {
16063
+ const sh = trackValue("shadow");
16064
+ if (sh !== void 0) {
16065
+ let prevSh;
16066
+ let nextSh;
16067
+ for (const f3 of shadowFrames) {
16068
+ if (f3.at <= t) prevSh = f3;
16069
+ else if (!nextSh) nextSh = f3;
16070
+ }
16071
+ if (prevSh?.shadow && nextSh?.shadow) {
16072
+ const span = nextSh.at - prevSh.at;
16073
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
16074
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
16075
+ } else {
16076
+ out.shadow = sh;
16077
+ }
16078
+ }
16079
+ }
16080
+ return out;
16081
+ }
16082
+ function gradientStyle(g, ox, oy, scale) {
16083
+ if (g.kind === "linear") {
16084
+ if (!g.from || !g.to) return void 0;
16085
+ return {
16086
+ kind: "linear",
16087
+ x1: ox + g.from.x * scale,
16088
+ y1: oy + g.from.y * scale,
16089
+ x2: ox + g.to.x * scale,
16090
+ y2: oy + g.to.y * scale,
16091
+ stops: g.stops
16092
+ };
16093
+ }
16094
+ if (g.kind === "conic") {
16095
+ if (!g.center) return void 0;
16096
+ return {
16097
+ kind: "conic",
16098
+ cx: ox + g.center.x * scale,
16099
+ cy: oy + g.center.y * scale,
16100
+ angle: g.angle ?? 0,
16101
+ stops: g.stops
16102
+ };
16103
+ }
16104
+ if (!g.center || g.radius === void 0) return void 0;
16105
+ return {
16106
+ kind: "radial",
16107
+ cx: ox + g.center.x * scale,
16108
+ cy: oy + g.center.y * scale,
16109
+ r: g.radius * scale,
16110
+ stops: g.stops
16111
+ };
16112
+ }
16113
+ function patternStyle(p, unit) {
16114
+ if (p.kind === "image") {
16115
+ if (!p.url) return void 0;
16116
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
16117
+ }
16118
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
16119
+ }
15965
16120
  function DrawShape(props) {
15966
16121
  const register = useContext(DrawableRegistryContext);
15967
16122
  if (register) {
@@ -15972,25 +16127,66 @@ function DrawShape(props) {
15972
16127
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
15973
16128
  };
15974
16129
  register(node);
15975
- return /* @__PURE__ */ jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
15976
16130
  }
15977
16131
  return null;
15978
16132
  }
15979
- var paintShape;
16133
+ var NUMERIC_TRACKS, lerp, paintShape;
15980
16134
  var init_DrawShape = __esm({
15981
16135
  "components/game/atoms/DrawShape.tsx"() {
15982
16136
  "use client";
15983
16137
  init_contract();
15984
16138
  init_registry();
15985
- paintShape = (painter, node, dctx) => {
16139
+ NUMERIC_TRACKS = [
16140
+ "offsetX",
16141
+ "offsetY",
16142
+ "rotate",
16143
+ "opacity",
16144
+ "radiusX",
16145
+ "radiusY",
16146
+ "width",
16147
+ "height",
16148
+ "strokeWidth",
16149
+ "strokeDashOffset",
16150
+ "blur"
16151
+ ];
16152
+ lerp = (a, b, k) => a + (b - a) * k;
16153
+ paintShape = (painter, rawNode, dctx) => {
16154
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
15986
16155
  if (!isValidScenePos(node.position)) return;
15987
16156
  painter.save();
15988
16157
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
16158
+ const origin = dctx.projector.project(node.position);
16159
+ const tileWidth = dctx.projector.tileWidth;
16160
+ const groupScale = dctx.groupScale ?? 1;
16161
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
16162
+ if (node.blendMode) painter.setBlend(node.blendMode);
16163
+ if (node.strokeDash && node.strokeDash.length > 0) {
16164
+ painter.setLineDash(
16165
+ node.strokeDash.map((v) => v / groupScale),
16166
+ (node.strokeDashOffset ?? 0) / groupScale
16167
+ );
16168
+ }
16169
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
16170
+ if (node.rotate) {
16171
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
16172
+ const px = origin.x + pivot.x * tileWidth;
16173
+ const py = origin.y + pivot.y * tileWidth;
16174
+ painter.translate(px, py);
16175
+ painter.rotate(node.rotate);
16176
+ painter.translate(-px, -py);
16177
+ }
16178
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
16179
+ const fill = node.fill === "none" ? void 0 : node.fill;
16180
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
16181
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
16182
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
16183
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
15989
16184
  switch (node.shape) {
15990
16185
  case "cell": {
15991
16186
  const pts = dctx.projector.cellPath(node.position);
15992
- if (node.fill) painter.fillPoly(pts, node.fill);
15993
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
16187
+ if (pxFill) painter.fillPoly(pts, pxFill);
16188
+ if (patFill) painter.fillPoly(pts, patFill);
16189
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
15994
16190
  break;
15995
16191
  }
15996
16192
  case "rect": {
@@ -16000,8 +16196,9 @@ var init_DrawShape = __esm({
16000
16196
  const y = p.y + (node.offsetY ?? 0) * tw;
16001
16197
  const w = (node.width ?? 0) * tw;
16002
16198
  const h = (node.height ?? 0) * tw;
16003
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
16004
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
16199
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
16200
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
16201
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
16005
16202
  break;
16006
16203
  }
16007
16204
  case "ellipse": {
@@ -16011,26 +16208,38 @@ var init_DrawShape = __esm({
16011
16208
  const cy = p.y + (node.offsetY ?? 0) * tw;
16012
16209
  const rx = (node.radiusX ?? 0) * tw;
16013
16210
  const ry = (node.radiusY ?? rx) * tw;
16014
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
16015
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
16211
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
16212
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
16213
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
16016
16214
  break;
16017
16215
  }
16018
16216
  case "poly": {
16019
- const base = dctx.projector.project(node.position);
16020
- const tw = dctx.projector.tileWidth;
16021
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
16022
- if (node.fill) painter.fillPoly(pts, node.fill);
16023
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
16217
+ const pts = (node.points ?? []).map((pt) => ({
16218
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
16219
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
16220
+ }));
16221
+ if (pxFill) painter.fillPoly(pts, pxFill);
16222
+ if (patFill) painter.fillPoly(pts, patFill);
16223
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
16024
16224
  break;
16025
16225
  }
16026
16226
  case "path": {
16027
16227
  if (!node.d) break;
16028
- const base = dctx.projector.project(node.position);
16029
- const tw = dctx.projector.tileWidth;
16030
- painter.translate(base.x, base.y);
16031
- painter.scale(tw, tw);
16032
- if (node.fill) painter.fillPath(node.d, node.fill);
16033
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
16228
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
16229
+ painter.scale(tileWidth, tileWidth);
16230
+ if (node.strokeDash && node.strokeDash.length > 0) {
16231
+ painter.setLineDash(
16232
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
16233
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
16234
+ );
16235
+ }
16236
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
16237
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
16238
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
16239
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
16240
+ if (localFill) painter.fillPath(node.d, localFill);
16241
+ if (localPat) painter.fillPath(node.d, localPat);
16242
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
16034
16243
  break;
16035
16244
  }
16036
16245
  }
@@ -16111,8 +16320,6 @@ var init_DrawTextLayer = __esm({
16111
16320
  };
16112
16321
  }
16113
16322
  });
16114
-
16115
- // lib/drawable/paintDispatch.ts
16116
16323
  function paintDrawable(painter, node, dctx) {
16117
16324
  switch (node.type) {
16118
16325
  case "draw-sprite":
@@ -16133,10 +16340,20 @@ function paintDrawable(painter, node, dctx) {
16133
16340
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
16134
16341
  if (node.rotate !== void 0) painter.rotate(node.rotate);
16135
16342
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
16136
- for (const item of node.items) paintDrawable(painter, item, dctx);
16343
+ if (node.clip) {
16344
+ const tw = dctx.projector.tileWidth;
16345
+ painter.scale(tw, tw);
16346
+ painter.clipPath(node.clip);
16347
+ painter.scale(1 / tw, 1 / tw);
16348
+ }
16349
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
16350
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
16137
16351
  painter.restore();
16138
16352
  break;
16139
16353
  }
16354
+ case "draw-mesh":
16355
+ warnUnsupported2d("draw-mesh");
16356
+ break;
16140
16357
  case "draw-sprite-layer":
16141
16358
  paintSpriteLayer(painter, node, dctx);
16142
16359
  break;
@@ -16148,6 +16365,7 @@ function paintDrawable(painter, node, dctx) {
16148
16365
  break;
16149
16366
  }
16150
16367
  }
16368
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
16151
16369
  var init_paintDispatch = __esm({
16152
16370
  "lib/drawable/paintDispatch.ts"() {
16153
16371
  init_contract();
@@ -16157,6 +16375,13 @@ var init_paintDispatch = __esm({
16157
16375
  init_DrawSpriteLayer();
16158
16376
  init_DrawShapeLayer();
16159
16377
  init_DrawTextLayer();
16378
+ paint2dLog = createLogger("almadar:ui:drawable-2d");
16379
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
16380
+ warnUnsupported2d = (kind) => {
16381
+ if (warnedUnsupported2d.has(kind)) return;
16382
+ warnedUnsupported2d.add(kind);
16383
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
16384
+ };
16160
16385
  }
16161
16386
  });
16162
16387
 
@@ -16171,6 +16396,7 @@ function collectDrawnItems(nodes) {
16171
16396
  case "draw-shape":
16172
16397
  case "draw-text":
16173
16398
  case "draw-group":
16399
+ case "draw-mesh":
16174
16400
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
16175
16401
  break;
16176
16402
  case "draw-sprite-layer":
@@ -16242,7 +16468,6 @@ function Canvas2D({
16242
16468
  childDrawablesRef.current.push(node);
16243
16469
  }, []);
16244
16470
  const hasJsxChildren = React76.Children.count(children) > 0;
16245
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
16246
16471
  function isDrawableLayer(node) {
16247
16472
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
16248
16473
  }
@@ -16391,11 +16616,24 @@ function Canvas2D({
16391
16616
  }, [showMinimap, scenePositions]);
16392
16617
  const miniMapWidth = gridExtent.width || 10;
16393
16618
  const miniMapHeight = gridExtent.height || 10;
16394
- const draw = useCallback(() => {
16619
+ const drawableIsAnimated = (node) => {
16620
+ if (node.type === "draw-shape") return isAnimatedShape(node);
16621
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
16622
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
16623
+ return false;
16624
+ };
16625
+ const animRafRef = useRef(0);
16626
+ const drawTimeRef = useRef(() => void 0);
16627
+ const draw = useCallback((timeMs = 0) => {
16395
16628
  const canvas = canvasRef.current;
16396
16629
  if (!canvas) return;
16397
16630
  const ctx = canvas.getContext("2d");
16398
16631
  if (!ctx) return;
16632
+ const scheduleAnimation = (nodes) => {
16633
+ if (!nodes.some(drawableIsAnimated)) return;
16634
+ cancelAnimationFrame(animRafRef.current);
16635
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
16636
+ };
16399
16637
  const dpr = window.devicePixelRatio || 1;
16400
16638
  canvas.width = viewportSize.width * dpr;
16401
16639
  canvas.height = viewportSize.height * dpr;
@@ -16428,14 +16666,24 @@ function Canvas2D({
16428
16666
  if (!drawables || drawables.length === 0) {
16429
16667
  const childDrawables = childDrawablesRef.current;
16430
16668
  if (childDrawables.length === 0) return;
16669
+ const cam0 = cameraRef.current;
16670
+ if (camera !== "follow" && dragDistance() === 0) {
16671
+ const focus = cameraPos ?? defaultGridFocus;
16672
+ if (focus) {
16673
+ const p = projector.anchorPoint(focus, "center");
16674
+ cam0.x = p.x - viewportSize.width / 2;
16675
+ cam0.y = p.y - viewportSize.height / 2;
16676
+ }
16677
+ }
16431
16678
  const painter0 = createWebPainter(ctx, bumpAtlas);
16432
16679
  painter0.save();
16433
16680
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
16434
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
16435
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
16436
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
16681
+ painter0.scale(cam0.zoom, cam0.zoom);
16682
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
16683
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
16437
16684
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
16438
16685
  painter0.restore();
16686
+ scheduleAnimation(childDrawables);
16439
16687
  return;
16440
16688
  }
16441
16689
  const cam = cameraRef.current;
@@ -16455,10 +16703,15 @@ function Canvas2D({
16455
16703
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
16456
16704
  painter.scale(cam.zoom, cam.zoom);
16457
16705
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
16458
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
16706
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
16459
16707
  for (const node of drawables) paintDrawable(painter, node, dctx);
16460
16708
  painter.restore();
16709
+ scheduleAnimation(drawables);
16461
16710
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
16711
+ useEffect(() => {
16712
+ drawTimeRef.current = draw;
16713
+ }, [draw]);
16714
+ useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
16462
16715
  useEffect(() => {
16463
16716
  if (camera !== "follow" || !followTarget) return;
16464
16717
  const p = projector.anchorPoint(followTarget, "center");
@@ -16681,15 +16934,7 @@ function Canvas2D({
16681
16934
  mapHeight: miniMapHeight
16682
16935
  }
16683
16936
  ) }),
16684
- hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
16685
- /* @__PURE__ */ jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
16686
- "C=",
16687
- React76.Children.count(children),
16688
- " J=",
16689
- String(hasJsxChildren),
16690
- " D=",
16691
- drawables?.length ?? -1
16692
- ] })
16937
+ hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
16693
16938
  ]
16694
16939
  }
16695
16940
  ) });
@@ -16714,6 +16959,7 @@ var init_Canvas2D = __esm({
16714
16959
  init_webPainter2d();
16715
16960
  init_projector();
16716
16961
  init_paintDispatch();
16962
+ init_DrawShape();
16717
16963
  init_registry();
16718
16964
  init_hitTest();
16719
16965
  init_isometric();
@@ -16768,6 +17014,7 @@ function Canvas({
16768
17014
  isLoading,
16769
17015
  cameraMode: to3DCameraMode(camera?.mode),
16770
17016
  ...zoom !== void 0 ? { scale: zoom } : {},
17017
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
16771
17018
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
16772
17019
  unitScale,
16773
17020
  backgroundColor,
@@ -40004,14 +40251,26 @@ var init_DetailPanel = __esm({
40004
40251
  DetailPanel.displayName = "DetailPanel";
40005
40252
  }
40006
40253
  });
40007
-
40008
- // components/game/atoms/DrawGroup.tsx
40009
- function DrawGroup(_props) {
40254
+ function DrawGroup(props) {
40255
+ const register = useContext(DrawableRegistryContext);
40256
+ if (register) register({ ...props, type: "draw-group" });
40010
40257
  return null;
40011
40258
  }
40012
40259
  var init_DrawGroup = __esm({
40013
40260
  "components/game/atoms/DrawGroup.tsx"() {
40014
40261
  "use client";
40262
+ init_registry();
40263
+ }
40264
+ });
40265
+ function DrawMesh(props) {
40266
+ const register = useContext(DrawableRegistryContext);
40267
+ if (register) register({ ...props, type: "draw-mesh" });
40268
+ return null;
40269
+ }
40270
+ var init_DrawMesh = __esm({
40271
+ "components/game/atoms/DrawMesh.tsx"() {
40272
+ "use client";
40273
+ init_registry();
40015
40274
  }
40016
40275
  });
40017
40276
  function extractTitle(children) {
@@ -45718,6 +45977,7 @@ var init_component_registry_generated = __esm({
45718
45977
  init_DocTOC();
45719
45978
  init_DocumentViewer();
45720
45979
  init_DrawGroup();
45980
+ init_DrawMesh();
45721
45981
  init_DrawShape();
45722
45982
  init_DrawShapeLayer();
45723
45983
  init_DrawSprite();
@@ -45986,6 +46246,7 @@ var init_component_registry_generated = __esm({
45986
46246
  "DocTOC": DocTOC,
45987
46247
  "DocumentViewer": DocumentViewer,
45988
46248
  "DrawGroup": DrawGroup,
46249
+ "DrawMesh": DrawMesh,
45989
46250
  "DrawShape": DrawShape,
45990
46251
  "DrawShapeLayer": DrawShapeLayer,
45991
46252
  "DrawSprite": DrawSprite,
@@ -46894,8 +47155,13 @@ function SlotContentRenderer({
46894
47155
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
46895
47156
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
46896
47157
  const isDrawHost = isDrawHostPattern(content.pattern);
47158
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
47159
+ const hasTraitChildren = arr.some(
47160
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
47161
+ );
47162
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
46897
47163
  const myPath = patternPath ?? "root";
46898
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
47164
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
46899
47165
  slot: content.slot,
46900
47166
  transitionEvent: content.transitionEvent,
46901
47167
  fromState: content.fromState,
@@ -46956,7 +47222,7 @@ function SlotContentRenderer({
46956
47222
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
46957
47223
  finalProps[k] = v;
46958
47224
  }
46959
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
47225
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
46960
47226
  finalProps.drawables = toDrawableNodes(childrenConfig);
46961
47227
  }
46962
47228
  const entityVal = finalProps.entity;
@@ -47135,6 +47401,8 @@ var init_UISlotRenderer = __esm({
47135
47401
  "vstack",
47136
47402
  "hstack",
47137
47403
  "box",
47404
+ "canvas",
47405
+ "canvas-2d",
47138
47406
  "grid",
47139
47407
  "center",
47140
47408
  "card",