@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.
package/dist/avl/index.js CHANGED
@@ -9470,27 +9470,61 @@ var init_InfiniteScrollSentinel = __esm({
9470
9470
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
9471
9471
  }
9472
9472
  });
9473
- function createParticles(count) {
9474
- return Array.from({ length: count }, () => {
9475
- particleIdCounter += 1;
9476
- return {
9477
- id: particleIdCounter,
9478
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
9479
- left: 30 + Math.random() * 40,
9480
- delay: Math.random() * 300,
9481
- angle: Math.random() * 360,
9482
- distance: 40 + Math.random() * 80,
9483
- rotation: Math.random() * 720 - 360,
9484
- size: 4 + Math.random() * 6
9485
- };
9486
- });
9473
+
9474
+ // components/core/atoms/fx.ts
9475
+ function resolveFxView(item, presets) {
9476
+ const row = presets?.find((p) => p.type === item.type);
9477
+ if (!row) return item;
9478
+ return {
9479
+ ...item,
9480
+ space: item.space ?? row.space,
9481
+ effect: item.effect ?? row.effect,
9482
+ color: item.color ?? row.color,
9483
+ size: item.size ?? row.size,
9484
+ vx: item.vx ?? row.vx,
9485
+ vy: item.vy ?? row.vy,
9486
+ vz: item.vz ?? row.vz,
9487
+ particleCount: item.particleCount ?? row.particleCount,
9488
+ shape: row.shape,
9489
+ count: row.count,
9490
+ glow: row.glow,
9491
+ gravity: row.gravity,
9492
+ color2: row.color2
9493
+ };
9487
9494
  }
9488
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
9489
- var init_ConfettiEffect = __esm({
9490
- "components/core/atoms/ConfettiEffect.tsx"() {
9491
- "use client";
9492
- init_cn();
9493
- init_Box();
9495
+ function fxLifecycle(item, epochNowMs, tickMs) {
9496
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
9497
+ const lifeMs = maxTtl * tickMs;
9498
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
9499
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
9500
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
9501
+ }
9502
+ function fxHash01(seed, salt) {
9503
+ let h = 2166136261 ^ salt;
9504
+ for (let i = 0; i < seed.length; i++) {
9505
+ h ^= seed.charCodeAt(i);
9506
+ h = Math.imul(h, 16777619);
9507
+ }
9508
+ h ^= h >>> 13;
9509
+ h = Math.imul(h, 1274126177);
9510
+ h ^= h >>> 16;
9511
+ return (h >>> 0) / 4294967296;
9512
+ }
9513
+ function createConfettiParticles(count, seed) {
9514
+ return Array.from({ length: count }, (_, i) => ({
9515
+ id: i,
9516
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
9517
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
9518
+ delay: fxHash01(seed, i * 7 + 3) * 300,
9519
+ angle: fxHash01(seed, i * 7 + 4) * 360,
9520
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
9521
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
9522
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
9523
+ }));
9524
+ }
9525
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
9526
+ var init_fx = __esm({
9527
+ "components/core/atoms/fx.ts"() {
9494
9528
  CONFETTI_COLORS = [
9495
9529
  "var(--color-primary)",
9496
9530
  "var(--color-success)",
@@ -9499,7 +9533,30 @@ var init_ConfettiEffect = __esm({
9499
9533
  "gold",
9500
9534
  "dodgerblue"
9501
9535
  ];
9502
- particleIdCounter = 0;
9536
+ CONFETTI_BURST_KEYFRAMES = `
9537
+ @keyframes confetti-burst {
9538
+ 0% {
9539
+ opacity: 1;
9540
+ transform: translate(0, 0) rotate(0deg) scale(1);
9541
+ }
9542
+ 70% {
9543
+ opacity: 1;
9544
+ }
9545
+ 100% {
9546
+ opacity: 0;
9547
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9548
+ }
9549
+ }
9550
+ `;
9551
+ }
9552
+ });
9553
+ var ConfettiEffect;
9554
+ var init_ConfettiEffect = __esm({
9555
+ "components/core/atoms/ConfettiEffect.tsx"() {
9556
+ "use client";
9557
+ init_cn();
9558
+ init_Box();
9559
+ init_fx();
9503
9560
  ConfettiEffect = ({
9504
9561
  trigger,
9505
9562
  duration = 2e3,
@@ -9508,11 +9565,13 @@ var init_ConfettiEffect = __esm({
9508
9565
  }) => {
9509
9566
  const [particles, setParticles] = useState([]);
9510
9567
  const previousTriggerRef = useRef(false);
9568
+ const burstRef = useRef(0);
9511
9569
  useEffect(() => {
9512
9570
  const wasFalse = !previousTriggerRef.current;
9513
9571
  previousTriggerRef.current = trigger;
9514
9572
  if (trigger && wasFalse) {
9515
- const newParticles = createParticles(particleCount);
9573
+ burstRef.current += 1;
9574
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
9516
9575
  setParticles(newParticles);
9517
9576
  const timer = window.setTimeout(() => {
9518
9577
  setParticles([]);
@@ -9560,21 +9619,7 @@ var init_ConfettiEffect = __esm({
9560
9619
  p.id
9561
9620
  );
9562
9621
  }),
9563
- /* @__PURE__ */ jsx("style", { children: `
9564
- @keyframes confetti-burst {
9565
- 0% {
9566
- opacity: 1;
9567
- transform: translate(0, 0) rotate(0deg) scale(1);
9568
- }
9569
- 70% {
9570
- opacity: 1;
9571
- }
9572
- 100% {
9573
- opacity: 0;
9574
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9575
- }
9576
- }
9577
- ` })
9622
+ /* @__PURE__ */ jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
9578
9623
  ]
9579
9624
  }
9580
9625
  );
@@ -13130,7 +13175,7 @@ var init_DrawShape = __esm({
13130
13175
  const cx = p.x + (node.offsetX ?? 0) * tw;
13131
13176
  const cy = p.y + (node.offsetY ?? 0) * tw;
13132
13177
  const rx = (node.radiusX ?? 0) * tw;
13133
- const ry = (node.radiusY ?? rx) * tw;
13178
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
13134
13179
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
13135
13180
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
13136
13181
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -13320,6 +13365,172 @@ var init_DrawTextLayer = __esm({
13320
13365
  };
13321
13366
  }
13322
13367
  });
13368
+
13369
+ // components/game/molecules/DrawFxLayer.tsx
13370
+ function fxPosition(view, dim, ageSec) {
13371
+ const g = view.gravity ?? 0;
13372
+ const fall = 0.5 * g * ageSec * ageSec;
13373
+ {
13374
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
13375
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
13376
+ return {
13377
+ x: base2.x + (view.vx ?? 0) * ageSec,
13378
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
13379
+ };
13380
+ }
13381
+ }
13382
+ function sparkShape(view, pos, i, fade, progress) {
13383
+ const size = view.size ?? 0.5;
13384
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13385
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
13386
+ const r2 = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
13387
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13388
+ return {
13389
+ type: "draw-shape",
13390
+ shape: "ellipse",
13391
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
13392
+ anchor: "center",
13393
+ radiusX: r2,
13394
+ fill: color,
13395
+ blendMode: "lighter",
13396
+ opacity: fade,
13397
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13398
+ };
13399
+ }
13400
+ function proceduralShapes(view, pos, fade, progress) {
13401
+ const size = view.size ?? 0.5;
13402
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13403
+ switch (view.shape ?? "spark") {
13404
+ case "ring": {
13405
+ return [
13406
+ {
13407
+ type: "draw-shape",
13408
+ shape: "ellipse",
13409
+ position: pos,
13410
+ anchor: "center",
13411
+ radiusX: size * easeOut2(progress),
13412
+ stroke: view.color2 ?? color,
13413
+ strokeWidth: 2,
13414
+ blendMode: "lighter",
13415
+ opacity: fade,
13416
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13417
+ }
13418
+ ];
13419
+ }
13420
+ case "puff": {
13421
+ const count = view.count ?? 3;
13422
+ return Array.from({ length: count }, (_, i) => {
13423
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
13424
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
13425
+ return {
13426
+ type: "draw-shape",
13427
+ shape: "ellipse",
13428
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
13429
+ anchor: "center",
13430
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
13431
+ fill: i === 0 && view.color2 ? view.color2 : color,
13432
+ opacity: fade * 0.6,
13433
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13434
+ };
13435
+ });
13436
+ }
13437
+ case "streak": {
13438
+ const count = view.count ?? 5;
13439
+ return Array.from({ length: count }, (_, i) => {
13440
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13441
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
13442
+ const len = size * 0.35;
13443
+ return {
13444
+ type: "draw-shape",
13445
+ shape: "ellipse",
13446
+ position: pos,
13447
+ anchor: "center",
13448
+ offsetX: inner + len / 2,
13449
+ offsetY: 0,
13450
+ radiusX: len / 2,
13451
+ radiusY: size * 0.04,
13452
+ rotate: angle,
13453
+ fill: color,
13454
+ blendMode: "lighter",
13455
+ opacity: fade,
13456
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13457
+ };
13458
+ });
13459
+ }
13460
+ default: {
13461
+ const count = view.count ?? 6;
13462
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
13463
+ }
13464
+ }
13465
+ }
13466
+ function expandFxItem(view, node, epochNowMs, dim) {
13467
+ if (view.space === "screen") return [];
13468
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
13469
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
13470
+ if (progress >= 1) return [];
13471
+ const pos = fxPosition(view, dim, ageMs / 1e3);
13472
+ if (!pos) return [];
13473
+ const out = [];
13474
+ const artItems = node.art?.[view.type]?.["idle"];
13475
+ const sprite = node.sprites?.[view.type];
13476
+ if (Array.isArray(artItems)) {
13477
+ out.push({
13478
+ type: "draw-group",
13479
+ position: pos,
13480
+ opacity: fade,
13481
+ ...view.size !== void 0 ? { scale: view.size } : {},
13482
+ items: artItems
13483
+ });
13484
+ } else if (sprite?.url) {
13485
+ const spriteSize = view.size ?? 0.6;
13486
+ out.push({
13487
+ type: "draw-sprite",
13488
+ position: pos,
13489
+ asset: sprite,
13490
+ anchor: "center",
13491
+ width: spriteSize,
13492
+ height: spriteSize,
13493
+ opacity: fade
13494
+ });
13495
+ } else {
13496
+ out.push(...proceduralShapes(view, pos, fade, progress));
13497
+ }
13498
+ if (view.message) {
13499
+ const text = {
13500
+ type: "draw-text",
13501
+ text: view.message,
13502
+ position: pos,
13503
+ offsetY: -(0.15 + 0.55 * progress),
13504
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
13505
+ opacity: fade
13506
+ };
13507
+ out.push(text);
13508
+ }
13509
+ return out;
13510
+ }
13511
+ function expandFxLayer(node, epochNowMs, dim) {
13512
+ if (!Array.isArray(node.items)) return [];
13513
+ const out = [];
13514
+ for (const item of node.items) {
13515
+ if (!item || typeof item.id !== "string") continue;
13516
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
13517
+ }
13518
+ return out;
13519
+ }
13520
+ function DrawFxLayer(_props) {
13521
+ return null;
13522
+ }
13523
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
13524
+ var init_DrawFxLayer = __esm({
13525
+ "components/game/molecules/DrawFxLayer.tsx"() {
13526
+ "use client";
13527
+ init_fx();
13528
+ DEFAULT_TICK_MS = 500;
13529
+ DEFAULT_TEXT_COLOR = "#ffe066";
13530
+ DEFAULT_SPARK_COLOR = "#ffffff";
13531
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
13532
+ }
13533
+ });
13323
13534
  function paintDrawable(painter, node, dctx) {
13324
13535
  switch (node.type) {
13325
13536
  case "draw-sprite":
@@ -13367,6 +13578,11 @@ function paintDrawable(painter, node, dctx) {
13367
13578
  case "draw-text-layer":
13368
13579
  paintTextLayer(painter, node, dctx);
13369
13580
  break;
13581
+ case "draw-fx-layer": {
13582
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
13583
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
13584
+ break;
13585
+ }
13370
13586
  }
13371
13587
  }
13372
13588
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -13381,6 +13597,7 @@ var init_paintDispatch = __esm({
13381
13597
  init_DrawSpriteLayer();
13382
13598
  init_DrawShapeLayer();
13383
13599
  init_DrawTextLayer();
13600
+ init_DrawFxLayer();
13384
13601
  paint2dLog = createLogger("almadar:ui:drawable-2d");
13385
13602
  warnedUnsupported2d = /* @__PURE__ */ new Set();
13386
13603
  warnUnsupported2d = (kind) => {
@@ -13629,6 +13846,7 @@ function Canvas2D({
13629
13846
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13630
13847
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13631
13848
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13849
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
13632
13850
  return false;
13633
13851
  };
13634
13852
  const animRafRef = useRef(0);
@@ -30391,6 +30609,196 @@ var init_PageTransition = __esm({
30391
30609
  PageTransition.displayName = "PageTransition";
30392
30610
  }
30393
30611
  });
30612
+ function ConfettiBurst({ item, lifeMs }) {
30613
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
30614
+ const left = (item.x ?? 0.5) * 100;
30615
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30616
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
30617
+ const rad = p.angle * Math.PI / 180;
30618
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
30619
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
30620
+ return /* @__PURE__ */ jsx(
30621
+ Box,
30622
+ {
30623
+ className: "absolute rounded-sm",
30624
+ style: {
30625
+ left: (p.left - 50) * 2,
30626
+ top: -10,
30627
+ width: p.size,
30628
+ height: p.size,
30629
+ backgroundColor: item.color ?? p.color,
30630
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
30631
+ opacity: 0,
30632
+ "--confetti-tx": `${tx}px`,
30633
+ "--confetti-ty": `${ty}px`,
30634
+ "--confetti-rotate": `${p.rotation}deg`
30635
+ }
30636
+ },
30637
+ p.id
30638
+ );
30639
+ }) });
30640
+ }
30641
+ function SparkleBurst({ item, lifeMs }) {
30642
+ const count = item.particleCount ?? 8;
30643
+ const scale = item.size ?? 1;
30644
+ const left = (item.x ?? 0.5) * 100;
30645
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30646
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
30647
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
30648
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
30649
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
30650
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
30651
+ return /* @__PURE__ */ jsx(
30652
+ Box,
30653
+ {
30654
+ className: "absolute rounded-full",
30655
+ style: {
30656
+ left: dx,
30657
+ top: dy,
30658
+ width: dot,
30659
+ height: dot,
30660
+ backgroundColor: item.color ?? "gold",
30661
+ opacity: 0,
30662
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
30663
+ }
30664
+ },
30665
+ i
30666
+ );
30667
+ }) });
30668
+ }
30669
+ function OverlayFxNode({ item, tickMs }) {
30670
+ const lifeMs = lifeMsOf(item, tickMs);
30671
+ switch (item.effect ?? "sparkle") {
30672
+ case "confetti":
30673
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
30674
+ case "flash":
30675
+ return /* @__PURE__ */ jsx(
30676
+ Box,
30677
+ {
30678
+ position: "absolute",
30679
+ className: "inset-0",
30680
+ style: {
30681
+ backgroundColor: item.color ?? "#ffffff",
30682
+ opacity: 0,
30683
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
30684
+ }
30685
+ }
30686
+ );
30687
+ case "streak-glow":
30688
+ return /* @__PURE__ */ jsx(
30689
+ Box,
30690
+ {
30691
+ position: "absolute",
30692
+ className: "inset-0",
30693
+ style: {
30694
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
30695
+ opacity: 0,
30696
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
30697
+ }
30698
+ }
30699
+ );
30700
+ case "float-text": {
30701
+ if (!item.message) return null;
30702
+ return /* @__PURE__ */ jsx(
30703
+ Box,
30704
+ {
30705
+ position: "absolute",
30706
+ style: {
30707
+ left: `${(item.x ?? 0.5) * 100}%`,
30708
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
30709
+ color: item.color ?? "var(--color-success)",
30710
+ fontWeight: 700,
30711
+ fontSize: 16 * (item.size ?? 1),
30712
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
30713
+ opacity: 0,
30714
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
30715
+ whiteSpace: "nowrap"
30716
+ },
30717
+ children: item.message
30718
+ }
30719
+ );
30720
+ }
30721
+ case "shake":
30722
+ return null;
30723
+ default:
30724
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
30725
+ }
30726
+ }
30727
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
30728
+ var init_FxOverlay = __esm({
30729
+ "components/core/molecules/FxOverlay.tsx"() {
30730
+ "use client";
30731
+ init_cn();
30732
+ init_Box();
30733
+ init_fx();
30734
+ DEFAULT_TICK_MS2 = 500;
30735
+ FX_OVERLAY_KEYFRAMES = `
30736
+ ${CONFETTI_BURST_KEYFRAMES}
30737
+ @keyframes fx-overlay-flash {
30738
+ 0% { opacity: 0.75; }
30739
+ 100% { opacity: 0; }
30740
+ }
30741
+ @keyframes fx-overlay-glow {
30742
+ 0% { opacity: 0.9; }
30743
+ 60% { opacity: 0.6; }
30744
+ 100% { opacity: 0; }
30745
+ }
30746
+ @keyframes fx-overlay-sparkle {
30747
+ 0% { opacity: 0; transform: scale(0); }
30748
+ 30% { opacity: 1; transform: scale(1); }
30749
+ 100% { opacity: 0; transform: scale(0.3); }
30750
+ }
30751
+ @keyframes fx-overlay-float {
30752
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
30753
+ 15% { opacity: 1; }
30754
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
30755
+ }
30756
+ @keyframes fx-overlay-shake {
30757
+ 0% { transform: translateX(0); }
30758
+ 15% { transform: translateX(-6px); }
30759
+ 30% { transform: translateX(5px); }
30760
+ 45% { transform: translateX(-4px); }
30761
+ 60% { transform: translateX(3px); }
30762
+ 75% { transform: translateX(-2px); }
30763
+ 100% { transform: translateX(0); }
30764
+ }
30765
+ `;
30766
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
30767
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
30768
+ const screenItems = (Array.isArray(items) ? items : []).filter(
30769
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
30770
+ );
30771
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
30772
+ const overlay = /* @__PURE__ */ jsxs(
30773
+ Box,
30774
+ {
30775
+ position: "absolute",
30776
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
30777
+ "aria-hidden": "true",
30778
+ children: [
30779
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
30780
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
30781
+ ]
30782
+ }
30783
+ );
30784
+ if (children !== void 0 && children !== null) {
30785
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
30786
+ /* @__PURE__ */ jsx(
30787
+ Box,
30788
+ {
30789
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
30790
+ children
30791
+ },
30792
+ shakeItem?.id ?? "steady"
30793
+ ),
30794
+ overlay
30795
+ ] });
30796
+ }
30797
+ return overlay;
30798
+ };
30799
+ FxOverlay.displayName = "FxOverlay";
30800
+ }
30801
+ });
30394
30802
  function computePopoverStyle(position, triggerRect, popoverWidth) {
30395
30803
  if (position === "left" || position === "right") {
30396
30804
  return {
@@ -32396,7 +32804,7 @@ var init_MathCanvas = __esm({
32396
32804
  x: mapX((first.x + last.x) / 2),
32397
32805
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
32398
32806
  text: region.label,
32399
- color: "#111827",
32807
+ color,
32400
32808
  fontSize: 11
32401
32809
  });
32402
32810
  }
@@ -32429,14 +32837,14 @@ var init_MathCanvas = __esm({
32429
32837
  const px = mapX(guide.at);
32430
32838
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
32431
32839
  if (guide.label) {
32432
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
32840
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
32433
32841
  }
32434
32842
  } else {
32435
32843
  if (guide.at < yMin || guide.at > yMax) continue;
32436
32844
  const py = mapY(guide.at);
32437
32845
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
32438
32846
  if (guide.label) {
32439
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
32847
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
32440
32848
  }
32441
32849
  }
32442
32850
  }
@@ -32502,7 +32910,7 @@ var init_MathCanvas = __esm({
32502
32910
  x: (x1 + x2) / 2,
32503
32911
  y: xAxisY - peak - 8,
32504
32912
  text: hop.label,
32505
- color: "#111827",
32913
+ color,
32506
32914
  fontSize: 10,
32507
32915
  align: "center"
32508
32916
  });
@@ -32529,7 +32937,7 @@ var init_MathCanvas = __esm({
32529
32937
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
32530
32938
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
32531
32939
  text: angle.label,
32532
- color: "#111827",
32940
+ color,
32533
32941
  fontSize: 11,
32534
32942
  align: "center"
32535
32943
  });
@@ -32547,7 +32955,7 @@ var init_MathCanvas = __esm({
32547
32955
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
32548
32956
  });
32549
32957
  if (p.label) {
32550
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
32958
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
32551
32959
  }
32552
32960
  }
32553
32961
  for (const v of vectors) {
@@ -32558,7 +32966,7 @@ var init_MathCanvas = __esm({
32558
32966
  const y2 = mapY(v.y + v.vy);
32559
32967
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
32560
32968
  if (v.label) {
32561
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
32969
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
32562
32970
  }
32563
32971
  }
32564
32972
  out.push(...shapes);
@@ -49841,6 +50249,7 @@ var init_component_registry_generated = __esm({
49841
50249
  init_DocSidebar();
49842
50250
  init_DocTOC();
49843
50251
  init_DocumentViewer();
50252
+ init_DrawFxLayer();
49844
50253
  init_DrawGroup();
49845
50254
  init_DrawMesh();
49846
50255
  init_DrawShape();
@@ -49871,6 +50280,7 @@ var init_component_registry_generated = __esm({
49871
50280
  init_FormField();
49872
50281
  init_FormSection();
49873
50282
  init_FormSectionHeader();
50283
+ init_FxOverlay();
49874
50284
  init_GameAudioToggle();
49875
50285
  init_GameHud();
49876
50286
  init_GameIcon();
@@ -50111,6 +50521,7 @@ var init_component_registry_generated = __esm({
50111
50521
  "DocSidebar": DocSidebar,
50112
50522
  "DocTOC": DocTOC,
50113
50523
  "DocumentViewer": DocumentViewer,
50524
+ "DrawFxLayer": DrawFxLayer,
50114
50525
  "DrawGroup": DrawGroup,
50115
50526
  "DrawMesh": DrawMesh,
50116
50527
  "DrawShape": DrawShape,
@@ -50141,6 +50552,7 @@ var init_component_registry_generated = __esm({
50141
50552
  "FormField": FormField,
50142
50553
  "FormLayout": FormLayout,
50143
50554
  "FormSectionHeader": FormSectionHeader,
50555
+ "FxOverlay": FxOverlay,
50144
50556
  "GameAudioToggle": GameAudioToggle,
50145
50557
  "GameHud": GameHud,
50146
50558
  "GameIcon": GameIcon,
@@ -1,7 +1,7 @@
1
1
  import { AnimationName, Asset, ScenePos, EventEmit, JsonObject, JsonValue } from '@almadar/core';
2
2
  import React__default from 'react';
3
3
  import * as THREE from 'three';
4
- import { D as DrawableNode } from './paintDispatch-CxdLjHQq.js';
4
+ import { D as DrawableNode } from './paintDispatch-B5n2DTB-.js';
5
5
 
6
6
  /**
7
7
  * Sprite Sheet Animation Types
@@ -1,7 +1,7 @@
1
1
  import { AnimationName, Asset, ScenePos, EventEmit, JsonObject, JsonValue } from '@almadar/core';
2
2
  import React__default from 'react';
3
3
  import * as THREE from 'three';
4
- import { D as DrawableNode } from './paintDispatch-BjZjUbcb.cjs';
4
+ import { D as DrawableNode } from './paintDispatch-DgBctxIq.cjs';
5
5
 
6
6
  /**
7
7
  * Sprite Sheet Animation Types
@@ -1,6 +1,6 @@
1
1
  import { OrbitalSchema, SExpr, Effect, OrbitalVerificationAPI, TraitStateSnapshot, EventPayload, BusEvent, VerificationCheck, BridgeHealth, VerificationSnapshot, VerificationSummary, TransitionTrace, ServerResponseTrace, CheckStatus, AssetLoadStatus } from '@almadar/core';
2
2
  import React__default from 'react';
3
- import { D as DrawableNode } from './paintDispatch-CxdLjHQq.js';
3
+ import { D as DrawableNode } from './paintDispatch-B5n2DTB-.js';
4
4
  import { ClassValue } from 'clsx';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { OrbitalSchema, SExpr, Effect, OrbitalVerificationAPI, TraitStateSnapshot, EventPayload, BusEvent, VerificationCheck, BridgeHealth, VerificationSnapshot, VerificationSummary, TransitionTrace, ServerResponseTrace, CheckStatus, AssetLoadStatus } from '@almadar/core';
2
2
  import React__default from 'react';
3
- import { D as DrawableNode } from './paintDispatch-BjZjUbcb.cjs';
3
+ import { D as DrawableNode } from './paintDispatch-DgBctxIq.cjs';
4
4
  import { ClassValue } from 'clsx';
5
5
 
6
6
  /**