@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.
@@ -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);
@@ -28128,6 +28346,196 @@ var init_PageTransition = __esm({
28128
28346
  PageTransition.displayName = "PageTransition";
28129
28347
  }
28130
28348
  });
28349
+ function ConfettiBurst({ item, lifeMs }) {
28350
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
28351
+ const left = (item.x ?? 0.5) * 100;
28352
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28353
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
28354
+ const rad = p.angle * Math.PI / 180;
28355
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
28356
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
28357
+ return /* @__PURE__ */ jsx(
28358
+ Box,
28359
+ {
28360
+ className: "absolute rounded-sm",
28361
+ style: {
28362
+ left: (p.left - 50) * 2,
28363
+ top: -10,
28364
+ width: p.size,
28365
+ height: p.size,
28366
+ backgroundColor: item.color ?? p.color,
28367
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
28368
+ opacity: 0,
28369
+ "--confetti-tx": `${tx}px`,
28370
+ "--confetti-ty": `${ty}px`,
28371
+ "--confetti-rotate": `${p.rotation}deg`
28372
+ }
28373
+ },
28374
+ p.id
28375
+ );
28376
+ }) });
28377
+ }
28378
+ function SparkleBurst({ item, lifeMs }) {
28379
+ const count = item.particleCount ?? 8;
28380
+ const scale = item.size ?? 1;
28381
+ const left = (item.x ?? 0.5) * 100;
28382
+ const top = (item.z ?? item.y ?? 0.4) * 100;
28383
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
28384
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
28385
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
28386
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
28387
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
28388
+ return /* @__PURE__ */ jsx(
28389
+ Box,
28390
+ {
28391
+ className: "absolute rounded-full",
28392
+ style: {
28393
+ left: dx,
28394
+ top: dy,
28395
+ width: dot,
28396
+ height: dot,
28397
+ backgroundColor: item.color ?? "gold",
28398
+ opacity: 0,
28399
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
28400
+ }
28401
+ },
28402
+ i
28403
+ );
28404
+ }) });
28405
+ }
28406
+ function OverlayFxNode({ item, tickMs }) {
28407
+ const lifeMs = lifeMsOf(item, tickMs);
28408
+ switch (item.effect ?? "sparkle") {
28409
+ case "confetti":
28410
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
28411
+ case "flash":
28412
+ return /* @__PURE__ */ jsx(
28413
+ Box,
28414
+ {
28415
+ position: "absolute",
28416
+ className: "inset-0",
28417
+ style: {
28418
+ backgroundColor: item.color ?? "#ffffff",
28419
+ opacity: 0,
28420
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
28421
+ }
28422
+ }
28423
+ );
28424
+ case "streak-glow":
28425
+ return /* @__PURE__ */ jsx(
28426
+ Box,
28427
+ {
28428
+ position: "absolute",
28429
+ className: "inset-0",
28430
+ style: {
28431
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
28432
+ opacity: 0,
28433
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
28434
+ }
28435
+ }
28436
+ );
28437
+ case "float-text": {
28438
+ if (!item.message) return null;
28439
+ return /* @__PURE__ */ jsx(
28440
+ Box,
28441
+ {
28442
+ position: "absolute",
28443
+ style: {
28444
+ left: `${(item.x ?? 0.5) * 100}%`,
28445
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
28446
+ color: item.color ?? "var(--color-success)",
28447
+ fontWeight: 700,
28448
+ fontSize: 16 * (item.size ?? 1),
28449
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
28450
+ opacity: 0,
28451
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
28452
+ whiteSpace: "nowrap"
28453
+ },
28454
+ children: item.message
28455
+ }
28456
+ );
28457
+ }
28458
+ case "shake":
28459
+ return null;
28460
+ default:
28461
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
28462
+ }
28463
+ }
28464
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
28465
+ var init_FxOverlay = __esm({
28466
+ "components/core/molecules/FxOverlay.tsx"() {
28467
+ "use client";
28468
+ init_cn();
28469
+ init_Box();
28470
+ init_fx();
28471
+ DEFAULT_TICK_MS2 = 500;
28472
+ FX_OVERLAY_KEYFRAMES = `
28473
+ ${CONFETTI_BURST_KEYFRAMES}
28474
+ @keyframes fx-overlay-flash {
28475
+ 0% { opacity: 0.75; }
28476
+ 100% { opacity: 0; }
28477
+ }
28478
+ @keyframes fx-overlay-glow {
28479
+ 0% { opacity: 0.9; }
28480
+ 60% { opacity: 0.6; }
28481
+ 100% { opacity: 0; }
28482
+ }
28483
+ @keyframes fx-overlay-sparkle {
28484
+ 0% { opacity: 0; transform: scale(0); }
28485
+ 30% { opacity: 1; transform: scale(1); }
28486
+ 100% { opacity: 0; transform: scale(0.3); }
28487
+ }
28488
+ @keyframes fx-overlay-float {
28489
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
28490
+ 15% { opacity: 1; }
28491
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
28492
+ }
28493
+ @keyframes fx-overlay-shake {
28494
+ 0% { transform: translateX(0); }
28495
+ 15% { transform: translateX(-6px); }
28496
+ 30% { transform: translateX(5px); }
28497
+ 45% { transform: translateX(-4px); }
28498
+ 60% { transform: translateX(3px); }
28499
+ 75% { transform: translateX(-2px); }
28500
+ 100% { transform: translateX(0); }
28501
+ }
28502
+ `;
28503
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28504
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28505
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28506
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28507
+ );
28508
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28509
+ const overlay = /* @__PURE__ */ jsxs(
28510
+ Box,
28511
+ {
28512
+ position: "absolute",
28513
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28514
+ "aria-hidden": "true",
28515
+ children: [
28516
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28517
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28518
+ ]
28519
+ }
28520
+ );
28521
+ if (children !== void 0 && children !== null) {
28522
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
28523
+ /* @__PURE__ */ jsx(
28524
+ Box,
28525
+ {
28526
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28527
+ children
28528
+ },
28529
+ shakeItem?.id ?? "steady"
28530
+ ),
28531
+ overlay
28532
+ ] });
28533
+ }
28534
+ return overlay;
28535
+ };
28536
+ FxOverlay.displayName = "FxOverlay";
28537
+ }
28538
+ });
28131
28539
  function computePopoverStyle(position, triggerRect, popoverWidth) {
28132
28540
  if (position === "left" || position === "right") {
28133
28541
  return {
@@ -30133,7 +30541,7 @@ var init_MathCanvas = __esm({
30133
30541
  x: mapX((first.x + last.x) / 2),
30134
30542
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
30135
30543
  text: region.label,
30136
- color: "#111827",
30544
+ color,
30137
30545
  fontSize: 11
30138
30546
  });
30139
30547
  }
@@ -30166,14 +30574,14 @@ var init_MathCanvas = __esm({
30166
30574
  const px = mapX(guide.at);
30167
30575
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
30168
30576
  if (guide.label) {
30169
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30577
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
30170
30578
  }
30171
30579
  } else {
30172
30580
  if (guide.at < yMin || guide.at > yMax) continue;
30173
30581
  const py = mapY(guide.at);
30174
30582
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
30175
30583
  if (guide.label) {
30176
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30584
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
30177
30585
  }
30178
30586
  }
30179
30587
  }
@@ -30239,7 +30647,7 @@ var init_MathCanvas = __esm({
30239
30647
  x: (x1 + x2) / 2,
30240
30648
  y: xAxisY - peak - 8,
30241
30649
  text: hop.label,
30242
- color: "#111827",
30650
+ color,
30243
30651
  fontSize: 10,
30244
30652
  align: "center"
30245
30653
  });
@@ -30266,7 +30674,7 @@ var init_MathCanvas = __esm({
30266
30674
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
30267
30675
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
30268
30676
  text: angle.label,
30269
- color: "#111827",
30677
+ color,
30270
30678
  fontSize: 11,
30271
30679
  align: "center"
30272
30680
  });
@@ -30284,7 +30692,7 @@ var init_MathCanvas = __esm({
30284
30692
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
30285
30693
  });
30286
30694
  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 });
30695
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
30288
30696
  }
30289
30697
  }
30290
30698
  for (const v of vectors) {
@@ -30295,7 +30703,7 @@ var init_MathCanvas = __esm({
30295
30703
  const y2 = mapY(v.y + v.vy);
30296
30704
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
30297
30705
  if (v.label) {
30298
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30706
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
30299
30707
  }
30300
30708
  }
30301
30709
  out.push(...shapes);
@@ -47968,6 +48376,7 @@ var init_component_registry_generated = __esm({
47968
48376
  init_DocSidebar();
47969
48377
  init_DocTOC();
47970
48378
  init_DocumentViewer();
48379
+ init_DrawFxLayer();
47971
48380
  init_DrawGroup();
47972
48381
  init_DrawMesh();
47973
48382
  init_DrawShape();
@@ -47998,6 +48407,7 @@ var init_component_registry_generated = __esm({
47998
48407
  init_FormField();
47999
48408
  init_FormSection();
48000
48409
  init_FormSectionHeader();
48410
+ init_FxOverlay();
48001
48411
  init_GameAudioToggle();
48002
48412
  init_GameHud();
48003
48413
  init_GameIcon();
@@ -48238,6 +48648,7 @@ var init_component_registry_generated = __esm({
48238
48648
  "DocSidebar": DocSidebar,
48239
48649
  "DocTOC": DocTOC,
48240
48650
  "DocumentViewer": DocumentViewer,
48651
+ "DrawFxLayer": DrawFxLayer,
48241
48652
  "DrawGroup": DrawGroup,
48242
48653
  "DrawMesh": DrawMesh,
48243
48654
  "DrawShape": DrawShape,
@@ -48268,6 +48679,7 @@ var init_component_registry_generated = __esm({
48268
48679
  "FormField": FormField,
48269
48680
  "FormLayout": FormLayout,
48270
48681
  "FormSectionHeader": FormSectionHeader,
48682
+ "FxOverlay": FxOverlay,
48271
48683
  "GameAudioToggle": GameAudioToggle,
48272
48684
  "GameHud": GameHud,
48273
48685
  "GameIcon": GameIcon,