@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
@@ -1,6 +1,6 @@
1
1
  import * as React87 from 'react';
2
2
  import React87__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId } from 'react';
3
- import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
3
+ import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider, useNavStack, useCurrentPagePath, useGameAudioContextOptional, matchPathAmong } from '@almadar/ui/providers';
4
4
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
5
5
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
6
6
  import { ANONYMOUS_USER, isRenderBindingMarker, isFileValue, isInlineTrait, ANIMATION_NAMES } from '@almadar/core';
@@ -16,7 +16,7 @@ import { useUISlots, ThemeProvider, useTheme } from '@almadar/ui/context';
16
16
  export { DesignThemeProvider, useDesignTheme } from '@almadar/ui/context';
17
17
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
18
18
  import { wrapCallbackForEvent } from '@almadar/runtime/ui';
19
- import { Link, Outlet, useLocation } from 'react-router-dom';
19
+ import { useLocation, useNavigate, Link, Outlet } from 'react-router-dom';
20
20
  import ELK from 'elkjs/lib/elk.bundled.js';
21
21
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
22
22
  import dark from 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus.js';
@@ -5729,27 +5729,61 @@ var init_InfiniteScrollSentinel = __esm({
5729
5729
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
5730
5730
  }
5731
5731
  });
5732
- function createParticles(count) {
5733
- return Array.from({ length: count }, () => {
5734
- particleIdCounter += 1;
5735
- return {
5736
- id: particleIdCounter,
5737
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
5738
- left: 30 + Math.random() * 40,
5739
- delay: Math.random() * 300,
5740
- angle: Math.random() * 360,
5741
- distance: 40 + Math.random() * 80,
5742
- rotation: Math.random() * 720 - 360,
5743
- size: 4 + Math.random() * 6
5744
- };
5745
- });
5732
+
5733
+ // components/core/atoms/fx.ts
5734
+ function resolveFxView(item, presets) {
5735
+ const row = presets?.find((p) => p.type === item.type);
5736
+ if (!row) return item;
5737
+ return {
5738
+ ...item,
5739
+ space: item.space ?? row.space,
5740
+ effect: item.effect ?? row.effect,
5741
+ color: item.color ?? row.color,
5742
+ size: item.size ?? row.size,
5743
+ vx: item.vx ?? row.vx,
5744
+ vy: item.vy ?? row.vy,
5745
+ vz: item.vz ?? row.vz,
5746
+ particleCount: item.particleCount ?? row.particleCount,
5747
+ shape: row.shape,
5748
+ count: row.count,
5749
+ glow: row.glow,
5750
+ gravity: row.gravity,
5751
+ color2: row.color2
5752
+ };
5746
5753
  }
5747
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
5748
- var init_ConfettiEffect = __esm({
5749
- "components/core/atoms/ConfettiEffect.tsx"() {
5750
- "use client";
5751
- init_cn();
5752
- init_Box();
5754
+ function fxLifecycle(item, epochNowMs, tickMs) {
5755
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
5756
+ const lifeMs = maxTtl * tickMs;
5757
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
5758
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
5759
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
5760
+ }
5761
+ function fxHash01(seed, salt) {
5762
+ let h = 2166136261 ^ salt;
5763
+ for (let i = 0; i < seed.length; i++) {
5764
+ h ^= seed.charCodeAt(i);
5765
+ h = Math.imul(h, 16777619);
5766
+ }
5767
+ h ^= h >>> 13;
5768
+ h = Math.imul(h, 1274126177);
5769
+ h ^= h >>> 16;
5770
+ return (h >>> 0) / 4294967296;
5771
+ }
5772
+ function createConfettiParticles(count, seed) {
5773
+ return Array.from({ length: count }, (_, i) => ({
5774
+ id: i,
5775
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
5776
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
5777
+ delay: fxHash01(seed, i * 7 + 3) * 300,
5778
+ angle: fxHash01(seed, i * 7 + 4) * 360,
5779
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
5780
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
5781
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
5782
+ }));
5783
+ }
5784
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
5785
+ var init_fx = __esm({
5786
+ "components/core/atoms/fx.ts"() {
5753
5787
  CONFETTI_COLORS = [
5754
5788
  "var(--color-primary)",
5755
5789
  "var(--color-success)",
@@ -5758,7 +5792,30 @@ var init_ConfettiEffect = __esm({
5758
5792
  "gold",
5759
5793
  "dodgerblue"
5760
5794
  ];
5761
- particleIdCounter = 0;
5795
+ CONFETTI_BURST_KEYFRAMES = `
5796
+ @keyframes confetti-burst {
5797
+ 0% {
5798
+ opacity: 1;
5799
+ transform: translate(0, 0) rotate(0deg) scale(1);
5800
+ }
5801
+ 70% {
5802
+ opacity: 1;
5803
+ }
5804
+ 100% {
5805
+ opacity: 0;
5806
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
5807
+ }
5808
+ }
5809
+ `;
5810
+ }
5811
+ });
5812
+ var ConfettiEffect;
5813
+ var init_ConfettiEffect = __esm({
5814
+ "components/core/atoms/ConfettiEffect.tsx"() {
5815
+ "use client";
5816
+ init_cn();
5817
+ init_Box();
5818
+ init_fx();
5762
5819
  ConfettiEffect = ({
5763
5820
  trigger,
5764
5821
  duration = 2e3,
@@ -5767,11 +5824,13 @@ var init_ConfettiEffect = __esm({
5767
5824
  }) => {
5768
5825
  const [particles, setParticles] = useState([]);
5769
5826
  const previousTriggerRef = useRef(false);
5827
+ const burstRef = useRef(0);
5770
5828
  useEffect(() => {
5771
5829
  const wasFalse = !previousTriggerRef.current;
5772
5830
  previousTriggerRef.current = trigger;
5773
5831
  if (trigger && wasFalse) {
5774
- const newParticles = createParticles(particleCount);
5832
+ burstRef.current += 1;
5833
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
5775
5834
  setParticles(newParticles);
5776
5835
  const timer = window.setTimeout(() => {
5777
5836
  setParticles([]);
@@ -5819,21 +5878,7 @@ var init_ConfettiEffect = __esm({
5819
5878
  p.id
5820
5879
  );
5821
5880
  }),
5822
- /* @__PURE__ */ jsx("style", { children: `
5823
- @keyframes confetti-burst {
5824
- 0% {
5825
- opacity: 1;
5826
- transform: translate(0, 0) rotate(0deg) scale(1);
5827
- }
5828
- 70% {
5829
- opacity: 1;
5830
- }
5831
- 100% {
5832
- opacity: 0;
5833
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
5834
- }
5835
- }
5836
- ` })
5881
+ /* @__PURE__ */ jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
5837
5882
  ]
5838
5883
  }
5839
5884
  );
@@ -9390,7 +9435,7 @@ var init_DrawShape = __esm({
9390
9435
  const cx = p.x + (node.offsetX ?? 0) * tw;
9391
9436
  const cy = p.y + (node.offsetY ?? 0) * tw;
9392
9437
  const rx = (node.radiusX ?? 0) * tw;
9393
- const ry = (node.radiusY ?? rx) * tw;
9438
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9394
9439
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9395
9440
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9396
9441
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9580,6 +9625,172 @@ var init_DrawTextLayer = __esm({
9580
9625
  };
9581
9626
  }
9582
9627
  });
9628
+
9629
+ // components/game/molecules/DrawFxLayer.tsx
9630
+ function fxPosition(view, dim, ageSec) {
9631
+ const g = view.gravity ?? 0;
9632
+ const fall = 0.5 * g * ageSec * ageSec;
9633
+ {
9634
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
9635
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
9636
+ return {
9637
+ x: base2.x + (view.vx ?? 0) * ageSec,
9638
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
9639
+ };
9640
+ }
9641
+ }
9642
+ function sparkShape(view, pos, i, fade, progress) {
9643
+ const size = view.size ?? 0.5;
9644
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9645
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
9646
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
9647
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9648
+ return {
9649
+ type: "draw-shape",
9650
+ shape: "ellipse",
9651
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
9652
+ anchor: "center",
9653
+ radiusX: r,
9654
+ fill: color,
9655
+ blendMode: "lighter",
9656
+ opacity: fade,
9657
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9658
+ };
9659
+ }
9660
+ function proceduralShapes(view, pos, fade, progress) {
9661
+ const size = view.size ?? 0.5;
9662
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9663
+ switch (view.shape ?? "spark") {
9664
+ case "ring": {
9665
+ return [
9666
+ {
9667
+ type: "draw-shape",
9668
+ shape: "ellipse",
9669
+ position: pos,
9670
+ anchor: "center",
9671
+ radiusX: size * easeOut2(progress),
9672
+ stroke: view.color2 ?? color,
9673
+ strokeWidth: 2,
9674
+ blendMode: "lighter",
9675
+ opacity: fade,
9676
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9677
+ }
9678
+ ];
9679
+ }
9680
+ case "puff": {
9681
+ const count = view.count ?? 3;
9682
+ return Array.from({ length: count }, (_, i) => {
9683
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
9684
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
9685
+ return {
9686
+ type: "draw-shape",
9687
+ shape: "ellipse",
9688
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
9689
+ anchor: "center",
9690
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
9691
+ fill: i === 0 && view.color2 ? view.color2 : color,
9692
+ opacity: fade * 0.6,
9693
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9694
+ };
9695
+ });
9696
+ }
9697
+ case "streak": {
9698
+ const count = view.count ?? 5;
9699
+ return Array.from({ length: count }, (_, i) => {
9700
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9701
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
9702
+ const len = size * 0.35;
9703
+ return {
9704
+ type: "draw-shape",
9705
+ shape: "ellipse",
9706
+ position: pos,
9707
+ anchor: "center",
9708
+ offsetX: inner + len / 2,
9709
+ offsetY: 0,
9710
+ radiusX: len / 2,
9711
+ radiusY: size * 0.04,
9712
+ rotate: angle,
9713
+ fill: color,
9714
+ blendMode: "lighter",
9715
+ opacity: fade,
9716
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9717
+ };
9718
+ });
9719
+ }
9720
+ default: {
9721
+ const count = view.count ?? 6;
9722
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
9723
+ }
9724
+ }
9725
+ }
9726
+ function expandFxItem(view, node, epochNowMs, dim) {
9727
+ if (view.space === "screen") return [];
9728
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
9729
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
9730
+ if (progress >= 1) return [];
9731
+ const pos = fxPosition(view, dim, ageMs / 1e3);
9732
+ if (!pos) return [];
9733
+ const out = [];
9734
+ const artItems = node.art?.[view.type]?.["idle"];
9735
+ const sprite = node.sprites?.[view.type];
9736
+ if (Array.isArray(artItems)) {
9737
+ out.push({
9738
+ type: "draw-group",
9739
+ position: pos,
9740
+ opacity: fade,
9741
+ ...view.size !== void 0 ? { scale: view.size } : {},
9742
+ items: artItems
9743
+ });
9744
+ } else if (sprite?.url) {
9745
+ const spriteSize = view.size ?? 0.6;
9746
+ out.push({
9747
+ type: "draw-sprite",
9748
+ position: pos,
9749
+ asset: sprite,
9750
+ anchor: "center",
9751
+ width: spriteSize,
9752
+ height: spriteSize,
9753
+ opacity: fade
9754
+ });
9755
+ } else {
9756
+ out.push(...proceduralShapes(view, pos, fade, progress));
9757
+ }
9758
+ if (view.message) {
9759
+ const text = {
9760
+ type: "draw-text",
9761
+ text: view.message,
9762
+ position: pos,
9763
+ offsetY: -(0.15 + 0.55 * progress),
9764
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
9765
+ opacity: fade
9766
+ };
9767
+ out.push(text);
9768
+ }
9769
+ return out;
9770
+ }
9771
+ function expandFxLayer(node, epochNowMs, dim) {
9772
+ if (!Array.isArray(node.items)) return [];
9773
+ const out = [];
9774
+ for (const item of node.items) {
9775
+ if (!item || typeof item.id !== "string") continue;
9776
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
9777
+ }
9778
+ return out;
9779
+ }
9780
+ function DrawFxLayer(_props) {
9781
+ return null;
9782
+ }
9783
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
9784
+ var init_DrawFxLayer = __esm({
9785
+ "components/game/molecules/DrawFxLayer.tsx"() {
9786
+ "use client";
9787
+ init_fx();
9788
+ DEFAULT_TICK_MS = 500;
9789
+ DEFAULT_TEXT_COLOR = "#ffe066";
9790
+ DEFAULT_SPARK_COLOR = "#ffffff";
9791
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
9792
+ }
9793
+ });
9583
9794
  function paintDrawable(painter, node, dctx) {
9584
9795
  switch (node.type) {
9585
9796
  case "draw-sprite":
@@ -9627,6 +9838,11 @@ function paintDrawable(painter, node, dctx) {
9627
9838
  case "draw-text-layer":
9628
9839
  paintTextLayer(painter, node, dctx);
9629
9840
  break;
9841
+ case "draw-fx-layer": {
9842
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
9843
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
9844
+ break;
9845
+ }
9630
9846
  }
9631
9847
  }
9632
9848
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -9641,6 +9857,7 @@ var init_paintDispatch = __esm({
9641
9857
  init_DrawSpriteLayer();
9642
9858
  init_DrawShapeLayer();
9643
9859
  init_DrawTextLayer();
9860
+ init_DrawFxLayer();
9644
9861
  paint2dLog = createLogger("almadar:ui:drawable-2d");
9645
9862
  warnedUnsupported2d = /* @__PURE__ */ new Set();
9646
9863
  warnUnsupported2d = (kind) => {
@@ -9889,6 +10106,7 @@ function Canvas2D({
9889
10106
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
9890
10107
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
9891
10108
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10109
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
9892
10110
  return false;
9893
10111
  };
9894
10112
  const animRafRef = useRef(0);
@@ -21108,17 +21326,27 @@ var init_Breadcrumb = __esm({
21108
21326
  init_useEventBus();
21109
21327
  Breadcrumb = ({
21110
21328
  items,
21329
+ fromNavStack = false,
21330
+ itemEvent,
21111
21331
  separator = "chevron-right",
21112
21332
  maxItems,
21113
21333
  className
21114
21334
  }) => {
21115
21335
  const eventBus = useEventBus();
21116
21336
  const { t } = useTranslate();
21117
- const displayItems = maxItems && items.length > maxItems ? [
21118
- ...items.slice(0, 1),
21337
+ const navStack = useNavStack();
21338
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
21339
+ label: entry.label,
21340
+ path: entry.href,
21341
+ isCurrent: i === navStack.entries.length - 1,
21342
+ event: itemEvent
21343
+ })) : items ?? [];
21344
+ if (fromNavStack && sourceItems.length === 0) return null;
21345
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
21346
+ ...sourceItems.slice(0, 1),
21119
21347
  { label: "...", isCurrent: false },
21120
- ...items.slice(-maxItems + 1)
21121
- ] : items;
21348
+ ...sourceItems.slice(-maxItems + 1)
21349
+ ] : sourceItems;
21122
21350
  return /* @__PURE__ */ jsx(
21123
21351
  "nav",
21124
21352
  {
@@ -21128,7 +21356,7 @@ var init_Breadcrumb = __esm({
21128
21356
  const isLast = index === displayItems.length - 1;
21129
21357
  const isEllipsis = item.label === "...";
21130
21358
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
21131
- isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxs(
21359
+ isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxs(
21132
21360
  "a",
21133
21361
  {
21134
21362
  href: item.href || item.path,
@@ -21154,7 +21382,12 @@ var init_Breadcrumb = __esm({
21154
21382
  {
21155
21383
  type: "button",
21156
21384
  onClick: () => {
21157
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
21385
+ const href = item.path ?? item.href;
21386
+ if (item.event) {
21387
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
21388
+ } else if (fromNavStack && href) {
21389
+ navStack.goTo(href);
21390
+ }
21158
21391
  item.onClick?.();
21159
21392
  },
21160
21393
  className: cn(
@@ -28128,6 +28361,196 @@ var init_PageTransition = __esm({
28128
28361
  PageTransition.displayName = "PageTransition";
28129
28362
  }
28130
28363
  });
28364
+ function ConfettiBurst({ item, lifeMs }) {
28365
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
28366
+ const left = (item.x ?? 0.5) * 100;
28367
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28368
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
28369
+ const rad = p.angle * Math.PI / 180;
28370
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
28371
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
28372
+ return /* @__PURE__ */ jsx(
28373
+ Box,
28374
+ {
28375
+ className: "absolute rounded-sm",
28376
+ style: {
28377
+ left: (p.left - 50) * 2,
28378
+ top: -10,
28379
+ width: p.size,
28380
+ height: p.size,
28381
+ backgroundColor: item.color ?? p.color,
28382
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
28383
+ opacity: 0,
28384
+ "--confetti-tx": `${tx}px`,
28385
+ "--confetti-ty": `${ty}px`,
28386
+ "--confetti-rotate": `${p.rotation}deg`
28387
+ }
28388
+ },
28389
+ p.id
28390
+ );
28391
+ }) });
28392
+ }
28393
+ function SparkleBurst({ item, lifeMs }) {
28394
+ const count = item.particleCount ?? 8;
28395
+ const scale = item.size ?? 1;
28396
+ const left = (item.x ?? 0.5) * 100;
28397
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28398
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
28399
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
28400
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
28401
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
28402
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
28403
+ return /* @__PURE__ */ jsx(
28404
+ Box,
28405
+ {
28406
+ className: "absolute rounded-full",
28407
+ style: {
28408
+ left: dx,
28409
+ top: dy,
28410
+ width: dot,
28411
+ height: dot,
28412
+ backgroundColor: item.color ?? "gold",
28413
+ opacity: 0,
28414
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
28415
+ }
28416
+ },
28417
+ i
28418
+ );
28419
+ }) });
28420
+ }
28421
+ function OverlayFxNode({ item, tickMs }) {
28422
+ const lifeMs = lifeMsOf(item, tickMs);
28423
+ switch (item.effect ?? "sparkle") {
28424
+ case "confetti":
28425
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
28426
+ case "flash":
28427
+ return /* @__PURE__ */ jsx(
28428
+ Box,
28429
+ {
28430
+ position: "absolute",
28431
+ className: "inset-0",
28432
+ style: {
28433
+ backgroundColor: item.color ?? "#ffffff",
28434
+ opacity: 0,
28435
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
28436
+ }
28437
+ }
28438
+ );
28439
+ case "streak-glow":
28440
+ return /* @__PURE__ */ jsx(
28441
+ Box,
28442
+ {
28443
+ position: "absolute",
28444
+ className: "inset-0",
28445
+ style: {
28446
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
28447
+ opacity: 0,
28448
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
28449
+ }
28450
+ }
28451
+ );
28452
+ case "float-text": {
28453
+ if (!item.message) return null;
28454
+ return /* @__PURE__ */ jsx(
28455
+ Box,
28456
+ {
28457
+ position: "absolute",
28458
+ style: {
28459
+ left: `${(item.x ?? 0.5) * 100}%`,
28460
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
28461
+ color: item.color ?? "var(--color-success)",
28462
+ fontWeight: 700,
28463
+ fontSize: 16 * (item.size ?? 1),
28464
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
28465
+ opacity: 0,
28466
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
28467
+ whiteSpace: "nowrap"
28468
+ },
28469
+ children: item.message
28470
+ }
28471
+ );
28472
+ }
28473
+ case "shake":
28474
+ return null;
28475
+ default:
28476
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
28477
+ }
28478
+ }
28479
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
28480
+ var init_FxOverlay = __esm({
28481
+ "components/core/molecules/FxOverlay.tsx"() {
28482
+ "use client";
28483
+ init_cn();
28484
+ init_Box();
28485
+ init_fx();
28486
+ DEFAULT_TICK_MS2 = 500;
28487
+ FX_OVERLAY_KEYFRAMES = `
28488
+ ${CONFETTI_BURST_KEYFRAMES}
28489
+ @keyframes fx-overlay-flash {
28490
+ 0% { opacity: 0.75; }
28491
+ 100% { opacity: 0; }
28492
+ }
28493
+ @keyframes fx-overlay-glow {
28494
+ 0% { opacity: 0.9; }
28495
+ 60% { opacity: 0.6; }
28496
+ 100% { opacity: 0; }
28497
+ }
28498
+ @keyframes fx-overlay-sparkle {
28499
+ 0% { opacity: 0; transform: scale(0); }
28500
+ 30% { opacity: 1; transform: scale(1); }
28501
+ 100% { opacity: 0; transform: scale(0.3); }
28502
+ }
28503
+ @keyframes fx-overlay-float {
28504
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
28505
+ 15% { opacity: 1; }
28506
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
28507
+ }
28508
+ @keyframes fx-overlay-shake {
28509
+ 0% { transform: translateX(0); }
28510
+ 15% { transform: translateX(-6px); }
28511
+ 30% { transform: translateX(5px); }
28512
+ 45% { transform: translateX(-4px); }
28513
+ 60% { transform: translateX(3px); }
28514
+ 75% { transform: translateX(-2px); }
28515
+ 100% { transform: translateX(0); }
28516
+ }
28517
+ `;
28518
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28519
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28520
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28521
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28522
+ );
28523
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28524
+ const overlay = /* @__PURE__ */ jsxs(
28525
+ Box,
28526
+ {
28527
+ position: "absolute",
28528
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28529
+ "aria-hidden": "true",
28530
+ children: [
28531
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28532
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28533
+ ]
28534
+ }
28535
+ );
28536
+ if (children !== void 0 && children !== null) {
28537
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
28538
+ /* @__PURE__ */ jsx(
28539
+ Box,
28540
+ {
28541
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28542
+ children
28543
+ },
28544
+ shakeItem?.id ?? "steady"
28545
+ ),
28546
+ overlay
28547
+ ] });
28548
+ }
28549
+ return overlay;
28550
+ };
28551
+ FxOverlay.displayName = "FxOverlay";
28552
+ }
28553
+ });
28131
28554
  function computePopoverStyle(position, triggerRect, popoverWidth) {
28132
28555
  if (position === "left" || position === "right") {
28133
28556
  return {
@@ -30133,7 +30556,7 @@ var init_MathCanvas = __esm({
30133
30556
  x: mapX((first.x + last.x) / 2),
30134
30557
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
30135
30558
  text: region.label,
30136
- color: "#111827",
30559
+ color,
30137
30560
  fontSize: 11
30138
30561
  });
30139
30562
  }
@@ -30166,14 +30589,14 @@ var init_MathCanvas = __esm({
30166
30589
  const px = mapX(guide.at);
30167
30590
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
30168
30591
  if (guide.label) {
30169
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30592
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
30170
30593
  }
30171
30594
  } else {
30172
30595
  if (guide.at < yMin || guide.at > yMax) continue;
30173
30596
  const py = mapY(guide.at);
30174
30597
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
30175
30598
  if (guide.label) {
30176
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30599
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
30177
30600
  }
30178
30601
  }
30179
30602
  }
@@ -30239,7 +30662,7 @@ var init_MathCanvas = __esm({
30239
30662
  x: (x1 + x2) / 2,
30240
30663
  y: xAxisY - peak - 8,
30241
30664
  text: hop.label,
30242
- color: "#111827",
30665
+ color,
30243
30666
  fontSize: 10,
30244
30667
  align: "center"
30245
30668
  });
@@ -30266,7 +30689,7 @@ var init_MathCanvas = __esm({
30266
30689
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
30267
30690
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
30268
30691
  text: angle.label,
30269
- color: "#111827",
30692
+ color,
30270
30693
  fontSize: 11,
30271
30694
  align: "center"
30272
30695
  });
@@ -30284,7 +30707,7 @@ var init_MathCanvas = __esm({
30284
30707
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
30285
30708
  });
30286
30709
  if (p.label) {
30287
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30710
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
30288
30711
  }
30289
30712
  }
30290
30713
  for (const v of vectors) {
@@ -30295,7 +30718,7 @@ var init_MathCanvas = __esm({
30295
30718
  const y2 = mapY(v.y + v.vy);
30296
30719
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
30297
30720
  if (v.label) {
30298
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30721
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
30299
30722
  }
30300
30723
  }
30301
30724
  out.push(...shapes);
@@ -31559,13 +31982,13 @@ var init_MapView = __esm({
31559
31982
  shadowSize: [41, 41]
31560
31983
  });
31561
31984
  L.Marker.prototype.options.icon = defaultIcon;
31562
- const { useEffect: useEffect64, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__default;
31985
+ const { useEffect: useEffect65, useRef: useRef64, useCallback: useCallback97, useState: useState95 } = React87__default;
31563
31986
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31564
31987
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31565
31988
  function MapUpdater({ centerLat, centerLng, zoom }) {
31566
31989
  const map = useMap();
31567
- const prevRef = useRef63({ centerLat, centerLng, zoom });
31568
- useEffect64(() => {
31990
+ const prevRef = useRef64({ centerLat, centerLng, zoom });
31991
+ useEffect65(() => {
31569
31992
  const prev = prevRef.current;
31570
31993
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31571
31994
  map.setView([centerLat, centerLng], zoom);
@@ -31576,7 +31999,7 @@ var init_MapView = __esm({
31576
31999
  }
31577
32000
  function MapClickHandler({ onMapClick }) {
31578
32001
  const map = useMap();
31579
- useEffect64(() => {
32002
+ useEffect65(() => {
31580
32003
  if (!onMapClick) return;
31581
32004
  const handler = (e) => {
31582
32005
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31604,8 +32027,8 @@ var init_MapView = __esm({
31604
32027
  showAttribution = true
31605
32028
  }) {
31606
32029
  const eventBus = useEventBus2();
31607
- const [clickedPosition, setClickedPosition] = useState94(null);
31608
- const handleMapClick = useCallback96((lat, lng) => {
32030
+ const [clickedPosition, setClickedPosition] = useState95(null);
32031
+ const handleMapClick = useCallback97((lat, lng) => {
31609
32032
  if (showClickedPin) {
31610
32033
  setClickedPosition({ lat, lng });
31611
32034
  }
@@ -31614,7 +32037,7 @@ var init_MapView = __esm({
31614
32037
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31615
32038
  }
31616
32039
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31617
- const handleMarkerClick = useCallback96((marker) => {
32040
+ const handleMarkerClick = useCallback97((marker) => {
31618
32041
  onMarkerClick?.(marker);
31619
32042
  if (markerClickEvent) {
31620
32043
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -41846,6 +42269,7 @@ var init_DetailPanel = __esm({
41846
42269
  init_Box();
41847
42270
  init_Stack();
41848
42271
  init_SimpleGrid();
42272
+ init_Menu();
41849
42273
  init_LoadingState();
41850
42274
  init_ErrorState();
41851
42275
  init_EmptyState();
@@ -41863,6 +42287,7 @@ var init_DetailPanel = __esm({
41863
42287
  avatar,
41864
42288
  sections: propSections,
41865
42289
  actions,
42290
+ maxInlineActions,
41866
42291
  backAction,
41867
42292
  footer,
41868
42293
  slideOver = false,
@@ -42097,7 +42522,7 @@ var init_DetailPanel = __esm({
42097
42522
  }
42098
42523
  ) }),
42099
42524
  /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
42100
- otherActions.map((action, idx) => /* @__PURE__ */ jsx(
42525
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
42101
42526
  Button,
42102
42527
  {
42103
42528
  variant: action.variant || "secondary",
@@ -42112,6 +42537,22 @@ var init_DetailPanel = __esm({
42112
42537
  },
42113
42538
  idx
42114
42539
  )),
42540
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
42541
+ Menu,
42542
+ {
42543
+ position: "bottom-end",
42544
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
42545
+ items: otherActions.slice(maxInlineActions).map((action) => ({
42546
+ // ONE firing path: onClick → handleActionClick (emits with
42547
+ // the {id, row} payload). Passing `event` too would make
42548
+ // Menu emit a second, payload-less copy of the same event.
42549
+ label: action.label,
42550
+ icon: action.icon,
42551
+ variant: action.variant === "danger" ? "danger" : "default",
42552
+ onClick: () => handleActionClick(action, normalizedData)
42553
+ }))
42554
+ }
42555
+ ),
42115
42556
  /* @__PURE__ */ jsx(
42116
42557
  Button,
42117
42558
  {
@@ -47968,6 +48409,7 @@ var init_component_registry_generated = __esm({
47968
48409
  init_DocSidebar();
47969
48410
  init_DocTOC();
47970
48411
  init_DocumentViewer();
48412
+ init_DrawFxLayer();
47971
48413
  init_DrawGroup();
47972
48414
  init_DrawMesh();
47973
48415
  init_DrawShape();
@@ -47998,6 +48440,7 @@ var init_component_registry_generated = __esm({
47998
48440
  init_FormField();
47999
48441
  init_FormSection();
48000
48442
  init_FormSectionHeader();
48443
+ init_FxOverlay();
48001
48444
  init_GameAudioToggle();
48002
48445
  init_GameHud();
48003
48446
  init_GameIcon();
@@ -48238,6 +48681,7 @@ var init_component_registry_generated = __esm({
48238
48681
  "DocSidebar": DocSidebar,
48239
48682
  "DocTOC": DocTOC,
48240
48683
  "DocumentViewer": DocumentViewer,
48684
+ "DrawFxLayer": DrawFxLayer,
48241
48685
  "DrawGroup": DrawGroup,
48242
48686
  "DrawMesh": DrawMesh,
48243
48687
  "DrawShape": DrawShape,
@@ -48268,6 +48712,7 @@ var init_component_registry_generated = __esm({
48268
48712
  "FormField": FormField,
48269
48713
  "FormLayout": FormLayout,
48270
48714
  "FormSectionHeader": FormSectionHeader,
48715
+ "FxOverlay": FxOverlay,
48271
48716
  "GameAudioToggle": GameAudioToggle,
48272
48717
  "GameHud": GameHud,
48273
48718
  "GameIcon": GameIcon,
@@ -50732,12 +51177,18 @@ function ServerBridgeProvider({
50732
51177
  } else if (effectType === "navigate") {
50733
51178
  const route = effect[1];
50734
51179
  const rawParams = effect[2];
51180
+ const rawOptions = effect[3];
51181
+ const optionsObj = rawOptions !== null && rawOptions !== void 0 && typeof rawOptions === "object" && !Array.isArray(rawOptions) ? rawOptions : void 0;
51182
+ const crumb = typeof optionsObj?.crumb === "string" ? optionsObj.crumb : void 0;
50735
51183
  effects.push({
50736
51184
  type: "navigate",
50737
51185
  route,
50738
51186
  params: rawParams !== null && rawParams !== void 0 && typeof rawParams === "object" && !Array.isArray(rawParams) ? rawParams : void 0,
51187
+ crumb,
50739
51188
  traitName
50740
51189
  });
51190
+ } else if (effectType === "navigate-back") {
51191
+ effects.push({ type: "navigate-back", traitName });
50741
51192
  } else if (effectType === "notify") {
50742
51193
  const message = effect[1];
50743
51194
  effects.push({ type: "notify", message: typeof message === "string" ? message : void 0, traitName });
@@ -51002,5 +51453,169 @@ function GameAudioProvider2({
51002
51453
  return /* @__PURE__ */ jsx(GameAudioContext2.Provider, { value, children });
51003
51454
  }
51004
51455
  GameAudioProvider2.displayName = "GameAudioProvider";
51456
+ function derivePageLabel(name) {
51457
+ const stripped = name.endsWith("Page") && name.length > 4 ? name.slice(0, -4) : name;
51458
+ return stripped.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
51459
+ }
51460
+ function normalizePath(p) {
51461
+ let normalized = p.trim();
51462
+ if (!normalized.startsWith("/")) normalized = "/" + normalized;
51463
+ if (normalized.length > 1 && normalized.endsWith("/")) {
51464
+ normalized = normalized.slice(0, -1);
51465
+ }
51466
+ return normalized;
51467
+ }
51468
+ function matchNavPage(pages, path) {
51469
+ const hit = matchPathAmong(pages, path, (p) => p.path);
51470
+ return hit ? hit.candidate : null;
51471
+ }
51472
+ function seedAncestors(pages, path) {
51473
+ const segments = normalizePath(path).split("/").filter(Boolean);
51474
+ const ancestors = [];
51475
+ for (let depth = 1; depth < segments.length; depth++) {
51476
+ const prefix = "/" + segments.slice(0, depth).join("/");
51477
+ const page = matchNavPage(pages, prefix);
51478
+ if (page) {
51479
+ ancestors.push({ href: prefix, label: derivePageLabel(page.name) });
51480
+ }
51481
+ }
51482
+ return ancestors;
51483
+ }
51484
+ function syncNavStack(state, pages, path, pending) {
51485
+ const normalized = normalizePath(path);
51486
+ const page = matchNavPage(pages, normalized);
51487
+ if (!page) return { state, orbital: null };
51488
+ const label = pending && normalizePath(pending.href) === normalized ? pending.crumb : derivePageLabel(page.name);
51489
+ const prior = state[page.orbital];
51490
+ const stack = prior && prior.length > 0 ? [...prior] : seedAncestors(pages, normalized);
51491
+ const existing = stack.findIndex((e) => e.href === normalized);
51492
+ if (existing >= 0) {
51493
+ stack.length = existing + 1;
51494
+ if (pending && normalizePath(pending.href) === normalized) {
51495
+ stack[existing] = { href: normalized, label };
51496
+ }
51497
+ } else {
51498
+ stack.push({ href: normalized, label });
51499
+ }
51500
+ return { state: { ...state, [page.orbital]: stack }, orbital: page.orbital };
51501
+ }
51502
+ function previousEntry(state, pages, path) {
51503
+ const page = matchNavPage(pages, normalizePath(path));
51504
+ if (!page) return null;
51505
+ const stack = state[page.orbital] ?? [];
51506
+ return stack.length >= 2 ? stack[stack.length - 2] : null;
51507
+ }
51508
+ function entriesFor(state, pages, path) {
51509
+ const page = matchNavPage(pages, normalizePath(path));
51510
+ if (!page) return [];
51511
+ return state[page.orbital] ?? [];
51512
+ }
51513
+ var INERT_API = {
51514
+ entries: [],
51515
+ canGoBack: false,
51516
+ beginNavigate: () => void 0,
51517
+ back: () => void 0,
51518
+ goTo: () => void 0
51519
+ };
51520
+ var NavStackContext = createContext(INERT_API);
51521
+ function useNavStack2() {
51522
+ return useContext(NavStackContext);
51523
+ }
51524
+ function loadStored(storageKey) {
51525
+ if (!storageKey) return {};
51526
+ try {
51527
+ const raw = window.sessionStorage.getItem(storageKey);
51528
+ if (!raw) return {};
51529
+ const parsed = JSON.parse(raw);
51530
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
51531
+ return parsed;
51532
+ }
51533
+ return {};
51534
+ } catch {
51535
+ return {};
51536
+ }
51537
+ }
51538
+ function persistStored(storageKey, state) {
51539
+ if (!storageKey) return;
51540
+ try {
51541
+ window.sessionStorage.setItem(storageKey, JSON.stringify(state));
51542
+ } catch {
51543
+ }
51544
+ }
51545
+ var NavStackProvider = ({
51546
+ pages,
51547
+ currentPath,
51548
+ navigate,
51549
+ storageKey,
51550
+ children
51551
+ }) => {
51552
+ const [state, setState] = useState(() => loadStored(storageKey));
51553
+ const pendingCrumbRef = useRef(null);
51554
+ const stateRef = useRef(state);
51555
+ stateRef.current = state;
51556
+ useEffect(() => {
51557
+ const pending = pendingCrumbRef.current;
51558
+ pendingCrumbRef.current = null;
51559
+ setState((prev) => {
51560
+ const next = syncNavStack(prev, pages, currentPath, pending);
51561
+ if (next.state !== prev) persistStored(storageKey, next.state);
51562
+ return next.state;
51563
+ });
51564
+ }, [currentPath, pages, storageKey]);
51565
+ const beginNavigate = useCallback((href, crumb) => {
51566
+ pendingCrumbRef.current = crumb !== void 0 && crumb !== "" ? { href, crumb } : null;
51567
+ }, []);
51568
+ const back = useCallback(() => {
51569
+ const prev = previousEntry(stateRef.current, pages, currentPath);
51570
+ if (prev) navigate(prev.href);
51571
+ }, [pages, currentPath, navigate]);
51572
+ const goTo = useCallback(
51573
+ (href) => {
51574
+ navigate(href);
51575
+ },
51576
+ [navigate]
51577
+ );
51578
+ const entries = useMemo(
51579
+ () => entriesFor(state, pages, currentPath),
51580
+ [state, pages, currentPath]
51581
+ );
51582
+ const api = useMemo(
51583
+ () => ({
51584
+ entries,
51585
+ canGoBack: previousEntry(state, pages, currentPath) !== null,
51586
+ beginNavigate,
51587
+ back,
51588
+ goTo
51589
+ }),
51590
+ [entries, state, pages, currentPath, beginNavigate, back, goTo]
51591
+ );
51592
+ return /* @__PURE__ */ jsx(NavStackContext.Provider, { value: api, children });
51593
+ };
51594
+ NavStackProvider.displayName = "NavStackProvider";
51595
+ var NavStackRouterBridge = ({
51596
+ pages,
51597
+ storageKey = "almadar:navstack",
51598
+ children
51599
+ }) => {
51600
+ const location = useLocation();
51601
+ const routerNavigate = useNavigate();
51602
+ const navigate = useCallback(
51603
+ (path) => {
51604
+ routerNavigate(path);
51605
+ },
51606
+ [routerNavigate]
51607
+ );
51608
+ return /* @__PURE__ */ jsx(
51609
+ NavStackProvider,
51610
+ {
51611
+ pages,
51612
+ currentPath: location.pathname,
51613
+ navigate,
51614
+ storageKey,
51615
+ children
51616
+ }
51617
+ );
51618
+ };
51619
+ NavStackRouterBridge.displayName = "NavStackRouterBridge";
51005
51620
 
51006
- export { CurrentPagePathContext, CurrentPagePathProvider, EntityBindingContext, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, comparePathSpecificity2 as comparePathSpecificity, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, matchPathAmong2 as matchPathAmong, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntityBindingSnapshot2 as useEntityBindingSnapshot, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope2 as useTraitScope, useTraitScopeChain2 as useTraitScopeChain, useUser, useUserForEvaluation };
51621
+ export { CurrentPagePathContext, CurrentPagePathProvider, EntityBindingContext, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavStackProvider, NavStackRouterBridge, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, comparePathSpecificity2 as comparePathSpecificity, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, matchPathAmong2 as matchPathAmong, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntityBindingSnapshot2 as useEntityBindingSnapshot, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavStack2 as useNavStack, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope2 as useTraitScope, useTraitScopeChain2 as useTraitScopeChain, useUser, useUserForEvaluation };