@almadar/ui 5.152.0 → 5.153.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.
@@ -5803,27 +5803,61 @@ var init_InfiniteScrollSentinel = __esm({
5803
5803
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
5804
5804
  }
5805
5805
  });
5806
- function createParticles(count) {
5807
- return Array.from({ length: count }, () => {
5808
- particleIdCounter += 1;
5809
- return {
5810
- id: particleIdCounter,
5811
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
5812
- left: 30 + Math.random() * 40,
5813
- delay: Math.random() * 300,
5814
- angle: Math.random() * 360,
5815
- distance: 40 + Math.random() * 80,
5816
- rotation: Math.random() * 720 - 360,
5817
- size: 4 + Math.random() * 6
5818
- };
5819
- });
5806
+
5807
+ // components/core/atoms/fx.ts
5808
+ function resolveFxView(item, presets) {
5809
+ const row = presets?.find((p) => p.type === item.type);
5810
+ if (!row) return item;
5811
+ return {
5812
+ ...item,
5813
+ space: item.space ?? row.space,
5814
+ effect: item.effect ?? row.effect,
5815
+ color: item.color ?? row.color,
5816
+ size: item.size ?? row.size,
5817
+ vx: item.vx ?? row.vx,
5818
+ vy: item.vy ?? row.vy,
5819
+ vz: item.vz ?? row.vz,
5820
+ particleCount: item.particleCount ?? row.particleCount,
5821
+ shape: row.shape,
5822
+ count: row.count,
5823
+ glow: row.glow,
5824
+ gravity: row.gravity,
5825
+ color2: row.color2
5826
+ };
5820
5827
  }
5821
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
5822
- var init_ConfettiEffect = __esm({
5823
- "components/core/atoms/ConfettiEffect.tsx"() {
5824
- "use client";
5825
- init_cn();
5826
- init_Box();
5828
+ function fxLifecycle(item, epochNowMs, tickMs) {
5829
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
5830
+ const lifeMs = maxTtl * tickMs;
5831
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
5832
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
5833
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
5834
+ }
5835
+ function fxHash01(seed, salt) {
5836
+ let h = 2166136261 ^ salt;
5837
+ for (let i = 0; i < seed.length; i++) {
5838
+ h ^= seed.charCodeAt(i);
5839
+ h = Math.imul(h, 16777619);
5840
+ }
5841
+ h ^= h >>> 13;
5842
+ h = Math.imul(h, 1274126177);
5843
+ h ^= h >>> 16;
5844
+ return (h >>> 0) / 4294967296;
5845
+ }
5846
+ function createConfettiParticles(count, seed) {
5847
+ return Array.from({ length: count }, (_, i) => ({
5848
+ id: i,
5849
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
5850
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
5851
+ delay: fxHash01(seed, i * 7 + 3) * 300,
5852
+ angle: fxHash01(seed, i * 7 + 4) * 360,
5853
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
5854
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
5855
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
5856
+ }));
5857
+ }
5858
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
5859
+ var init_fx = __esm({
5860
+ "components/core/atoms/fx.ts"() {
5827
5861
  CONFETTI_COLORS = [
5828
5862
  "var(--color-primary)",
5829
5863
  "var(--color-success)",
@@ -5832,7 +5866,30 @@ var init_ConfettiEffect = __esm({
5832
5866
  "gold",
5833
5867
  "dodgerblue"
5834
5868
  ];
5835
- particleIdCounter = 0;
5869
+ CONFETTI_BURST_KEYFRAMES = `
5870
+ @keyframes confetti-burst {
5871
+ 0% {
5872
+ opacity: 1;
5873
+ transform: translate(0, 0) rotate(0deg) scale(1);
5874
+ }
5875
+ 70% {
5876
+ opacity: 1;
5877
+ }
5878
+ 100% {
5879
+ opacity: 0;
5880
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
5881
+ }
5882
+ }
5883
+ `;
5884
+ }
5885
+ });
5886
+ var ConfettiEffect;
5887
+ var init_ConfettiEffect = __esm({
5888
+ "components/core/atoms/ConfettiEffect.tsx"() {
5889
+ "use client";
5890
+ init_cn();
5891
+ init_Box();
5892
+ init_fx();
5836
5893
  ConfettiEffect = ({
5837
5894
  trigger,
5838
5895
  duration = 2e3,
@@ -5841,11 +5898,13 @@ var init_ConfettiEffect = __esm({
5841
5898
  }) => {
5842
5899
  const [particles, setParticles] = React87.useState([]);
5843
5900
  const previousTriggerRef = React87.useRef(false);
5901
+ const burstRef = React87.useRef(0);
5844
5902
  React87.useEffect(() => {
5845
5903
  const wasFalse = !previousTriggerRef.current;
5846
5904
  previousTriggerRef.current = trigger;
5847
5905
  if (trigger && wasFalse) {
5848
- const newParticles = createParticles(particleCount);
5906
+ burstRef.current += 1;
5907
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
5849
5908
  setParticles(newParticles);
5850
5909
  const timer = window.setTimeout(() => {
5851
5910
  setParticles([]);
@@ -5893,21 +5952,7 @@ var init_ConfettiEffect = __esm({
5893
5952
  p.id
5894
5953
  );
5895
5954
  }),
5896
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
5897
- @keyframes confetti-burst {
5898
- 0% {
5899
- opacity: 1;
5900
- transform: translate(0, 0) rotate(0deg) scale(1);
5901
- }
5902
- 70% {
5903
- opacity: 1;
5904
- }
5905
- 100% {
5906
- opacity: 0;
5907
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
5908
- }
5909
- }
5910
- ` })
5955
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
5911
5956
  ]
5912
5957
  }
5913
5958
  );
@@ -9464,7 +9509,7 @@ var init_DrawShape = __esm({
9464
9509
  const cx = p.x + (node.offsetX ?? 0) * tw;
9465
9510
  const cy = p.y + (node.offsetY ?? 0) * tw;
9466
9511
  const rx = (node.radiusX ?? 0) * tw;
9467
- const ry = (node.radiusY ?? rx) * tw;
9512
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9468
9513
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9469
9514
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9470
9515
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9654,6 +9699,172 @@ var init_DrawTextLayer = __esm({
9654
9699
  };
9655
9700
  }
9656
9701
  });
9702
+
9703
+ // components/game/molecules/DrawFxLayer.tsx
9704
+ function fxPosition(view, dim, ageSec) {
9705
+ const g = view.gravity ?? 0;
9706
+ const fall = 0.5 * g * ageSec * ageSec;
9707
+ {
9708
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
9709
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
9710
+ return {
9711
+ x: base2.x + (view.vx ?? 0) * ageSec,
9712
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
9713
+ };
9714
+ }
9715
+ }
9716
+ function sparkShape(view, pos, i, fade, progress) {
9717
+ const size = view.size ?? 0.5;
9718
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9719
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
9720
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
9721
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9722
+ return {
9723
+ type: "draw-shape",
9724
+ shape: "ellipse",
9725
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
9726
+ anchor: "center",
9727
+ radiusX: r,
9728
+ fill: color,
9729
+ blendMode: "lighter",
9730
+ opacity: fade,
9731
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9732
+ };
9733
+ }
9734
+ function proceduralShapes(view, pos, fade, progress) {
9735
+ const size = view.size ?? 0.5;
9736
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9737
+ switch (view.shape ?? "spark") {
9738
+ case "ring": {
9739
+ return [
9740
+ {
9741
+ type: "draw-shape",
9742
+ shape: "ellipse",
9743
+ position: pos,
9744
+ anchor: "center",
9745
+ radiusX: size * easeOut2(progress),
9746
+ stroke: view.color2 ?? color,
9747
+ strokeWidth: 2,
9748
+ blendMode: "lighter",
9749
+ opacity: fade,
9750
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9751
+ }
9752
+ ];
9753
+ }
9754
+ case "puff": {
9755
+ const count = view.count ?? 3;
9756
+ return Array.from({ length: count }, (_, i) => {
9757
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
9758
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
9759
+ return {
9760
+ type: "draw-shape",
9761
+ shape: "ellipse",
9762
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
9763
+ anchor: "center",
9764
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
9765
+ fill: i === 0 && view.color2 ? view.color2 : color,
9766
+ opacity: fade * 0.6,
9767
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9768
+ };
9769
+ });
9770
+ }
9771
+ case "streak": {
9772
+ const count = view.count ?? 5;
9773
+ return Array.from({ length: count }, (_, i) => {
9774
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9775
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
9776
+ const len = size * 0.35;
9777
+ return {
9778
+ type: "draw-shape",
9779
+ shape: "ellipse",
9780
+ position: pos,
9781
+ anchor: "center",
9782
+ offsetX: inner + len / 2,
9783
+ offsetY: 0,
9784
+ radiusX: len / 2,
9785
+ radiusY: size * 0.04,
9786
+ rotate: angle,
9787
+ fill: color,
9788
+ blendMode: "lighter",
9789
+ opacity: fade,
9790
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9791
+ };
9792
+ });
9793
+ }
9794
+ default: {
9795
+ const count = view.count ?? 6;
9796
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
9797
+ }
9798
+ }
9799
+ }
9800
+ function expandFxItem(view, node, epochNowMs, dim) {
9801
+ if (view.space === "screen") return [];
9802
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
9803
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
9804
+ if (progress >= 1) return [];
9805
+ const pos = fxPosition(view, dim, ageMs / 1e3);
9806
+ if (!pos) return [];
9807
+ const out = [];
9808
+ const artItems = node.art?.[view.type]?.["idle"];
9809
+ const sprite = node.sprites?.[view.type];
9810
+ if (Array.isArray(artItems)) {
9811
+ out.push({
9812
+ type: "draw-group",
9813
+ position: pos,
9814
+ opacity: fade,
9815
+ ...view.size !== void 0 ? { scale: view.size } : {},
9816
+ items: artItems
9817
+ });
9818
+ } else if (sprite?.url) {
9819
+ const spriteSize = view.size ?? 0.6;
9820
+ out.push({
9821
+ type: "draw-sprite",
9822
+ position: pos,
9823
+ asset: sprite,
9824
+ anchor: "center",
9825
+ width: spriteSize,
9826
+ height: spriteSize,
9827
+ opacity: fade
9828
+ });
9829
+ } else {
9830
+ out.push(...proceduralShapes(view, pos, fade, progress));
9831
+ }
9832
+ if (view.message) {
9833
+ const text = {
9834
+ type: "draw-text",
9835
+ text: view.message,
9836
+ position: pos,
9837
+ offsetY: -(0.15 + 0.55 * progress),
9838
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
9839
+ opacity: fade
9840
+ };
9841
+ out.push(text);
9842
+ }
9843
+ return out;
9844
+ }
9845
+ function expandFxLayer(node, epochNowMs, dim) {
9846
+ if (!Array.isArray(node.items)) return [];
9847
+ const out = [];
9848
+ for (const item of node.items) {
9849
+ if (!item || typeof item.id !== "string") continue;
9850
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
9851
+ }
9852
+ return out;
9853
+ }
9854
+ function DrawFxLayer(_props) {
9855
+ return null;
9856
+ }
9857
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
9858
+ var init_DrawFxLayer = __esm({
9859
+ "components/game/molecules/DrawFxLayer.tsx"() {
9860
+ "use client";
9861
+ init_fx();
9862
+ DEFAULT_TICK_MS = 500;
9863
+ DEFAULT_TEXT_COLOR = "#ffe066";
9864
+ DEFAULT_SPARK_COLOR = "#ffffff";
9865
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
9866
+ }
9867
+ });
9657
9868
  function paintDrawable(painter, node, dctx) {
9658
9869
  switch (node.type) {
9659
9870
  case "draw-sprite":
@@ -9701,6 +9912,11 @@ function paintDrawable(painter, node, dctx) {
9701
9912
  case "draw-text-layer":
9702
9913
  paintTextLayer(painter, node, dctx);
9703
9914
  break;
9915
+ case "draw-fx-layer": {
9916
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
9917
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
9918
+ break;
9919
+ }
9704
9920
  }
9705
9921
  }
9706
9922
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -9715,6 +9931,7 @@ var init_paintDispatch = __esm({
9715
9931
  init_DrawSpriteLayer();
9716
9932
  init_DrawShapeLayer();
9717
9933
  init_DrawTextLayer();
9934
+ init_DrawFxLayer();
9718
9935
  paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
9719
9936
  warnedUnsupported2d = /* @__PURE__ */ new Set();
9720
9937
  warnUnsupported2d = (kind) => {
@@ -9963,6 +10180,7 @@ function Canvas2D({
9963
10180
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
9964
10181
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
9965
10182
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10183
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
9966
10184
  return false;
9967
10185
  };
9968
10186
  const animRafRef = React87.useRef(0);
@@ -28202,6 +28420,196 @@ var init_PageTransition = __esm({
28202
28420
  PageTransition.displayName = "PageTransition";
28203
28421
  }
28204
28422
  });
28423
+ function ConfettiBurst({ item, lifeMs }) {
28424
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
28425
+ const left = (item.x ?? 0.5) * 100;
28426
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28427
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
28428
+ const rad = p.angle * Math.PI / 180;
28429
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
28430
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
28431
+ return /* @__PURE__ */ jsxRuntime.jsx(
28432
+ Box,
28433
+ {
28434
+ className: "absolute rounded-sm",
28435
+ style: {
28436
+ left: (p.left - 50) * 2,
28437
+ top: -10,
28438
+ width: p.size,
28439
+ height: p.size,
28440
+ backgroundColor: item.color ?? p.color,
28441
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
28442
+ opacity: 0,
28443
+ "--confetti-tx": `${tx}px`,
28444
+ "--confetti-ty": `${ty}px`,
28445
+ "--confetti-rotate": `${p.rotation}deg`
28446
+ }
28447
+ },
28448
+ p.id
28449
+ );
28450
+ }) });
28451
+ }
28452
+ function SparkleBurst({ item, lifeMs }) {
28453
+ const count = item.particleCount ?? 8;
28454
+ const scale = item.size ?? 1;
28455
+ const left = (item.x ?? 0.5) * 100;
28456
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28457
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
28458
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
28459
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
28460
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
28461
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
28462
+ return /* @__PURE__ */ jsxRuntime.jsx(
28463
+ Box,
28464
+ {
28465
+ className: "absolute rounded-full",
28466
+ style: {
28467
+ left: dx,
28468
+ top: dy,
28469
+ width: dot,
28470
+ height: dot,
28471
+ backgroundColor: item.color ?? "gold",
28472
+ opacity: 0,
28473
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
28474
+ }
28475
+ },
28476
+ i
28477
+ );
28478
+ }) });
28479
+ }
28480
+ function OverlayFxNode({ item, tickMs }) {
28481
+ const lifeMs = lifeMsOf(item, tickMs);
28482
+ switch (item.effect ?? "sparkle") {
28483
+ case "confetti":
28484
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfettiBurst, { item, lifeMs });
28485
+ case "flash":
28486
+ return /* @__PURE__ */ jsxRuntime.jsx(
28487
+ Box,
28488
+ {
28489
+ position: "absolute",
28490
+ className: "inset-0",
28491
+ style: {
28492
+ backgroundColor: item.color ?? "#ffffff",
28493
+ opacity: 0,
28494
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
28495
+ }
28496
+ }
28497
+ );
28498
+ case "streak-glow":
28499
+ return /* @__PURE__ */ jsxRuntime.jsx(
28500
+ Box,
28501
+ {
28502
+ position: "absolute",
28503
+ className: "inset-0",
28504
+ style: {
28505
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
28506
+ opacity: 0,
28507
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
28508
+ }
28509
+ }
28510
+ );
28511
+ case "float-text": {
28512
+ if (!item.message) return null;
28513
+ return /* @__PURE__ */ jsxRuntime.jsx(
28514
+ Box,
28515
+ {
28516
+ position: "absolute",
28517
+ style: {
28518
+ left: `${(item.x ?? 0.5) * 100}%`,
28519
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
28520
+ color: item.color ?? "var(--color-success)",
28521
+ fontWeight: 700,
28522
+ fontSize: 16 * (item.size ?? 1),
28523
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
28524
+ opacity: 0,
28525
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
28526
+ whiteSpace: "nowrap"
28527
+ },
28528
+ children: item.message
28529
+ }
28530
+ );
28531
+ }
28532
+ case "shake":
28533
+ return null;
28534
+ default:
28535
+ return /* @__PURE__ */ jsxRuntime.jsx(SparkleBurst, { item, lifeMs });
28536
+ }
28537
+ }
28538
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
28539
+ var init_FxOverlay = __esm({
28540
+ "components/core/molecules/FxOverlay.tsx"() {
28541
+ "use client";
28542
+ init_cn();
28543
+ init_Box();
28544
+ init_fx();
28545
+ DEFAULT_TICK_MS2 = 500;
28546
+ FX_OVERLAY_KEYFRAMES = `
28547
+ ${CONFETTI_BURST_KEYFRAMES}
28548
+ @keyframes fx-overlay-flash {
28549
+ 0% { opacity: 0.75; }
28550
+ 100% { opacity: 0; }
28551
+ }
28552
+ @keyframes fx-overlay-glow {
28553
+ 0% { opacity: 0.9; }
28554
+ 60% { opacity: 0.6; }
28555
+ 100% { opacity: 0; }
28556
+ }
28557
+ @keyframes fx-overlay-sparkle {
28558
+ 0% { opacity: 0; transform: scale(0); }
28559
+ 30% { opacity: 1; transform: scale(1); }
28560
+ 100% { opacity: 0; transform: scale(0.3); }
28561
+ }
28562
+ @keyframes fx-overlay-float {
28563
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
28564
+ 15% { opacity: 1; }
28565
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
28566
+ }
28567
+ @keyframes fx-overlay-shake {
28568
+ 0% { transform: translateX(0); }
28569
+ 15% { transform: translateX(-6px); }
28570
+ 30% { transform: translateX(5px); }
28571
+ 45% { transform: translateX(-4px); }
28572
+ 60% { transform: translateX(3px); }
28573
+ 75% { transform: translateX(-2px); }
28574
+ 100% { transform: translateX(0); }
28575
+ }
28576
+ `;
28577
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28578
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28579
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28580
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28581
+ );
28582
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28583
+ const overlay = /* @__PURE__ */ jsxRuntime.jsxs(
28584
+ Box,
28585
+ {
28586
+ position: "absolute",
28587
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28588
+ "aria-hidden": "true",
28589
+ children: [
28590
+ screenItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28591
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28592
+ ]
28593
+ }
28594
+ );
28595
+ if (children !== void 0 && children !== null) {
28596
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { position: "relative", className, children: [
28597
+ /* @__PURE__ */ jsxRuntime.jsx(
28598
+ Box,
28599
+ {
28600
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28601
+ children
28602
+ },
28603
+ shakeItem?.id ?? "steady"
28604
+ ),
28605
+ overlay
28606
+ ] });
28607
+ }
28608
+ return overlay;
28609
+ };
28610
+ FxOverlay.displayName = "FxOverlay";
28611
+ }
28612
+ });
28205
28613
  function computePopoverStyle(position, triggerRect, popoverWidth) {
28206
28614
  if (position === "left" || position === "right") {
28207
28615
  return {
@@ -30207,7 +30615,7 @@ var init_MathCanvas = __esm({
30207
30615
  x: mapX((first.x + last.x) / 2),
30208
30616
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
30209
30617
  text: region.label,
30210
- color: "#111827",
30618
+ color,
30211
30619
  fontSize: 11
30212
30620
  });
30213
30621
  }
@@ -30240,14 +30648,14 @@ var init_MathCanvas = __esm({
30240
30648
  const px = mapX(guide.at);
30241
30649
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
30242
30650
  if (guide.label) {
30243
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30651
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
30244
30652
  }
30245
30653
  } else {
30246
30654
  if (guide.at < yMin || guide.at > yMax) continue;
30247
30655
  const py = mapY(guide.at);
30248
30656
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
30249
30657
  if (guide.label) {
30250
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30658
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
30251
30659
  }
30252
30660
  }
30253
30661
  }
@@ -30313,7 +30721,7 @@ var init_MathCanvas = __esm({
30313
30721
  x: (x1 + x2) / 2,
30314
30722
  y: xAxisY - peak - 8,
30315
30723
  text: hop.label,
30316
- color: "#111827",
30724
+ color,
30317
30725
  fontSize: 10,
30318
30726
  align: "center"
30319
30727
  });
@@ -30340,7 +30748,7 @@ var init_MathCanvas = __esm({
30340
30748
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
30341
30749
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
30342
30750
  text: angle.label,
30343
- color: "#111827",
30751
+ color,
30344
30752
  fontSize: 11,
30345
30753
  align: "center"
30346
30754
  });
@@ -30358,7 +30766,7 @@ var init_MathCanvas = __esm({
30358
30766
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
30359
30767
  });
30360
30768
  if (p.label) {
30361
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30769
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
30362
30770
  }
30363
30771
  }
30364
30772
  for (const v of vectors) {
@@ -30369,7 +30777,7 @@ var init_MathCanvas = __esm({
30369
30777
  const y2 = mapY(v.y + v.vy);
30370
30778
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
30371
30779
  if (v.label) {
30372
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30780
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
30373
30781
  }
30374
30782
  }
30375
30783
  out.push(...shapes);
@@ -48042,6 +48450,7 @@ var init_component_registry_generated = __esm({
48042
48450
  init_DocSidebar();
48043
48451
  init_DocTOC();
48044
48452
  init_DocumentViewer();
48453
+ init_DrawFxLayer();
48045
48454
  init_DrawGroup();
48046
48455
  init_DrawMesh();
48047
48456
  init_DrawShape();
@@ -48072,6 +48481,7 @@ var init_component_registry_generated = __esm({
48072
48481
  init_FormField();
48073
48482
  init_FormSection();
48074
48483
  init_FormSectionHeader();
48484
+ init_FxOverlay();
48075
48485
  init_GameAudioToggle();
48076
48486
  init_GameHud();
48077
48487
  init_GameIcon();
@@ -48312,6 +48722,7 @@ var init_component_registry_generated = __esm({
48312
48722
  "DocSidebar": DocSidebar,
48313
48723
  "DocTOC": DocTOC,
48314
48724
  "DocumentViewer": DocumentViewer,
48725
+ "DrawFxLayer": DrawFxLayer,
48315
48726
  "DrawGroup": DrawGroup,
48316
48727
  "DrawMesh": DrawMesh,
48317
48728
  "DrawShape": DrawShape,
@@ -48342,6 +48753,7 @@ var init_component_registry_generated = __esm({
48342
48753
  "FormField": FormField,
48343
48754
  "FormLayout": FormLayout,
48344
48755
  "FormSectionHeader": FormSectionHeader,
48756
+ "FxOverlay": FxOverlay,
48345
48757
  "GameAudioToggle": GameAudioToggle,
48346
48758
  "GameHud": GameHud,
48347
48759
  "GameIcon": GameIcon,