@almadar/ui 5.152.0 → 5.154.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.
Files changed (35) hide show
  1. package/dist/{EntityBindingContext-CD9ZoXb4.d.cts → EntityBindingContext-BfZGeDfX.d.cts} +4 -2
  2. package/dist/{EntityBindingContext-CD9ZoXb4.d.ts → EntityBindingContext-BfZGeDfX.d.ts} +4 -2
  3. package/dist/NavStackContext-BoVV9nWb.d.cts +83 -0
  4. package/dist/NavStackContext-BoVV9nWb.d.ts +83 -0
  5. package/dist/avl/index.cjs +571 -76
  6. package/dist/avl/index.js +572 -77
  7. package/dist/{avl-schema-parser-Cx_4SVg9.d.ts → avl-schema-parser-CLIm28Wa.d.ts} +1 -1
  8. package/dist/{avl-schema-parser-CVzkNzg7.d.cts → avl-schema-parser-Do_LPV1o.d.cts} +1 -1
  9. package/dist/{cn-DJTUjk1M.d.ts → cn-CFurBb2q.d.ts} +1 -1
  10. package/dist/{cn-BnAJZcNb.d.cts → cn-TH-RHihd.d.cts} +1 -1
  11. package/dist/components/index.cjs +502 -52
  12. package/dist/components/index.d.cts +58 -9
  13. package/dist/components/index.d.ts +58 -9
  14. package/dist/components/index.js +501 -55
  15. package/dist/hooks/index.cjs +4 -0
  16. package/dist/hooks/index.d.cts +1 -0
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/index.js +1 -0
  19. package/dist/lib/drawable/three/index.cjs +212 -0
  20. package/dist/lib/drawable/three/index.d.cts +3 -3
  21. package/dist/lib/drawable/three/index.d.ts +3 -3
  22. package/dist/lib/drawable/three/index.js +212 -0
  23. package/dist/lib/index.d.cts +2 -2
  24. package/dist/lib/index.d.ts +2 -2
  25. package/dist/{paintDispatch-CxdLjHQq.d.ts → paintDispatch-B5n2DTB-.d.ts} +178 -2
  26. package/dist/{paintDispatch-BjZjUbcb.d.cts → paintDispatch-DgBctxIq.d.cts} +178 -2
  27. package/dist/providers/index.cjs +677 -59
  28. package/dist/providers/index.d.cts +2 -1
  29. package/dist/providers/index.d.ts +2 -1
  30. package/dist/providers/index.js +677 -62
  31. package/dist/runtime/index.cjs +570 -75
  32. package/dist/runtime/index.d.cts +12 -5
  33. package/dist/runtime/index.d.ts +12 -5
  34. package/dist/runtime/index.js +571 -76
  35. package/package.json +4 -4
@@ -6245,27 +6245,61 @@ var init_InfiniteScrollSentinel = __esm({
6245
6245
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
6246
6246
  }
6247
6247
  });
6248
- function createParticles(count) {
6249
- return Array.from({ length: count }, () => {
6250
- particleIdCounter += 1;
6251
- return {
6252
- id: particleIdCounter,
6253
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
6254
- left: 30 + Math.random() * 40,
6255
- delay: Math.random() * 300,
6256
- angle: Math.random() * 360,
6257
- distance: 40 + Math.random() * 80,
6258
- rotation: Math.random() * 720 - 360,
6259
- size: 4 + Math.random() * 6
6260
- };
6261
- });
6248
+
6249
+ // components/core/atoms/fx.ts
6250
+ function resolveFxView(item, presets) {
6251
+ const row = presets?.find((p) => p.type === item.type);
6252
+ if (!row) return item;
6253
+ return {
6254
+ ...item,
6255
+ space: item.space ?? row.space,
6256
+ effect: item.effect ?? row.effect,
6257
+ color: item.color ?? row.color,
6258
+ size: item.size ?? row.size,
6259
+ vx: item.vx ?? row.vx,
6260
+ vy: item.vy ?? row.vy,
6261
+ vz: item.vz ?? row.vz,
6262
+ particleCount: item.particleCount ?? row.particleCount,
6263
+ shape: row.shape,
6264
+ count: row.count,
6265
+ glow: row.glow,
6266
+ gravity: row.gravity,
6267
+ color2: row.color2
6268
+ };
6262
6269
  }
6263
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
6264
- var init_ConfettiEffect = __esm({
6265
- "components/core/atoms/ConfettiEffect.tsx"() {
6266
- "use client";
6267
- init_cn();
6268
- init_Box();
6270
+ function fxLifecycle(item, epochNowMs, tickMs) {
6271
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
6272
+ const lifeMs = maxTtl * tickMs;
6273
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
6274
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
6275
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
6276
+ }
6277
+ function fxHash01(seed, salt) {
6278
+ let h = 2166136261 ^ salt;
6279
+ for (let i = 0; i < seed.length; i++) {
6280
+ h ^= seed.charCodeAt(i);
6281
+ h = Math.imul(h, 16777619);
6282
+ }
6283
+ h ^= h >>> 13;
6284
+ h = Math.imul(h, 1274126177);
6285
+ h ^= h >>> 16;
6286
+ return (h >>> 0) / 4294967296;
6287
+ }
6288
+ function createConfettiParticles(count, seed) {
6289
+ return Array.from({ length: count }, (_, i) => ({
6290
+ id: i,
6291
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
6292
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
6293
+ delay: fxHash01(seed, i * 7 + 3) * 300,
6294
+ angle: fxHash01(seed, i * 7 + 4) * 360,
6295
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
6296
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
6297
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
6298
+ }));
6299
+ }
6300
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
6301
+ var init_fx = __esm({
6302
+ "components/core/atoms/fx.ts"() {
6269
6303
  CONFETTI_COLORS = [
6270
6304
  "var(--color-primary)",
6271
6305
  "var(--color-success)",
@@ -6274,7 +6308,30 @@ var init_ConfettiEffect = __esm({
6274
6308
  "gold",
6275
6309
  "dodgerblue"
6276
6310
  ];
6277
- particleIdCounter = 0;
6311
+ CONFETTI_BURST_KEYFRAMES = `
6312
+ @keyframes confetti-burst {
6313
+ 0% {
6314
+ opacity: 1;
6315
+ transform: translate(0, 0) rotate(0deg) scale(1);
6316
+ }
6317
+ 70% {
6318
+ opacity: 1;
6319
+ }
6320
+ 100% {
6321
+ opacity: 0;
6322
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6323
+ }
6324
+ }
6325
+ `;
6326
+ }
6327
+ });
6328
+ var ConfettiEffect;
6329
+ var init_ConfettiEffect = __esm({
6330
+ "components/core/atoms/ConfettiEffect.tsx"() {
6331
+ "use client";
6332
+ init_cn();
6333
+ init_Box();
6334
+ init_fx();
6278
6335
  ConfettiEffect = ({
6279
6336
  trigger,
6280
6337
  duration = 2e3,
@@ -6283,11 +6340,13 @@ var init_ConfettiEffect = __esm({
6283
6340
  }) => {
6284
6341
  const [particles, setParticles] = React85.useState([]);
6285
6342
  const previousTriggerRef = React85.useRef(false);
6343
+ const burstRef = React85.useRef(0);
6286
6344
  React85.useEffect(() => {
6287
6345
  const wasFalse = !previousTriggerRef.current;
6288
6346
  previousTriggerRef.current = trigger;
6289
6347
  if (trigger && wasFalse) {
6290
- const newParticles = createParticles(particleCount);
6348
+ burstRef.current += 1;
6349
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
6291
6350
  setParticles(newParticles);
6292
6351
  const timer = window.setTimeout(() => {
6293
6352
  setParticles([]);
@@ -6335,21 +6394,7 @@ var init_ConfettiEffect = __esm({
6335
6394
  p.id
6336
6395
  );
6337
6396
  }),
6338
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
6339
- @keyframes confetti-burst {
6340
- 0% {
6341
- opacity: 1;
6342
- transform: translate(0, 0) rotate(0deg) scale(1);
6343
- }
6344
- 70% {
6345
- opacity: 1;
6346
- }
6347
- 100% {
6348
- opacity: 0;
6349
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6350
- }
6351
- }
6352
- ` })
6397
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
6353
6398
  ]
6354
6399
  }
6355
6400
  );
@@ -9798,7 +9843,7 @@ var init_DrawShape = __esm({
9798
9843
  const cx = p.x + (node.offsetX ?? 0) * tw;
9799
9844
  const cy = p.y + (node.offsetY ?? 0) * tw;
9800
9845
  const rx = (node.radiusX ?? 0) * tw;
9801
- const ry = (node.radiusY ?? rx) * tw;
9846
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9802
9847
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9803
9848
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9804
9849
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9988,6 +10033,172 @@ var init_DrawTextLayer = __esm({
9988
10033
  };
9989
10034
  }
9990
10035
  });
10036
+
10037
+ // components/game/molecules/DrawFxLayer.tsx
10038
+ function fxPosition(view, dim, ageSec) {
10039
+ const g = view.gravity ?? 0;
10040
+ const fall = 0.5 * g * ageSec * ageSec;
10041
+ {
10042
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
10043
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
10044
+ return {
10045
+ x: base2.x + (view.vx ?? 0) * ageSec,
10046
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
10047
+ };
10048
+ }
10049
+ }
10050
+ function sparkShape(view, pos, i, fade, progress) {
10051
+ const size = view.size ?? 0.5;
10052
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10053
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
10054
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
10055
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
10056
+ return {
10057
+ type: "draw-shape",
10058
+ shape: "ellipse",
10059
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
10060
+ anchor: "center",
10061
+ radiusX: r,
10062
+ fill: color,
10063
+ blendMode: "lighter",
10064
+ opacity: fade,
10065
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10066
+ };
10067
+ }
10068
+ function proceduralShapes(view, pos, fade, progress) {
10069
+ const size = view.size ?? 0.5;
10070
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
10071
+ switch (view.shape ?? "spark") {
10072
+ case "ring": {
10073
+ return [
10074
+ {
10075
+ type: "draw-shape",
10076
+ shape: "ellipse",
10077
+ position: pos,
10078
+ anchor: "center",
10079
+ radiusX: size * easeOut2(progress),
10080
+ stroke: view.color2 ?? color,
10081
+ strokeWidth: 2,
10082
+ blendMode: "lighter",
10083
+ opacity: fade,
10084
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10085
+ }
10086
+ ];
10087
+ }
10088
+ case "puff": {
10089
+ const count = view.count ?? 3;
10090
+ return Array.from({ length: count }, (_, i) => {
10091
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
10092
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
10093
+ return {
10094
+ type: "draw-shape",
10095
+ shape: "ellipse",
10096
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
10097
+ anchor: "center",
10098
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
10099
+ fill: i === 0 && view.color2 ? view.color2 : color,
10100
+ opacity: fade * 0.6,
10101
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10102
+ };
10103
+ });
10104
+ }
10105
+ case "streak": {
10106
+ const count = view.count ?? 5;
10107
+ return Array.from({ length: count }, (_, i) => {
10108
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10109
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
10110
+ const len = size * 0.35;
10111
+ return {
10112
+ type: "draw-shape",
10113
+ shape: "ellipse",
10114
+ position: pos,
10115
+ anchor: "center",
10116
+ offsetX: inner + len / 2,
10117
+ offsetY: 0,
10118
+ radiusX: len / 2,
10119
+ radiusY: size * 0.04,
10120
+ rotate: angle,
10121
+ fill: color,
10122
+ blendMode: "lighter",
10123
+ opacity: fade,
10124
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10125
+ };
10126
+ });
10127
+ }
10128
+ default: {
10129
+ const count = view.count ?? 6;
10130
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
10131
+ }
10132
+ }
10133
+ }
10134
+ function expandFxItem(view, node, epochNowMs, dim) {
10135
+ if (view.space === "screen") return [];
10136
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
10137
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
10138
+ if (progress >= 1) return [];
10139
+ const pos = fxPosition(view, dim, ageMs / 1e3);
10140
+ if (!pos) return [];
10141
+ const out = [];
10142
+ const artItems = node.art?.[view.type]?.["idle"];
10143
+ const sprite = node.sprites?.[view.type];
10144
+ if (Array.isArray(artItems)) {
10145
+ out.push({
10146
+ type: "draw-group",
10147
+ position: pos,
10148
+ opacity: fade,
10149
+ ...view.size !== void 0 ? { scale: view.size } : {},
10150
+ items: artItems
10151
+ });
10152
+ } else if (sprite?.url) {
10153
+ const spriteSize = view.size ?? 0.6;
10154
+ out.push({
10155
+ type: "draw-sprite",
10156
+ position: pos,
10157
+ asset: sprite,
10158
+ anchor: "center",
10159
+ width: spriteSize,
10160
+ height: spriteSize,
10161
+ opacity: fade
10162
+ });
10163
+ } else {
10164
+ out.push(...proceduralShapes(view, pos, fade, progress));
10165
+ }
10166
+ if (view.message) {
10167
+ const text = {
10168
+ type: "draw-text",
10169
+ text: view.message,
10170
+ position: pos,
10171
+ offsetY: -(0.15 + 0.55 * progress),
10172
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
10173
+ opacity: fade
10174
+ };
10175
+ out.push(text);
10176
+ }
10177
+ return out;
10178
+ }
10179
+ function expandFxLayer(node, epochNowMs, dim) {
10180
+ if (!Array.isArray(node.items)) return [];
10181
+ const out = [];
10182
+ for (const item of node.items) {
10183
+ if (!item || typeof item.id !== "string") continue;
10184
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
10185
+ }
10186
+ return out;
10187
+ }
10188
+ function DrawFxLayer(_props) {
10189
+ return null;
10190
+ }
10191
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
10192
+ var init_DrawFxLayer = __esm({
10193
+ "components/game/molecules/DrawFxLayer.tsx"() {
10194
+ "use client";
10195
+ init_fx();
10196
+ DEFAULT_TICK_MS = 500;
10197
+ DEFAULT_TEXT_COLOR = "#ffe066";
10198
+ DEFAULT_SPARK_COLOR = "#ffffff";
10199
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
10200
+ }
10201
+ });
9991
10202
  function paintDrawable(painter, node, dctx) {
9992
10203
  switch (node.type) {
9993
10204
  case "draw-sprite":
@@ -10035,6 +10246,11 @@ function paintDrawable(painter, node, dctx) {
10035
10246
  case "draw-text-layer":
10036
10247
  paintTextLayer(painter, node, dctx);
10037
10248
  break;
10249
+ case "draw-fx-layer": {
10250
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
10251
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
10252
+ break;
10253
+ }
10038
10254
  }
10039
10255
  }
10040
10256
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -10049,6 +10265,7 @@ var init_paintDispatch = __esm({
10049
10265
  init_DrawSpriteLayer();
10050
10266
  init_DrawShapeLayer();
10051
10267
  init_DrawTextLayer();
10268
+ init_DrawFxLayer();
10052
10269
  paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
10053
10270
  warnedUnsupported2d = /* @__PURE__ */ new Set();
10054
10271
  warnUnsupported2d = (kind) => {
@@ -10297,6 +10514,7 @@ function Canvas2D({
10297
10514
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10298
10515
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10299
10516
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10517
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
10300
10518
  return false;
10301
10519
  };
10302
10520
  const animRafRef = React85.useRef(0);
@@ -20816,17 +21034,27 @@ var init_Breadcrumb = __esm({
20816
21034
  init_useEventBus();
20817
21035
  Breadcrumb = ({
20818
21036
  items,
21037
+ fromNavStack = false,
21038
+ itemEvent,
20819
21039
  separator = "chevron-right",
20820
21040
  maxItems,
20821
21041
  className
20822
21042
  }) => {
20823
21043
  const eventBus = useEventBus();
20824
21044
  const { t } = hooks.useTranslate();
20825
- const displayItems = maxItems && items.length > maxItems ? [
20826
- ...items.slice(0, 1),
21045
+ const navStack = providers.useNavStack();
21046
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
21047
+ label: entry.label,
21048
+ path: entry.href,
21049
+ isCurrent: i === navStack.entries.length - 1,
21050
+ event: itemEvent
21051
+ })) : items ?? [];
21052
+ if (fromNavStack && sourceItems.length === 0) return null;
21053
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
21054
+ ...sourceItems.slice(0, 1),
20827
21055
  { label: "...", isCurrent: false },
20828
- ...items.slice(-maxItems + 1)
20829
- ] : items;
21056
+ ...sourceItems.slice(-maxItems + 1)
21057
+ ] : sourceItems;
20830
21058
  return /* @__PURE__ */ jsxRuntime.jsx(
20831
21059
  "nav",
20832
21060
  {
@@ -20836,7 +21064,7 @@ var init_Breadcrumb = __esm({
20836
21064
  const isLast = index === displayItems.length - 1;
20837
21065
  const isEllipsis = item.label === "...";
20838
21066
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
20839
- isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxRuntime.jsxs(
21067
+ isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxRuntime.jsxs(
20840
21068
  "a",
20841
21069
  {
20842
21070
  href: item.href || item.path,
@@ -20862,7 +21090,12 @@ var init_Breadcrumb = __esm({
20862
21090
  {
20863
21091
  type: "button",
20864
21092
  onClick: () => {
20865
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
21093
+ const href = item.path ?? item.href;
21094
+ if (item.event) {
21095
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
21096
+ } else if (fromNavStack && href) {
21097
+ navStack.goTo(href);
21098
+ }
20866
21099
  item.onClick?.();
20867
21100
  },
20868
21101
  className: cn(
@@ -27691,6 +27924,196 @@ var init_PageTransition = __esm({
27691
27924
  PageTransition.displayName = "PageTransition";
27692
27925
  }
27693
27926
  });
27927
+ function ConfettiBurst({ item, lifeMs }) {
27928
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
27929
+ const left = (item.x ?? 0.5) * 100;
27930
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27931
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
27932
+ const rad = p.angle * Math.PI / 180;
27933
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
27934
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
27935
+ return /* @__PURE__ */ jsxRuntime.jsx(
27936
+ Box,
27937
+ {
27938
+ className: "absolute rounded-sm",
27939
+ style: {
27940
+ left: (p.left - 50) * 2,
27941
+ top: -10,
27942
+ width: p.size,
27943
+ height: p.size,
27944
+ backgroundColor: item.color ?? p.color,
27945
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
27946
+ opacity: 0,
27947
+ "--confetti-tx": `${tx}px`,
27948
+ "--confetti-ty": `${ty}px`,
27949
+ "--confetti-rotate": `${p.rotation}deg`
27950
+ }
27951
+ },
27952
+ p.id
27953
+ );
27954
+ }) });
27955
+ }
27956
+ function SparkleBurst({ item, lifeMs }) {
27957
+ const count = item.particleCount ?? 8;
27958
+ const scale = item.size ?? 1;
27959
+ const left = (item.x ?? 0.5) * 100;
27960
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27961
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
27962
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
27963
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
27964
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
27965
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
27966
+ return /* @__PURE__ */ jsxRuntime.jsx(
27967
+ Box,
27968
+ {
27969
+ className: "absolute rounded-full",
27970
+ style: {
27971
+ left: dx,
27972
+ top: dy,
27973
+ width: dot,
27974
+ height: dot,
27975
+ backgroundColor: item.color ?? "gold",
27976
+ opacity: 0,
27977
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
27978
+ }
27979
+ },
27980
+ i
27981
+ );
27982
+ }) });
27983
+ }
27984
+ function OverlayFxNode({ item, tickMs }) {
27985
+ const lifeMs = lifeMsOf(item, tickMs);
27986
+ switch (item.effect ?? "sparkle") {
27987
+ case "confetti":
27988
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfettiBurst, { item, lifeMs });
27989
+ case "flash":
27990
+ return /* @__PURE__ */ jsxRuntime.jsx(
27991
+ Box,
27992
+ {
27993
+ position: "absolute",
27994
+ className: "inset-0",
27995
+ style: {
27996
+ backgroundColor: item.color ?? "#ffffff",
27997
+ opacity: 0,
27998
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
27999
+ }
28000
+ }
28001
+ );
28002
+ case "streak-glow":
28003
+ return /* @__PURE__ */ jsxRuntime.jsx(
28004
+ Box,
28005
+ {
28006
+ position: "absolute",
28007
+ className: "inset-0",
28008
+ style: {
28009
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
28010
+ opacity: 0,
28011
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
28012
+ }
28013
+ }
28014
+ );
28015
+ case "float-text": {
28016
+ if (!item.message) return null;
28017
+ return /* @__PURE__ */ jsxRuntime.jsx(
28018
+ Box,
28019
+ {
28020
+ position: "absolute",
28021
+ style: {
28022
+ left: `${(item.x ?? 0.5) * 100}%`,
28023
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
28024
+ color: item.color ?? "var(--color-success)",
28025
+ fontWeight: 700,
28026
+ fontSize: 16 * (item.size ?? 1),
28027
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
28028
+ opacity: 0,
28029
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
28030
+ whiteSpace: "nowrap"
28031
+ },
28032
+ children: item.message
28033
+ }
28034
+ );
28035
+ }
28036
+ case "shake":
28037
+ return null;
28038
+ default:
28039
+ return /* @__PURE__ */ jsxRuntime.jsx(SparkleBurst, { item, lifeMs });
28040
+ }
28041
+ }
28042
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
28043
+ var init_FxOverlay = __esm({
28044
+ "components/core/molecules/FxOverlay.tsx"() {
28045
+ "use client";
28046
+ init_cn();
28047
+ init_Box();
28048
+ init_fx();
28049
+ DEFAULT_TICK_MS2 = 500;
28050
+ FX_OVERLAY_KEYFRAMES = `
28051
+ ${CONFETTI_BURST_KEYFRAMES}
28052
+ @keyframes fx-overlay-flash {
28053
+ 0% { opacity: 0.75; }
28054
+ 100% { opacity: 0; }
28055
+ }
28056
+ @keyframes fx-overlay-glow {
28057
+ 0% { opacity: 0.9; }
28058
+ 60% { opacity: 0.6; }
28059
+ 100% { opacity: 0; }
28060
+ }
28061
+ @keyframes fx-overlay-sparkle {
28062
+ 0% { opacity: 0; transform: scale(0); }
28063
+ 30% { opacity: 1; transform: scale(1); }
28064
+ 100% { opacity: 0; transform: scale(0.3); }
28065
+ }
28066
+ @keyframes fx-overlay-float {
28067
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
28068
+ 15% { opacity: 1; }
28069
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
28070
+ }
28071
+ @keyframes fx-overlay-shake {
28072
+ 0% { transform: translateX(0); }
28073
+ 15% { transform: translateX(-6px); }
28074
+ 30% { transform: translateX(5px); }
28075
+ 45% { transform: translateX(-4px); }
28076
+ 60% { transform: translateX(3px); }
28077
+ 75% { transform: translateX(-2px); }
28078
+ 100% { transform: translateX(0); }
28079
+ }
28080
+ `;
28081
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28082
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28083
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28084
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28085
+ );
28086
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28087
+ const overlay = /* @__PURE__ */ jsxRuntime.jsxs(
28088
+ Box,
28089
+ {
28090
+ position: "absolute",
28091
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28092
+ "aria-hidden": "true",
28093
+ children: [
28094
+ screenItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28095
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28096
+ ]
28097
+ }
28098
+ );
28099
+ if (children !== void 0 && children !== null) {
28100
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { position: "relative", className, children: [
28101
+ /* @__PURE__ */ jsxRuntime.jsx(
28102
+ Box,
28103
+ {
28104
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28105
+ children
28106
+ },
28107
+ shakeItem?.id ?? "steady"
28108
+ ),
28109
+ overlay
28110
+ ] });
28111
+ }
28112
+ return overlay;
28113
+ };
28114
+ FxOverlay.displayName = "FxOverlay";
28115
+ }
28116
+ });
27694
28117
  function computePopoverStyle(position, triggerRect, popoverWidth) {
27695
28118
  if (position === "left" || position === "right") {
27696
28119
  return {
@@ -29696,7 +30119,7 @@ var init_MathCanvas = __esm({
29696
30119
  x: mapX((first.x + last.x) / 2),
29697
30120
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
29698
30121
  text: region.label,
29699
- color: "#111827",
30122
+ color,
29700
30123
  fontSize: 11
29701
30124
  });
29702
30125
  }
@@ -29729,14 +30152,14 @@ var init_MathCanvas = __esm({
29729
30152
  const px = mapX(guide.at);
29730
30153
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
29731
30154
  if (guide.label) {
29732
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30155
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
29733
30156
  }
29734
30157
  } else {
29735
30158
  if (guide.at < yMin || guide.at > yMax) continue;
29736
30159
  const py = mapY(guide.at);
29737
30160
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
29738
30161
  if (guide.label) {
29739
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30162
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
29740
30163
  }
29741
30164
  }
29742
30165
  }
@@ -29802,7 +30225,7 @@ var init_MathCanvas = __esm({
29802
30225
  x: (x1 + x2) / 2,
29803
30226
  y: xAxisY - peak - 8,
29804
30227
  text: hop.label,
29805
- color: "#111827",
30228
+ color,
29806
30229
  fontSize: 10,
29807
30230
  align: "center"
29808
30231
  });
@@ -29829,7 +30252,7 @@ var init_MathCanvas = __esm({
29829
30252
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
29830
30253
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
29831
30254
  text: angle.label,
29832
- color: "#111827",
30255
+ color,
29833
30256
  fontSize: 11,
29834
30257
  align: "center"
29835
30258
  });
@@ -29847,7 +30270,7 @@ var init_MathCanvas = __esm({
29847
30270
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29848
30271
  });
29849
30272
  if (p.label) {
29850
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30273
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
29851
30274
  }
29852
30275
  }
29853
30276
  for (const v of vectors) {
@@ -29858,7 +30281,7 @@ var init_MathCanvas = __esm({
29858
30281
  const y2 = mapY(v.y + v.vy);
29859
30282
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
29860
30283
  if (v.label) {
29861
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30284
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
29862
30285
  }
29863
30286
  }
29864
30287
  out.push(...shapes);
@@ -41271,6 +41694,7 @@ var init_DetailPanel = __esm({
41271
41694
  init_Box();
41272
41695
  init_Stack();
41273
41696
  init_SimpleGrid();
41697
+ init_Menu();
41274
41698
  init_LoadingState();
41275
41699
  init_ErrorState();
41276
41700
  init_EmptyState();
@@ -41288,6 +41712,7 @@ var init_DetailPanel = __esm({
41288
41712
  avatar,
41289
41713
  sections: propSections,
41290
41714
  actions,
41715
+ maxInlineActions,
41291
41716
  backAction,
41292
41717
  footer,
41293
41718
  slideOver = false,
@@ -41522,7 +41947,7 @@ var init_DetailPanel = __esm({
41522
41947
  }
41523
41948
  ) }),
41524
41949
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
41525
- otherActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
41950
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
41526
41951
  Button,
41527
41952
  {
41528
41953
  variant: action.variant || "secondary",
@@ -41537,6 +41962,22 @@ var init_DetailPanel = __esm({
41537
41962
  },
41538
41963
  idx
41539
41964
  )),
41965
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
41966
+ Menu,
41967
+ {
41968
+ position: "bottom-end",
41969
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
41970
+ items: otherActions.slice(maxInlineActions).map((action) => ({
41971
+ // ONE firing path: onClick → handleActionClick (emits with
41972
+ // the {id, row} payload). Passing `event` too would make
41973
+ // Menu emit a second, payload-less copy of the same event.
41974
+ label: action.label,
41975
+ icon: action.icon,
41976
+ variant: action.variant === "danger" ? "danger" : "default",
41977
+ onClick: () => handleActionClick(action, normalizedData)
41978
+ }))
41979
+ }
41980
+ ),
41540
41981
  /* @__PURE__ */ jsxRuntime.jsx(
41541
41982
  Button,
41542
41983
  {
@@ -47412,6 +47853,7 @@ var init_component_registry_generated = __esm({
47412
47853
  init_DocSidebar();
47413
47854
  init_DocTOC();
47414
47855
  init_DocumentViewer();
47856
+ init_DrawFxLayer();
47415
47857
  init_DrawGroup();
47416
47858
  init_DrawMesh();
47417
47859
  init_DrawShape();
@@ -47442,6 +47884,7 @@ var init_component_registry_generated = __esm({
47442
47884
  init_FormField();
47443
47885
  init_FormSection();
47444
47886
  init_FormSectionHeader();
47887
+ init_FxOverlay();
47445
47888
  init_GameAudioToggle();
47446
47889
  init_GameHud();
47447
47890
  init_GameIcon();
@@ -47682,6 +48125,7 @@ var init_component_registry_generated = __esm({
47682
48125
  "DocSidebar": DocSidebar,
47683
48126
  "DocTOC": DocTOC,
47684
48127
  "DocumentViewer": DocumentViewer,
48128
+ "DrawFxLayer": DrawFxLayer,
47685
48129
  "DrawGroup": DrawGroup,
47686
48130
  "DrawMesh": DrawMesh,
47687
48131
  "DrawShape": DrawShape,
@@ -47712,6 +48156,7 @@ var init_component_registry_generated = __esm({
47712
48156
  "FormField": FormField,
47713
48157
  "FormLayout": FormLayout,
47714
48158
  "FormSectionHeader": FormSectionHeader,
48159
+ "FxOverlay": FxOverlay,
47715
48160
  "GameAudioToggle": GameAudioToggle,
47716
48161
  "GameHud": GameHud,
47717
48162
  "GameIcon": GameIcon,
@@ -48977,7 +49422,7 @@ function runTickFrame(entityId, orderedWriters, store) {
48977
49422
  }
48978
49423
  var log3 = logger.createLogger("almadar:ui:effects:client-handlers");
48979
49424
  function createClientEffectHandlers(options) {
48980
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
49425
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
48981
49426
  return {
48982
49427
  emit: (event, payload, source) => {
48983
49428
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -49027,6 +49472,9 @@ function createClientEffectHandlers(options) {
49027
49472
  }
49028
49473
  log3.warn("No navigate handler, ignoring", { path });
49029
49474
  }),
49475
+ navigateBack: navigateBack ?? (() => {
49476
+ log3.warn("No navigate-back handler, ignoring");
49477
+ }),
49030
49478
  notify: notify ?? ((msg, type) => {
49031
49479
  log3.debug("notify", { type, message: msg });
49032
49480
  })
@@ -49666,6 +50114,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49666
50114
  }
49667
50115
  },
49668
50116
  navigate: optionsRef.current?.navigate,
50117
+ navigateBack: optionsRef.current?.navigateBack,
49669
50118
  notify: optionsRef.current?.notify,
49670
50119
  callService: optionsRef.current?.callService,
49671
50120
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -49727,6 +50176,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49727
50176
  emit: clientHandlers.emit,
49728
50177
  renderUI: clientHandlers.renderUI,
49729
50178
  navigate: clientHandlers.navigate,
50179
+ navigateBack: clientHandlers.navigateBack,
49730
50180
  notify: clientHandlers.notify
49731
50181
  };
49732
50182
  }
@@ -50449,7 +50899,7 @@ function normalizeChild(child) {
50449
50899
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
50450
50900
  };
50451
50901
  }
50452
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
50902
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
50453
50903
  for (const eff of effects) {
50454
50904
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
50455
50905
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -50495,7 +50945,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
50495
50945
  });
50496
50946
  }
50497
50947
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
50498
- onNavigate(eff.route, eff.params);
50948
+ onNavigate(eff.route, eff.params, eff.crumb);
50949
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
50950
+ onNavigateBack();
50499
50951
  }
50500
50952
  }
50501
50953
  }
@@ -50518,7 +50970,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
50518
50970
  }
50519
50971
  return active;
50520
50972
  }
50521
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
50973
+ function NavStackRefBridge({ apiRef }) {
50974
+ const api = providers.useNavStack();
50975
+ React85.useEffect(() => {
50976
+ apiRef.current = api;
50977
+ return () => {
50978
+ apiRef.current = null;
50979
+ };
50980
+ }, [api, apiRef]);
50981
+ return null;
50982
+ }
50983
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onNavigateBack, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
50522
50984
  const bridge = providers.useServerBridge();
50523
50985
  const activeTraitNames = React85.useMemo(
50524
50986
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -50544,10 +51006,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
50544
51006
  for (const name of targets) {
50545
51007
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
50546
51008
  recordServerResponse(name, event, meta);
50547
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
51009
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
50548
51010
  }
50549
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50550
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
51011
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
51012
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
50551
51013
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
50552
51014
  const initSentRef = React85.useRef(false);
50553
51015
  const prevTraitsRef = React85.useRef(void 0);
@@ -50606,13 +51068,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
50606
51068
  effects: effectTraces,
50607
51069
  timestamp: Date.now()
50608
51070
  });
50609
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
51071
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
50610
51072
  }
50611
51073
  })();
50612
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
51074
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
50613
51075
  return /* @__PURE__ */ jsxRuntime.jsx(providers.EntityBindingContext.Provider, { value: entityBindingSource, children });
50614
51076
  }
50615
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
51077
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
50616
51078
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
50617
51079
  const allPageTraits = React85.useMemo(() => {
50618
51080
  let base;
@@ -50760,6 +51222,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
50760
51222
  embeddedTraits,
50761
51223
  serverActiveTraits,
50762
51224
  onNavigate,
51225
+ onNavigateBack,
50763
51226
  onLocalFallback,
50764
51227
  persistence,
50765
51228
  children: /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalThemeProvider, { theme: activeOrbitalTheme, children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "h-full min-h-full overflow-auto p-4", children: /* @__PURE__ */ jsxRuntime.jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) }) })
@@ -50852,6 +51315,15 @@ function OrbPreview({
50852
51315
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
50853
51316
  return resolved ?? initialPagePath;
50854
51317
  }, [pages, currentPage, initialPagePath]);
51318
+ const navPages = React85.useMemo(
51319
+ () => pages.filter((p) => typeof p.page.path === "string" && p.page.path.length > 0).map((p) => ({ path: p.page.path, name: p.page.name, orbital: p.orbitalName })),
51320
+ [pages]
51321
+ );
51322
+ const concreteCurrentPath = React85.useMemo(() => {
51323
+ const pattern = currentPagePath ?? "/";
51324
+ return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
51325
+ }, [currentPagePath, routeParams]);
51326
+ const navStackRef = React85.useRef(null);
50855
51327
  const handleNavigate = React85.useCallback((path) => {
50856
51328
  const hit = providers.matchPathAmong(pages, path, (entry) => entry.page.path);
50857
51329
  const match = hit?.candidate;
@@ -50873,6 +51345,16 @@ function OrbPreview({
50873
51345
  }
50874
51346
  }
50875
51347
  }, [pages]);
51348
+ const handleNavigateEffect = React85.useCallback(
51349
+ (path, _params, crumb) => {
51350
+ navStackRef.current?.beginNavigate(path, crumb);
51351
+ handleNavigate(path);
51352
+ },
51353
+ [handleNavigate]
51354
+ );
51355
+ const handleNavigateBack = React85.useCallback(() => {
51356
+ navStackRef.current?.back();
51357
+ }, []);
50876
51358
  React85.useEffect(() => {
50877
51359
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
50878
51360
  const url = event.payload?.url;
@@ -50925,20 +51407,33 @@ function OrbPreview({
50925
51407
  style: { height },
50926
51408
  children: [
50927
51409
  localFallback && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-3 py-2 bg-[var(--color-warning)] bg-opacity-10 border-b border-[var(--color-warning)] flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-[var(--color-warning-foreground)] flex-1", children: "Preview server unreachable \u2014 running locally. Server-side state and persistence are disabled." }) }),
50928
- /* @__PURE__ */ jsxRuntime.jsx(providers.CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
50929
- SchemaRunner,
51410
+ /* @__PURE__ */ jsxRuntime.jsx(providers.CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxRuntime.jsxs(
51411
+ providers.NavStackProvider,
50930
51412
  {
50931
- schema: parseResult.schema,
50932
- serverUrl,
50933
- transport,
50934
- mockData: effectiveMockData,
50935
- pageName: currentPage,
50936
- routeParams,
50937
- onNavigate: handleNavigate,
50938
- onLocalFallback: handleLocalFallback,
50939
- persistence
50940
- }
50941
- ) }) }) })
51413
+ pages: navPages,
51414
+ currentPath: concreteCurrentPath,
51415
+ navigate: handleNavigate,
51416
+ storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
51417
+ children: [
51418
+ /* @__PURE__ */ jsxRuntime.jsx(NavStackRefBridge, { apiRef: navStackRef }),
51419
+ /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
51420
+ SchemaRunner,
51421
+ {
51422
+ schema: parseResult.schema,
51423
+ serverUrl,
51424
+ transport,
51425
+ mockData: effectiveMockData,
51426
+ pageName: currentPage,
51427
+ routeParams,
51428
+ onNavigate: handleNavigateEffect,
51429
+ onNavigateBack: handleNavigateBack,
51430
+ onLocalFallback: handleLocalFallback,
51431
+ persistence
51432
+ }
51433
+ ) }) })
51434
+ ]
51435
+ }
51436
+ ) })
50942
51437
  ]
50943
51438
  }
50944
51439
  );