@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.
@@ -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);
@@ -30467,6 +30685,196 @@ var init_PageTransition = __esm({
30467
30685
  PageTransition.displayName = "PageTransition";
30468
30686
  }
30469
30687
  });
30688
+ function ConfettiBurst({ item, lifeMs }) {
30689
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
30690
+ const left = (item.x ?? 0.5) * 100;
30691
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30692
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
30693
+ const rad = p.angle * Math.PI / 180;
30694
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
30695
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
30696
+ return /* @__PURE__ */ jsxRuntime.jsx(
30697
+ Box,
30698
+ {
30699
+ className: "absolute rounded-sm",
30700
+ style: {
30701
+ left: (p.left - 50) * 2,
30702
+ top: -10,
30703
+ width: p.size,
30704
+ height: p.size,
30705
+ backgroundColor: item.color ?? p.color,
30706
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
30707
+ opacity: 0,
30708
+ "--confetti-tx": `${tx}px`,
30709
+ "--confetti-ty": `${ty}px`,
30710
+ "--confetti-rotate": `${p.rotation}deg`
30711
+ }
30712
+ },
30713
+ p.id
30714
+ );
30715
+ }) });
30716
+ }
30717
+ function SparkleBurst({ item, lifeMs }) {
30718
+ const count = item.particleCount ?? 8;
30719
+ const scale = item.size ?? 1;
30720
+ const left = (item.x ?? 0.5) * 100;
30721
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30722
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
30723
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
30724
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
30725
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
30726
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
30727
+ return /* @__PURE__ */ jsxRuntime.jsx(
30728
+ Box,
30729
+ {
30730
+ className: "absolute rounded-full",
30731
+ style: {
30732
+ left: dx,
30733
+ top: dy,
30734
+ width: dot,
30735
+ height: dot,
30736
+ backgroundColor: item.color ?? "gold",
30737
+ opacity: 0,
30738
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
30739
+ }
30740
+ },
30741
+ i
30742
+ );
30743
+ }) });
30744
+ }
30745
+ function OverlayFxNode({ item, tickMs }) {
30746
+ const lifeMs = lifeMsOf(item, tickMs);
30747
+ switch (item.effect ?? "sparkle") {
30748
+ case "confetti":
30749
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfettiBurst, { item, lifeMs });
30750
+ case "flash":
30751
+ return /* @__PURE__ */ jsxRuntime.jsx(
30752
+ Box,
30753
+ {
30754
+ position: "absolute",
30755
+ className: "inset-0",
30756
+ style: {
30757
+ backgroundColor: item.color ?? "#ffffff",
30758
+ opacity: 0,
30759
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
30760
+ }
30761
+ }
30762
+ );
30763
+ case "streak-glow":
30764
+ return /* @__PURE__ */ jsxRuntime.jsx(
30765
+ Box,
30766
+ {
30767
+ position: "absolute",
30768
+ className: "inset-0",
30769
+ style: {
30770
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
30771
+ opacity: 0,
30772
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
30773
+ }
30774
+ }
30775
+ );
30776
+ case "float-text": {
30777
+ if (!item.message) return null;
30778
+ return /* @__PURE__ */ jsxRuntime.jsx(
30779
+ Box,
30780
+ {
30781
+ position: "absolute",
30782
+ style: {
30783
+ left: `${(item.x ?? 0.5) * 100}%`,
30784
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
30785
+ color: item.color ?? "var(--color-success)",
30786
+ fontWeight: 700,
30787
+ fontSize: 16 * (item.size ?? 1),
30788
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
30789
+ opacity: 0,
30790
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
30791
+ whiteSpace: "nowrap"
30792
+ },
30793
+ children: item.message
30794
+ }
30795
+ );
30796
+ }
30797
+ case "shake":
30798
+ return null;
30799
+ default:
30800
+ return /* @__PURE__ */ jsxRuntime.jsx(SparkleBurst, { item, lifeMs });
30801
+ }
30802
+ }
30803
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
30804
+ var init_FxOverlay = __esm({
30805
+ "components/core/molecules/FxOverlay.tsx"() {
30806
+ "use client";
30807
+ init_cn();
30808
+ init_Box();
30809
+ init_fx();
30810
+ DEFAULT_TICK_MS2 = 500;
30811
+ FX_OVERLAY_KEYFRAMES = `
30812
+ ${CONFETTI_BURST_KEYFRAMES}
30813
+ @keyframes fx-overlay-flash {
30814
+ 0% { opacity: 0.75; }
30815
+ 100% { opacity: 0; }
30816
+ }
30817
+ @keyframes fx-overlay-glow {
30818
+ 0% { opacity: 0.9; }
30819
+ 60% { opacity: 0.6; }
30820
+ 100% { opacity: 0; }
30821
+ }
30822
+ @keyframes fx-overlay-sparkle {
30823
+ 0% { opacity: 0; transform: scale(0); }
30824
+ 30% { opacity: 1; transform: scale(1); }
30825
+ 100% { opacity: 0; transform: scale(0.3); }
30826
+ }
30827
+ @keyframes fx-overlay-float {
30828
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
30829
+ 15% { opacity: 1; }
30830
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
30831
+ }
30832
+ @keyframes fx-overlay-shake {
30833
+ 0% { transform: translateX(0); }
30834
+ 15% { transform: translateX(-6px); }
30835
+ 30% { transform: translateX(5px); }
30836
+ 45% { transform: translateX(-4px); }
30837
+ 60% { transform: translateX(3px); }
30838
+ 75% { transform: translateX(-2px); }
30839
+ 100% { transform: translateX(0); }
30840
+ }
30841
+ `;
30842
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
30843
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
30844
+ const screenItems = (Array.isArray(items) ? items : []).filter(
30845
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
30846
+ );
30847
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
30848
+ const overlay = /* @__PURE__ */ jsxRuntime.jsxs(
30849
+ Box,
30850
+ {
30851
+ position: "absolute",
30852
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
30853
+ "aria-hidden": "true",
30854
+ children: [
30855
+ screenItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
30856
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: FX_OVERLAY_KEYFRAMES })
30857
+ ]
30858
+ }
30859
+ );
30860
+ if (children !== void 0 && children !== null) {
30861
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { position: "relative", className, children: [
30862
+ /* @__PURE__ */ jsxRuntime.jsx(
30863
+ Box,
30864
+ {
30865
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
30866
+ children
30867
+ },
30868
+ shakeItem?.id ?? "steady"
30869
+ ),
30870
+ overlay
30871
+ ] });
30872
+ }
30873
+ return overlay;
30874
+ };
30875
+ FxOverlay.displayName = "FxOverlay";
30876
+ }
30877
+ });
30470
30878
  function computePopoverStyle(position, triggerRect, popoverWidth) {
30471
30879
  if (position === "left" || position === "right") {
30472
30880
  return {
@@ -32472,7 +32880,7 @@ var init_MathCanvas = __esm({
32472
32880
  x: mapX((first.x + last.x) / 2),
32473
32881
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
32474
32882
  text: region.label,
32475
- color: "#111827",
32883
+ color,
32476
32884
  fontSize: 11
32477
32885
  });
32478
32886
  }
@@ -32505,14 +32913,14 @@ var init_MathCanvas = __esm({
32505
32913
  const px = mapX(guide.at);
32506
32914
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
32507
32915
  if (guide.label) {
32508
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
32916
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
32509
32917
  }
32510
32918
  } else {
32511
32919
  if (guide.at < yMin || guide.at > yMax) continue;
32512
32920
  const py = mapY(guide.at);
32513
32921
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
32514
32922
  if (guide.label) {
32515
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
32923
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
32516
32924
  }
32517
32925
  }
32518
32926
  }
@@ -32578,7 +32986,7 @@ var init_MathCanvas = __esm({
32578
32986
  x: (x1 + x2) / 2,
32579
32987
  y: xAxisY - peak - 8,
32580
32988
  text: hop.label,
32581
- color: "#111827",
32989
+ color,
32582
32990
  fontSize: 10,
32583
32991
  align: "center"
32584
32992
  });
@@ -32605,7 +33013,7 @@ var init_MathCanvas = __esm({
32605
33013
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
32606
33014
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
32607
33015
  text: angle.label,
32608
- color: "#111827",
33016
+ color,
32609
33017
  fontSize: 11,
32610
33018
  align: "center"
32611
33019
  });
@@ -32623,7 +33031,7 @@ var init_MathCanvas = __esm({
32623
33031
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
32624
33032
  });
32625
33033
  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 });
33034
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
32627
33035
  }
32628
33036
  }
32629
33037
  for (const v of vectors) {
@@ -32634,7 +33042,7 @@ var init_MathCanvas = __esm({
32634
33042
  const y2 = mapY(v.y + v.vy);
32635
33043
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
32636
33044
  if (v.label) {
32637
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
33045
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
32638
33046
  }
32639
33047
  }
32640
33048
  out.push(...shapes);
@@ -49917,6 +50325,7 @@ var init_component_registry_generated = __esm({
49917
50325
  init_DocSidebar();
49918
50326
  init_DocTOC();
49919
50327
  init_DocumentViewer();
50328
+ init_DrawFxLayer();
49920
50329
  init_DrawGroup();
49921
50330
  init_DrawMesh();
49922
50331
  init_DrawShape();
@@ -49947,6 +50356,7 @@ var init_component_registry_generated = __esm({
49947
50356
  init_FormField();
49948
50357
  init_FormSection();
49949
50358
  init_FormSectionHeader();
50359
+ init_FxOverlay();
49950
50360
  init_GameAudioToggle();
49951
50361
  init_GameHud();
49952
50362
  init_GameIcon();
@@ -50187,6 +50597,7 @@ var init_component_registry_generated = __esm({
50187
50597
  "DocSidebar": DocSidebar,
50188
50598
  "DocTOC": DocTOC,
50189
50599
  "DocumentViewer": DocumentViewer,
50600
+ "DrawFxLayer": DrawFxLayer,
50190
50601
  "DrawGroup": DrawGroup,
50191
50602
  "DrawMesh": DrawMesh,
50192
50603
  "DrawShape": DrawShape,
@@ -50217,6 +50628,7 @@ var init_component_registry_generated = __esm({
50217
50628
  "FormField": FormField,
50218
50629
  "FormLayout": FormLayout,
50219
50630
  "FormSectionHeader": FormSectionHeader,
50631
+ "FxOverlay": FxOverlay,
50220
50632
  "GameAudioToggle": GameAudioToggle,
50221
50633
  "GameHud": GameHud,
50222
50634
  "GameIcon": GameIcon,