@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
@@ -9546,27 +9546,61 @@ var init_InfiniteScrollSentinel = __esm({
9546
9546
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
9547
9547
  }
9548
9548
  });
9549
- function createParticles(count) {
9550
- return Array.from({ length: count }, () => {
9551
- particleIdCounter += 1;
9552
- return {
9553
- id: particleIdCounter,
9554
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
9555
- left: 30 + Math.random() * 40,
9556
- delay: Math.random() * 300,
9557
- angle: Math.random() * 360,
9558
- distance: 40 + Math.random() * 80,
9559
- rotation: Math.random() * 720 - 360,
9560
- size: 4 + Math.random() * 6
9561
- };
9562
- });
9549
+
9550
+ // components/core/atoms/fx.ts
9551
+ function resolveFxView(item, presets) {
9552
+ const row = presets?.find((p) => p.type === item.type);
9553
+ if (!row) return item;
9554
+ return {
9555
+ ...item,
9556
+ space: item.space ?? row.space,
9557
+ effect: item.effect ?? row.effect,
9558
+ color: item.color ?? row.color,
9559
+ size: item.size ?? row.size,
9560
+ vx: item.vx ?? row.vx,
9561
+ vy: item.vy ?? row.vy,
9562
+ vz: item.vz ?? row.vz,
9563
+ particleCount: item.particleCount ?? row.particleCount,
9564
+ shape: row.shape,
9565
+ count: row.count,
9566
+ glow: row.glow,
9567
+ gravity: row.gravity,
9568
+ color2: row.color2
9569
+ };
9563
9570
  }
9564
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
9565
- var init_ConfettiEffect = __esm({
9566
- "components/core/atoms/ConfettiEffect.tsx"() {
9567
- "use client";
9568
- init_cn();
9569
- init_Box();
9571
+ function fxLifecycle(item, epochNowMs, tickMs) {
9572
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
9573
+ const lifeMs = maxTtl * tickMs;
9574
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
9575
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
9576
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
9577
+ }
9578
+ function fxHash01(seed, salt) {
9579
+ let h = 2166136261 ^ salt;
9580
+ for (let i = 0; i < seed.length; i++) {
9581
+ h ^= seed.charCodeAt(i);
9582
+ h = Math.imul(h, 16777619);
9583
+ }
9584
+ h ^= h >>> 13;
9585
+ h = Math.imul(h, 1274126177);
9586
+ h ^= h >>> 16;
9587
+ return (h >>> 0) / 4294967296;
9588
+ }
9589
+ function createConfettiParticles(count, seed) {
9590
+ return Array.from({ length: count }, (_, i) => ({
9591
+ id: i,
9592
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
9593
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
9594
+ delay: fxHash01(seed, i * 7 + 3) * 300,
9595
+ angle: fxHash01(seed, i * 7 + 4) * 360,
9596
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
9597
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
9598
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
9599
+ }));
9600
+ }
9601
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
9602
+ var init_fx = __esm({
9603
+ "components/core/atoms/fx.ts"() {
9570
9604
  CONFETTI_COLORS = [
9571
9605
  "var(--color-primary)",
9572
9606
  "var(--color-success)",
@@ -9575,7 +9609,30 @@ var init_ConfettiEffect = __esm({
9575
9609
  "gold",
9576
9610
  "dodgerblue"
9577
9611
  ];
9578
- particleIdCounter = 0;
9612
+ CONFETTI_BURST_KEYFRAMES = `
9613
+ @keyframes confetti-burst {
9614
+ 0% {
9615
+ opacity: 1;
9616
+ transform: translate(0, 0) rotate(0deg) scale(1);
9617
+ }
9618
+ 70% {
9619
+ opacity: 1;
9620
+ }
9621
+ 100% {
9622
+ opacity: 0;
9623
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9624
+ }
9625
+ }
9626
+ `;
9627
+ }
9628
+ });
9629
+ var ConfettiEffect;
9630
+ var init_ConfettiEffect = __esm({
9631
+ "components/core/atoms/ConfettiEffect.tsx"() {
9632
+ "use client";
9633
+ init_cn();
9634
+ init_Box();
9635
+ init_fx();
9579
9636
  ConfettiEffect = ({
9580
9637
  trigger,
9581
9638
  duration = 2e3,
@@ -9584,11 +9641,13 @@ var init_ConfettiEffect = __esm({
9584
9641
  }) => {
9585
9642
  const [particles, setParticles] = React94.useState([]);
9586
9643
  const previousTriggerRef = React94.useRef(false);
9644
+ const burstRef = React94.useRef(0);
9587
9645
  React94.useEffect(() => {
9588
9646
  const wasFalse = !previousTriggerRef.current;
9589
9647
  previousTriggerRef.current = trigger;
9590
9648
  if (trigger && wasFalse) {
9591
- const newParticles = createParticles(particleCount);
9649
+ burstRef.current += 1;
9650
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
9592
9651
  setParticles(newParticles);
9593
9652
  const timer = window.setTimeout(() => {
9594
9653
  setParticles([]);
@@ -9636,21 +9695,7 @@ var init_ConfettiEffect = __esm({
9636
9695
  p.id
9637
9696
  );
9638
9697
  }),
9639
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
9640
- @keyframes confetti-burst {
9641
- 0% {
9642
- opacity: 1;
9643
- transform: translate(0, 0) rotate(0deg) scale(1);
9644
- }
9645
- 70% {
9646
- opacity: 1;
9647
- }
9648
- 100% {
9649
- opacity: 0;
9650
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9651
- }
9652
- }
9653
- ` })
9698
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
9654
9699
  ]
9655
9700
  }
9656
9701
  );
@@ -13206,7 +13251,7 @@ var init_DrawShape = __esm({
13206
13251
  const cx = p.x + (node.offsetX ?? 0) * tw;
13207
13252
  const cy = p.y + (node.offsetY ?? 0) * tw;
13208
13253
  const rx = (node.radiusX ?? 0) * tw;
13209
- const ry = (node.radiusY ?? rx) * tw;
13254
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
13210
13255
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
13211
13256
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
13212
13257
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -13396,6 +13441,172 @@ var init_DrawTextLayer = __esm({
13396
13441
  };
13397
13442
  }
13398
13443
  });
13444
+
13445
+ // components/game/molecules/DrawFxLayer.tsx
13446
+ function fxPosition(view, dim, ageSec) {
13447
+ const g = view.gravity ?? 0;
13448
+ const fall = 0.5 * g * ageSec * ageSec;
13449
+ {
13450
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
13451
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
13452
+ return {
13453
+ x: base2.x + (view.vx ?? 0) * ageSec,
13454
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
13455
+ };
13456
+ }
13457
+ }
13458
+ function sparkShape(view, pos, i, fade, progress) {
13459
+ const size = view.size ?? 0.5;
13460
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13461
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
13462
+ const r2 = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
13463
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13464
+ return {
13465
+ type: "draw-shape",
13466
+ shape: "ellipse",
13467
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
13468
+ anchor: "center",
13469
+ radiusX: r2,
13470
+ fill: color,
13471
+ blendMode: "lighter",
13472
+ opacity: fade,
13473
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13474
+ };
13475
+ }
13476
+ function proceduralShapes(view, pos, fade, progress) {
13477
+ const size = view.size ?? 0.5;
13478
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13479
+ switch (view.shape ?? "spark") {
13480
+ case "ring": {
13481
+ return [
13482
+ {
13483
+ type: "draw-shape",
13484
+ shape: "ellipse",
13485
+ position: pos,
13486
+ anchor: "center",
13487
+ radiusX: size * easeOut2(progress),
13488
+ stroke: view.color2 ?? color,
13489
+ strokeWidth: 2,
13490
+ blendMode: "lighter",
13491
+ opacity: fade,
13492
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13493
+ }
13494
+ ];
13495
+ }
13496
+ case "puff": {
13497
+ const count = view.count ?? 3;
13498
+ return Array.from({ length: count }, (_, i) => {
13499
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
13500
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
13501
+ return {
13502
+ type: "draw-shape",
13503
+ shape: "ellipse",
13504
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
13505
+ anchor: "center",
13506
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
13507
+ fill: i === 0 && view.color2 ? view.color2 : color,
13508
+ opacity: fade * 0.6,
13509
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13510
+ };
13511
+ });
13512
+ }
13513
+ case "streak": {
13514
+ const count = view.count ?? 5;
13515
+ return Array.from({ length: count }, (_, i) => {
13516
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13517
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
13518
+ const len = size * 0.35;
13519
+ return {
13520
+ type: "draw-shape",
13521
+ shape: "ellipse",
13522
+ position: pos,
13523
+ anchor: "center",
13524
+ offsetX: inner + len / 2,
13525
+ offsetY: 0,
13526
+ radiusX: len / 2,
13527
+ radiusY: size * 0.04,
13528
+ rotate: angle,
13529
+ fill: color,
13530
+ blendMode: "lighter",
13531
+ opacity: fade,
13532
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13533
+ };
13534
+ });
13535
+ }
13536
+ default: {
13537
+ const count = view.count ?? 6;
13538
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
13539
+ }
13540
+ }
13541
+ }
13542
+ function expandFxItem(view, node, epochNowMs, dim) {
13543
+ if (view.space === "screen") return [];
13544
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
13545
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
13546
+ if (progress >= 1) return [];
13547
+ const pos = fxPosition(view, dim, ageMs / 1e3);
13548
+ if (!pos) return [];
13549
+ const out = [];
13550
+ const artItems = node.art?.[view.type]?.["idle"];
13551
+ const sprite = node.sprites?.[view.type];
13552
+ if (Array.isArray(artItems)) {
13553
+ out.push({
13554
+ type: "draw-group",
13555
+ position: pos,
13556
+ opacity: fade,
13557
+ ...view.size !== void 0 ? { scale: view.size } : {},
13558
+ items: artItems
13559
+ });
13560
+ } else if (sprite?.url) {
13561
+ const spriteSize = view.size ?? 0.6;
13562
+ out.push({
13563
+ type: "draw-sprite",
13564
+ position: pos,
13565
+ asset: sprite,
13566
+ anchor: "center",
13567
+ width: spriteSize,
13568
+ height: spriteSize,
13569
+ opacity: fade
13570
+ });
13571
+ } else {
13572
+ out.push(...proceduralShapes(view, pos, fade, progress));
13573
+ }
13574
+ if (view.message) {
13575
+ const text = {
13576
+ type: "draw-text",
13577
+ text: view.message,
13578
+ position: pos,
13579
+ offsetY: -(0.15 + 0.55 * progress),
13580
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
13581
+ opacity: fade
13582
+ };
13583
+ out.push(text);
13584
+ }
13585
+ return out;
13586
+ }
13587
+ function expandFxLayer(node, epochNowMs, dim) {
13588
+ if (!Array.isArray(node.items)) return [];
13589
+ const out = [];
13590
+ for (const item of node.items) {
13591
+ if (!item || typeof item.id !== "string") continue;
13592
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
13593
+ }
13594
+ return out;
13595
+ }
13596
+ function DrawFxLayer(_props) {
13597
+ return null;
13598
+ }
13599
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
13600
+ var init_DrawFxLayer = __esm({
13601
+ "components/game/molecules/DrawFxLayer.tsx"() {
13602
+ "use client";
13603
+ init_fx();
13604
+ DEFAULT_TICK_MS = 500;
13605
+ DEFAULT_TEXT_COLOR = "#ffe066";
13606
+ DEFAULT_SPARK_COLOR = "#ffffff";
13607
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
13608
+ }
13609
+ });
13399
13610
  function paintDrawable(painter, node, dctx) {
13400
13611
  switch (node.type) {
13401
13612
  case "draw-sprite":
@@ -13443,6 +13654,11 @@ function paintDrawable(painter, node, dctx) {
13443
13654
  case "draw-text-layer":
13444
13655
  paintTextLayer(painter, node, dctx);
13445
13656
  break;
13657
+ case "draw-fx-layer": {
13658
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
13659
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
13660
+ break;
13661
+ }
13446
13662
  }
13447
13663
  }
13448
13664
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -13457,6 +13673,7 @@ var init_paintDispatch = __esm({
13457
13673
  init_DrawSpriteLayer();
13458
13674
  init_DrawShapeLayer();
13459
13675
  init_DrawTextLayer();
13676
+ init_DrawFxLayer();
13460
13677
  paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
13461
13678
  warnedUnsupported2d = /* @__PURE__ */ new Set();
13462
13679
  warnUnsupported2d = (kind) => {
@@ -13705,6 +13922,7 @@ function Canvas2D({
13705
13922
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13706
13923
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13707
13924
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13925
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
13708
13926
  return false;
13709
13927
  };
13710
13928
  const animRafRef = React94.useRef(0);
@@ -23447,17 +23665,27 @@ var init_Breadcrumb = __esm({
23447
23665
  init_useEventBus();
23448
23666
  Breadcrumb = ({
23449
23667
  items,
23668
+ fromNavStack = false,
23669
+ itemEvent,
23450
23670
  separator = "chevron-right",
23451
23671
  maxItems,
23452
23672
  className
23453
23673
  }) => {
23454
23674
  const eventBus = useEventBus();
23455
23675
  const { t } = hooks.useTranslate();
23456
- const displayItems = maxItems && items.length > maxItems ? [
23457
- ...items.slice(0, 1),
23676
+ const navStack = providers.useNavStack();
23677
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
23678
+ label: entry.label,
23679
+ path: entry.href,
23680
+ isCurrent: i === navStack.entries.length - 1,
23681
+ event: itemEvent
23682
+ })) : items ?? [];
23683
+ if (fromNavStack && sourceItems.length === 0) return null;
23684
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
23685
+ ...sourceItems.slice(0, 1),
23458
23686
  { label: "...", isCurrent: false },
23459
- ...items.slice(-maxItems + 1)
23460
- ] : items;
23687
+ ...sourceItems.slice(-maxItems + 1)
23688
+ ] : sourceItems;
23461
23689
  return /* @__PURE__ */ jsxRuntime.jsx(
23462
23690
  "nav",
23463
23691
  {
@@ -23467,7 +23695,7 @@ var init_Breadcrumb = __esm({
23467
23695
  const isLast = index === displayItems.length - 1;
23468
23696
  const isEllipsis = item.label === "...";
23469
23697
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
23470
- isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxRuntime.jsxs(
23698
+ isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxRuntime.jsxs(
23471
23699
  "a",
23472
23700
  {
23473
23701
  href: item.href || item.path,
@@ -23493,7 +23721,12 @@ var init_Breadcrumb = __esm({
23493
23721
  {
23494
23722
  type: "button",
23495
23723
  onClick: () => {
23496
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
23724
+ const href = item.path ?? item.href;
23725
+ if (item.event) {
23726
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
23727
+ } else if (fromNavStack && href) {
23728
+ navStack.goTo(href);
23729
+ }
23497
23730
  item.onClick?.();
23498
23731
  },
23499
23732
  className: cn(
@@ -30467,6 +30700,196 @@ var init_PageTransition = __esm({
30467
30700
  PageTransition.displayName = "PageTransition";
30468
30701
  }
30469
30702
  });
30703
+ function ConfettiBurst({ item, lifeMs }) {
30704
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
30705
+ const left = (item.x ?? 0.5) * 100;
30706
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30707
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
30708
+ const rad = p.angle * Math.PI / 180;
30709
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
30710
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
30711
+ return /* @__PURE__ */ jsxRuntime.jsx(
30712
+ Box,
30713
+ {
30714
+ className: "absolute rounded-sm",
30715
+ style: {
30716
+ left: (p.left - 50) * 2,
30717
+ top: -10,
30718
+ width: p.size,
30719
+ height: p.size,
30720
+ backgroundColor: item.color ?? p.color,
30721
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
30722
+ opacity: 0,
30723
+ "--confetti-tx": `${tx}px`,
30724
+ "--confetti-ty": `${ty}px`,
30725
+ "--confetti-rotate": `${p.rotation}deg`
30726
+ }
30727
+ },
30728
+ p.id
30729
+ );
30730
+ }) });
30731
+ }
30732
+ function SparkleBurst({ item, lifeMs }) {
30733
+ const count = item.particleCount ?? 8;
30734
+ const scale = item.size ?? 1;
30735
+ const left = (item.x ?? 0.5) * 100;
30736
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30737
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
30738
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
30739
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
30740
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
30741
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
30742
+ return /* @__PURE__ */ jsxRuntime.jsx(
30743
+ Box,
30744
+ {
30745
+ className: "absolute rounded-full",
30746
+ style: {
30747
+ left: dx,
30748
+ top: dy,
30749
+ width: dot,
30750
+ height: dot,
30751
+ backgroundColor: item.color ?? "gold",
30752
+ opacity: 0,
30753
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
30754
+ }
30755
+ },
30756
+ i
30757
+ );
30758
+ }) });
30759
+ }
30760
+ function OverlayFxNode({ item, tickMs }) {
30761
+ const lifeMs = lifeMsOf(item, tickMs);
30762
+ switch (item.effect ?? "sparkle") {
30763
+ case "confetti":
30764
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfettiBurst, { item, lifeMs });
30765
+ case "flash":
30766
+ return /* @__PURE__ */ jsxRuntime.jsx(
30767
+ Box,
30768
+ {
30769
+ position: "absolute",
30770
+ className: "inset-0",
30771
+ style: {
30772
+ backgroundColor: item.color ?? "#ffffff",
30773
+ opacity: 0,
30774
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
30775
+ }
30776
+ }
30777
+ );
30778
+ case "streak-glow":
30779
+ return /* @__PURE__ */ jsxRuntime.jsx(
30780
+ Box,
30781
+ {
30782
+ position: "absolute",
30783
+ className: "inset-0",
30784
+ style: {
30785
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
30786
+ opacity: 0,
30787
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
30788
+ }
30789
+ }
30790
+ );
30791
+ case "float-text": {
30792
+ if (!item.message) return null;
30793
+ return /* @__PURE__ */ jsxRuntime.jsx(
30794
+ Box,
30795
+ {
30796
+ position: "absolute",
30797
+ style: {
30798
+ left: `${(item.x ?? 0.5) * 100}%`,
30799
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
30800
+ color: item.color ?? "var(--color-success)",
30801
+ fontWeight: 700,
30802
+ fontSize: 16 * (item.size ?? 1),
30803
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
30804
+ opacity: 0,
30805
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
30806
+ whiteSpace: "nowrap"
30807
+ },
30808
+ children: item.message
30809
+ }
30810
+ );
30811
+ }
30812
+ case "shake":
30813
+ return null;
30814
+ default:
30815
+ return /* @__PURE__ */ jsxRuntime.jsx(SparkleBurst, { item, lifeMs });
30816
+ }
30817
+ }
30818
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
30819
+ var init_FxOverlay = __esm({
30820
+ "components/core/molecules/FxOverlay.tsx"() {
30821
+ "use client";
30822
+ init_cn();
30823
+ init_Box();
30824
+ init_fx();
30825
+ DEFAULT_TICK_MS2 = 500;
30826
+ FX_OVERLAY_KEYFRAMES = `
30827
+ ${CONFETTI_BURST_KEYFRAMES}
30828
+ @keyframes fx-overlay-flash {
30829
+ 0% { opacity: 0.75; }
30830
+ 100% { opacity: 0; }
30831
+ }
30832
+ @keyframes fx-overlay-glow {
30833
+ 0% { opacity: 0.9; }
30834
+ 60% { opacity: 0.6; }
30835
+ 100% { opacity: 0; }
30836
+ }
30837
+ @keyframes fx-overlay-sparkle {
30838
+ 0% { opacity: 0; transform: scale(0); }
30839
+ 30% { opacity: 1; transform: scale(1); }
30840
+ 100% { opacity: 0; transform: scale(0.3); }
30841
+ }
30842
+ @keyframes fx-overlay-float {
30843
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
30844
+ 15% { opacity: 1; }
30845
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
30846
+ }
30847
+ @keyframes fx-overlay-shake {
30848
+ 0% { transform: translateX(0); }
30849
+ 15% { transform: translateX(-6px); }
30850
+ 30% { transform: translateX(5px); }
30851
+ 45% { transform: translateX(-4px); }
30852
+ 60% { transform: translateX(3px); }
30853
+ 75% { transform: translateX(-2px); }
30854
+ 100% { transform: translateX(0); }
30855
+ }
30856
+ `;
30857
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
30858
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
30859
+ const screenItems = (Array.isArray(items) ? items : []).filter(
30860
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
30861
+ );
30862
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
30863
+ const overlay = /* @__PURE__ */ jsxRuntime.jsxs(
30864
+ Box,
30865
+ {
30866
+ position: "absolute",
30867
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
30868
+ "aria-hidden": "true",
30869
+ children: [
30870
+ screenItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
30871
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: FX_OVERLAY_KEYFRAMES })
30872
+ ]
30873
+ }
30874
+ );
30875
+ if (children !== void 0 && children !== null) {
30876
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { position: "relative", className, children: [
30877
+ /* @__PURE__ */ jsxRuntime.jsx(
30878
+ Box,
30879
+ {
30880
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
30881
+ children
30882
+ },
30883
+ shakeItem?.id ?? "steady"
30884
+ ),
30885
+ overlay
30886
+ ] });
30887
+ }
30888
+ return overlay;
30889
+ };
30890
+ FxOverlay.displayName = "FxOverlay";
30891
+ }
30892
+ });
30470
30893
  function computePopoverStyle(position, triggerRect, popoverWidth) {
30471
30894
  if (position === "left" || position === "right") {
30472
30895
  return {
@@ -32472,7 +32895,7 @@ var init_MathCanvas = __esm({
32472
32895
  x: mapX((first.x + last.x) / 2),
32473
32896
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
32474
32897
  text: region.label,
32475
- color: "#111827",
32898
+ color,
32476
32899
  fontSize: 11
32477
32900
  });
32478
32901
  }
@@ -32505,14 +32928,14 @@ var init_MathCanvas = __esm({
32505
32928
  const px = mapX(guide.at);
32506
32929
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
32507
32930
  if (guide.label) {
32508
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
32931
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
32509
32932
  }
32510
32933
  } else {
32511
32934
  if (guide.at < yMin || guide.at > yMax) continue;
32512
32935
  const py = mapY(guide.at);
32513
32936
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
32514
32937
  if (guide.label) {
32515
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
32938
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
32516
32939
  }
32517
32940
  }
32518
32941
  }
@@ -32578,7 +33001,7 @@ var init_MathCanvas = __esm({
32578
33001
  x: (x1 + x2) / 2,
32579
33002
  y: xAxisY - peak - 8,
32580
33003
  text: hop.label,
32581
- color: "#111827",
33004
+ color,
32582
33005
  fontSize: 10,
32583
33006
  align: "center"
32584
33007
  });
@@ -32605,7 +33028,7 @@ var init_MathCanvas = __esm({
32605
33028
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
32606
33029
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
32607
33030
  text: angle.label,
32608
- color: "#111827",
33031
+ color,
32609
33032
  fontSize: 11,
32610
33033
  align: "center"
32611
33034
  });
@@ -32623,7 +33046,7 @@ var init_MathCanvas = __esm({
32623
33046
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
32624
33047
  });
32625
33048
  if (p.label) {
32626
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
33049
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
32627
33050
  }
32628
33051
  }
32629
33052
  for (const v of vectors) {
@@ -32634,7 +33057,7 @@ var init_MathCanvas = __esm({
32634
33057
  const y2 = mapY(v.y + v.vy);
32635
33058
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
32636
33059
  if (v.label) {
32637
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
33060
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
32638
33061
  }
32639
33062
  }
32640
33063
  out.push(...shapes);
@@ -43776,6 +44199,7 @@ var init_DetailPanel = __esm({
43776
44199
  init_Box();
43777
44200
  init_Stack();
43778
44201
  init_SimpleGrid();
44202
+ init_Menu();
43779
44203
  init_LoadingState();
43780
44204
  init_ErrorState();
43781
44205
  init_EmptyState();
@@ -43793,6 +44217,7 @@ var init_DetailPanel = __esm({
43793
44217
  avatar,
43794
44218
  sections: propSections,
43795
44219
  actions,
44220
+ maxInlineActions,
43796
44221
  backAction,
43797
44222
  footer,
43798
44223
  slideOver = false,
@@ -44027,7 +44452,7 @@ var init_DetailPanel = __esm({
44027
44452
  }
44028
44453
  ) }),
44029
44454
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
44030
- otherActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
44455
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
44031
44456
  Button,
44032
44457
  {
44033
44458
  variant: action.variant || "secondary",
@@ -44042,6 +44467,22 @@ var init_DetailPanel = __esm({
44042
44467
  },
44043
44468
  idx
44044
44469
  )),
44470
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
44471
+ Menu,
44472
+ {
44473
+ position: "bottom-end",
44474
+ 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" }) }),
44475
+ items: otherActions.slice(maxInlineActions).map((action) => ({
44476
+ // ONE firing path: onClick → handleActionClick (emits with
44477
+ // the {id, row} payload). Passing `event` too would make
44478
+ // Menu emit a second, payload-less copy of the same event.
44479
+ label: action.label,
44480
+ icon: action.icon,
44481
+ variant: action.variant === "danger" ? "danger" : "default",
44482
+ onClick: () => handleActionClick(action, normalizedData)
44483
+ }))
44484
+ }
44485
+ ),
44045
44486
  /* @__PURE__ */ jsxRuntime.jsx(
44046
44487
  Button,
44047
44488
  {
@@ -49917,6 +50358,7 @@ var init_component_registry_generated = __esm({
49917
50358
  init_DocSidebar();
49918
50359
  init_DocTOC();
49919
50360
  init_DocumentViewer();
50361
+ init_DrawFxLayer();
49920
50362
  init_DrawGroup();
49921
50363
  init_DrawMesh();
49922
50364
  init_DrawShape();
@@ -49947,6 +50389,7 @@ var init_component_registry_generated = __esm({
49947
50389
  init_FormField();
49948
50390
  init_FormSection();
49949
50391
  init_FormSectionHeader();
50392
+ init_FxOverlay();
49950
50393
  init_GameAudioToggle();
49951
50394
  init_GameHud();
49952
50395
  init_GameIcon();
@@ -50187,6 +50630,7 @@ var init_component_registry_generated = __esm({
50187
50630
  "DocSidebar": DocSidebar,
50188
50631
  "DocTOC": DocTOC,
50189
50632
  "DocumentViewer": DocumentViewer,
50633
+ "DrawFxLayer": DrawFxLayer,
50190
50634
  "DrawGroup": DrawGroup,
50191
50635
  "DrawMesh": DrawMesh,
50192
50636
  "DrawShape": DrawShape,
@@ -50217,6 +50661,7 @@ var init_component_registry_generated = __esm({
50217
50661
  "FormField": FormField,
50218
50662
  "FormLayout": FormLayout,
50219
50663
  "FormSectionHeader": FormSectionHeader,
50664
+ "FxOverlay": FxOverlay,
50220
50665
  "GameAudioToggle": GameAudioToggle,
50221
50666
  "GameHud": GameHud,
50222
50667
  "GameIcon": GameIcon,
@@ -55028,7 +55473,7 @@ function runTickFrame(entityId, orderedWriters, store) {
55028
55473
  }
55029
55474
  var log8 = logger.createLogger("almadar:ui:effects:client-handlers");
55030
55475
  function createClientEffectHandlers(options) {
55031
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
55476
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
55032
55477
  return {
55033
55478
  emit: (event, payload, source) => {
55034
55479
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -55078,6 +55523,9 @@ function createClientEffectHandlers(options) {
55078
55523
  }
55079
55524
  log8.warn("No navigate handler, ignoring", { path });
55080
55525
  }),
55526
+ navigateBack: navigateBack ?? (() => {
55527
+ log8.warn("No navigate-back handler, ignoring");
55528
+ }),
55081
55529
  notify: notify ?? ((msg, type) => {
55082
55530
  log8.debug("notify", { type, message: msg });
55083
55531
  })
@@ -55547,6 +55995,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55547
55995
  }
55548
55996
  },
55549
55997
  navigate: optionsRef.current?.navigate,
55998
+ navigateBack: optionsRef.current?.navigateBack,
55550
55999
  notify: optionsRef.current?.notify,
55551
56000
  callService: optionsRef.current?.callService,
55552
56001
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -55608,6 +56057,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55608
56057
  emit: clientHandlers.emit,
55609
56058
  renderUI: clientHandlers.renderUI,
55610
56059
  navigate: clientHandlers.navigate,
56060
+ navigateBack: clientHandlers.navigateBack,
55611
56061
  notify: clientHandlers.notify
55612
56062
  };
55613
56063
  }
@@ -56227,7 +56677,7 @@ function normalizeChild(child) {
56227
56677
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
56228
56678
  };
56229
56679
  }
56230
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
56680
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
56231
56681
  for (const eff of effects) {
56232
56682
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
56233
56683
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -56273,7 +56723,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
56273
56723
  });
56274
56724
  }
56275
56725
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
56276
- onNavigate(eff.route, eff.params);
56726
+ onNavigate(eff.route, eff.params, eff.crumb);
56727
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
56728
+ onNavigateBack();
56277
56729
  }
56278
56730
  }
56279
56731
  }
@@ -56296,7 +56748,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
56296
56748
  }
56297
56749
  return active;
56298
56750
  }
56299
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56751
+ function NavStackRefBridge({ apiRef }) {
56752
+ const api = providers.useNavStack();
56753
+ React94.useEffect(() => {
56754
+ apiRef.current = api;
56755
+ return () => {
56756
+ apiRef.current = null;
56757
+ };
56758
+ }, [api, apiRef]);
56759
+ return null;
56760
+ }
56761
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onNavigateBack, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56300
56762
  const bridge = providers.useServerBridge();
56301
56763
  const activeTraitNames = React94.useMemo(
56302
56764
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -56322,10 +56784,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56322
56784
  for (const name of targets) {
56323
56785
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56324
56786
  recordServerResponse(name, event, meta);
56325
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56787
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56326
56788
  }
56327
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
56328
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56789
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
56790
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56329
56791
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
56330
56792
  const initSentRef = React94.useRef(false);
56331
56793
  const prevTraitsRef = React94.useRef(void 0);
@@ -56384,13 +56846,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56384
56846
  effects: effectTraces,
56385
56847
  timestamp: Date.now()
56386
56848
  });
56387
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56849
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56388
56850
  }
56389
56851
  })();
56390
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56852
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56391
56853
  return /* @__PURE__ */ jsxRuntime.jsx(providers.EntityBindingContext.Provider, { value: entityBindingSource, children });
56392
56854
  }
56393
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
56855
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
56394
56856
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
56395
56857
  const allPageTraits = React94.useMemo(() => {
56396
56858
  let base;
@@ -56538,6 +57000,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
56538
57000
  embeddedTraits,
56539
57001
  serverActiveTraits,
56540
57002
  onNavigate,
57003
+ onNavigateBack,
56541
57004
  onLocalFallback,
56542
57005
  persistence,
56543
57006
  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 }) }) })
@@ -56630,6 +57093,15 @@ function OrbPreview({
56630
57093
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
56631
57094
  return resolved ?? initialPagePath;
56632
57095
  }, [pages, currentPage, initialPagePath]);
57096
+ const navPages = React94.useMemo(
57097
+ () => 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 })),
57098
+ [pages]
57099
+ );
57100
+ const concreteCurrentPath = React94.useMemo(() => {
57101
+ const pattern = currentPagePath ?? "/";
57102
+ return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
57103
+ }, [currentPagePath, routeParams]);
57104
+ const navStackRef = React94.useRef(null);
56633
57105
  const handleNavigate = React94.useCallback((path) => {
56634
57106
  const hit = providers.matchPathAmong(pages, path, (entry) => entry.page.path);
56635
57107
  const match = hit?.candidate;
@@ -56651,6 +57123,16 @@ function OrbPreview({
56651
57123
  }
56652
57124
  }
56653
57125
  }, [pages]);
57126
+ const handleNavigateEffect = React94.useCallback(
57127
+ (path, _params, crumb) => {
57128
+ navStackRef.current?.beginNavigate(path, crumb);
57129
+ handleNavigate(path);
57130
+ },
57131
+ [handleNavigate]
57132
+ );
57133
+ const handleNavigateBack = React94.useCallback(() => {
57134
+ navStackRef.current?.back();
57135
+ }, []);
56654
57136
  React94.useEffect(() => {
56655
57137
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
56656
57138
  const url = event.payload?.url;
@@ -56703,20 +57185,33 @@ function OrbPreview({
56703
57185
  style: { height },
56704
57186
  children: [
56705
57187
  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." }) }),
56706
- /* @__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(
56707
- SchemaRunner,
56708
- {
56709
- schema: parseResult.schema,
56710
- serverUrl,
56711
- transport,
56712
- mockData: effectiveMockData,
56713
- pageName: currentPage,
56714
- routeParams,
56715
- onNavigate: handleNavigate,
56716
- onLocalFallback: handleLocalFallback,
56717
- persistence
56718
- }
56719
- ) }) }) })
57188
+ /* @__PURE__ */ jsxRuntime.jsx(providers.CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxRuntime.jsxs(
57189
+ providers.NavStackProvider,
57190
+ {
57191
+ pages: navPages,
57192
+ currentPath: concreteCurrentPath,
57193
+ navigate: handleNavigate,
57194
+ storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
57195
+ children: [
57196
+ /* @__PURE__ */ jsxRuntime.jsx(NavStackRefBridge, { apiRef: navStackRef }),
57197
+ /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
57198
+ SchemaRunner,
57199
+ {
57200
+ schema: parseResult.schema,
57201
+ serverUrl,
57202
+ transport,
57203
+ mockData: effectiveMockData,
57204
+ pageName: currentPage,
57205
+ routeParams,
57206
+ onNavigate: handleNavigateEffect,
57207
+ onNavigateBack: handleNavigateBack,
57208
+ onLocalFallback: handleLocalFallback,
57209
+ persistence
57210
+ }
57211
+ ) }) })
57212
+ ]
57213
+ }
57214
+ ) })
56720
57215
  ]
56721
57216
  }
56722
57217
  );