@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.
@@ -6171,27 +6171,61 @@ var init_InfiniteScrollSentinel = __esm({
6171
6171
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
6172
6172
  }
6173
6173
  });
6174
- function createParticles(count) {
6175
- return Array.from({ length: count }, () => {
6176
- particleIdCounter += 1;
6177
- return {
6178
- id: particleIdCounter,
6179
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
6180
- left: 30 + Math.random() * 40,
6181
- delay: Math.random() * 300,
6182
- angle: Math.random() * 360,
6183
- distance: 40 + Math.random() * 80,
6184
- rotation: Math.random() * 720 - 360,
6185
- size: 4 + Math.random() * 6
6186
- };
6187
- });
6174
+
6175
+ // components/core/atoms/fx.ts
6176
+ function resolveFxView(item, presets) {
6177
+ const row = presets?.find((p) => p.type === item.type);
6178
+ if (!row) return item;
6179
+ return {
6180
+ ...item,
6181
+ space: item.space ?? row.space,
6182
+ effect: item.effect ?? row.effect,
6183
+ color: item.color ?? row.color,
6184
+ size: item.size ?? row.size,
6185
+ vx: item.vx ?? row.vx,
6186
+ vy: item.vy ?? row.vy,
6187
+ vz: item.vz ?? row.vz,
6188
+ particleCount: item.particleCount ?? row.particleCount,
6189
+ shape: row.shape,
6190
+ count: row.count,
6191
+ glow: row.glow,
6192
+ gravity: row.gravity,
6193
+ color2: row.color2
6194
+ };
6188
6195
  }
6189
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
6190
- var init_ConfettiEffect = __esm({
6191
- "components/core/atoms/ConfettiEffect.tsx"() {
6192
- "use client";
6193
- init_cn();
6194
- init_Box();
6196
+ function fxLifecycle(item, epochNowMs, tickMs) {
6197
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
6198
+ const lifeMs = maxTtl * tickMs;
6199
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
6200
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
6201
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
6202
+ }
6203
+ function fxHash01(seed, salt) {
6204
+ let h = 2166136261 ^ salt;
6205
+ for (let i = 0; i < seed.length; i++) {
6206
+ h ^= seed.charCodeAt(i);
6207
+ h = Math.imul(h, 16777619);
6208
+ }
6209
+ h ^= h >>> 13;
6210
+ h = Math.imul(h, 1274126177);
6211
+ h ^= h >>> 16;
6212
+ return (h >>> 0) / 4294967296;
6213
+ }
6214
+ function createConfettiParticles(count, seed) {
6215
+ return Array.from({ length: count }, (_, i) => ({
6216
+ id: i,
6217
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
6218
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
6219
+ delay: fxHash01(seed, i * 7 + 3) * 300,
6220
+ angle: fxHash01(seed, i * 7 + 4) * 360,
6221
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
6222
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
6223
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
6224
+ }));
6225
+ }
6226
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
6227
+ var init_fx = __esm({
6228
+ "components/core/atoms/fx.ts"() {
6195
6229
  CONFETTI_COLORS = [
6196
6230
  "var(--color-primary)",
6197
6231
  "var(--color-success)",
@@ -6200,7 +6234,30 @@ var init_ConfettiEffect = __esm({
6200
6234
  "gold",
6201
6235
  "dodgerblue"
6202
6236
  ];
6203
- particleIdCounter = 0;
6237
+ CONFETTI_BURST_KEYFRAMES = `
6238
+ @keyframes confetti-burst {
6239
+ 0% {
6240
+ opacity: 1;
6241
+ transform: translate(0, 0) rotate(0deg) scale(1);
6242
+ }
6243
+ 70% {
6244
+ opacity: 1;
6245
+ }
6246
+ 100% {
6247
+ opacity: 0;
6248
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6249
+ }
6250
+ }
6251
+ `;
6252
+ }
6253
+ });
6254
+ var ConfettiEffect;
6255
+ var init_ConfettiEffect = __esm({
6256
+ "components/core/atoms/ConfettiEffect.tsx"() {
6257
+ "use client";
6258
+ init_cn();
6259
+ init_Box();
6260
+ init_fx();
6204
6261
  ConfettiEffect = ({
6205
6262
  trigger,
6206
6263
  duration = 2e3,
@@ -6209,11 +6266,13 @@ var init_ConfettiEffect = __esm({
6209
6266
  }) => {
6210
6267
  const [particles, setParticles] = useState([]);
6211
6268
  const previousTriggerRef = useRef(false);
6269
+ const burstRef = useRef(0);
6212
6270
  useEffect(() => {
6213
6271
  const wasFalse = !previousTriggerRef.current;
6214
6272
  previousTriggerRef.current = trigger;
6215
6273
  if (trigger && wasFalse) {
6216
- const newParticles = createParticles(particleCount);
6274
+ burstRef.current += 1;
6275
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
6217
6276
  setParticles(newParticles);
6218
6277
  const timer = window.setTimeout(() => {
6219
6278
  setParticles([]);
@@ -6261,21 +6320,7 @@ var init_ConfettiEffect = __esm({
6261
6320
  p.id
6262
6321
  );
6263
6322
  }),
6264
- /* @__PURE__ */ jsx("style", { children: `
6265
- @keyframes confetti-burst {
6266
- 0% {
6267
- opacity: 1;
6268
- transform: translate(0, 0) rotate(0deg) scale(1);
6269
- }
6270
- 70% {
6271
- opacity: 1;
6272
- }
6273
- 100% {
6274
- opacity: 0;
6275
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6276
- }
6277
- }
6278
- ` })
6323
+ /* @__PURE__ */ jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
6279
6324
  ]
6280
6325
  }
6281
6326
  );
@@ -9724,7 +9769,7 @@ var init_DrawShape = __esm({
9724
9769
  const cx = p.x + (node.offsetX ?? 0) * tw;
9725
9770
  const cy = p.y + (node.offsetY ?? 0) * tw;
9726
9771
  const rx = (node.radiusX ?? 0) * tw;
9727
- const ry = (node.radiusY ?? rx) * tw;
9772
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9728
9773
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9729
9774
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9730
9775
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9914,6 +9959,172 @@ var init_DrawTextLayer = __esm({
9914
9959
  };
9915
9960
  }
9916
9961
  });
9962
+
9963
+ // components/game/molecules/DrawFxLayer.tsx
9964
+ function fxPosition(view, dim, ageSec) {
9965
+ const g = view.gravity ?? 0;
9966
+ const fall = 0.5 * g * ageSec * ageSec;
9967
+ {
9968
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
9969
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
9970
+ return {
9971
+ x: base2.x + (view.vx ?? 0) * ageSec,
9972
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
9973
+ };
9974
+ }
9975
+ }
9976
+ function sparkShape(view, pos, i, fade, progress) {
9977
+ const size = view.size ?? 0.5;
9978
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9979
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
9980
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
9981
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9982
+ return {
9983
+ type: "draw-shape",
9984
+ shape: "ellipse",
9985
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
9986
+ anchor: "center",
9987
+ radiusX: r,
9988
+ fill: color,
9989
+ blendMode: "lighter",
9990
+ opacity: fade,
9991
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9992
+ };
9993
+ }
9994
+ function proceduralShapes(view, pos, fade, progress) {
9995
+ const size = view.size ?? 0.5;
9996
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9997
+ switch (view.shape ?? "spark") {
9998
+ case "ring": {
9999
+ return [
10000
+ {
10001
+ type: "draw-shape",
10002
+ shape: "ellipse",
10003
+ position: pos,
10004
+ anchor: "center",
10005
+ radiusX: size * easeOut2(progress),
10006
+ stroke: view.color2 ?? color,
10007
+ strokeWidth: 2,
10008
+ blendMode: "lighter",
10009
+ opacity: fade,
10010
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10011
+ }
10012
+ ];
10013
+ }
10014
+ case "puff": {
10015
+ const count = view.count ?? 3;
10016
+ return Array.from({ length: count }, (_, i) => {
10017
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
10018
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
10019
+ return {
10020
+ type: "draw-shape",
10021
+ shape: "ellipse",
10022
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
10023
+ anchor: "center",
10024
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
10025
+ fill: i === 0 && view.color2 ? view.color2 : color,
10026
+ opacity: fade * 0.6,
10027
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10028
+ };
10029
+ });
10030
+ }
10031
+ case "streak": {
10032
+ const count = view.count ?? 5;
10033
+ return Array.from({ length: count }, (_, i) => {
10034
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10035
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
10036
+ const len = size * 0.35;
10037
+ return {
10038
+ type: "draw-shape",
10039
+ shape: "ellipse",
10040
+ position: pos,
10041
+ anchor: "center",
10042
+ offsetX: inner + len / 2,
10043
+ offsetY: 0,
10044
+ radiusX: len / 2,
10045
+ radiusY: size * 0.04,
10046
+ rotate: angle,
10047
+ fill: color,
10048
+ blendMode: "lighter",
10049
+ opacity: fade,
10050
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10051
+ };
10052
+ });
10053
+ }
10054
+ default: {
10055
+ const count = view.count ?? 6;
10056
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
10057
+ }
10058
+ }
10059
+ }
10060
+ function expandFxItem(view, node, epochNowMs, dim) {
10061
+ if (view.space === "screen") return [];
10062
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
10063
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
10064
+ if (progress >= 1) return [];
10065
+ const pos = fxPosition(view, dim, ageMs / 1e3);
10066
+ if (!pos) return [];
10067
+ const out = [];
10068
+ const artItems = node.art?.[view.type]?.["idle"];
10069
+ const sprite = node.sprites?.[view.type];
10070
+ if (Array.isArray(artItems)) {
10071
+ out.push({
10072
+ type: "draw-group",
10073
+ position: pos,
10074
+ opacity: fade,
10075
+ ...view.size !== void 0 ? { scale: view.size } : {},
10076
+ items: artItems
10077
+ });
10078
+ } else if (sprite?.url) {
10079
+ const spriteSize = view.size ?? 0.6;
10080
+ out.push({
10081
+ type: "draw-sprite",
10082
+ position: pos,
10083
+ asset: sprite,
10084
+ anchor: "center",
10085
+ width: spriteSize,
10086
+ height: spriteSize,
10087
+ opacity: fade
10088
+ });
10089
+ } else {
10090
+ out.push(...proceduralShapes(view, pos, fade, progress));
10091
+ }
10092
+ if (view.message) {
10093
+ const text = {
10094
+ type: "draw-text",
10095
+ text: view.message,
10096
+ position: pos,
10097
+ offsetY: -(0.15 + 0.55 * progress),
10098
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
10099
+ opacity: fade
10100
+ };
10101
+ out.push(text);
10102
+ }
10103
+ return out;
10104
+ }
10105
+ function expandFxLayer(node, epochNowMs, dim) {
10106
+ if (!Array.isArray(node.items)) return [];
10107
+ const out = [];
10108
+ for (const item of node.items) {
10109
+ if (!item || typeof item.id !== "string") continue;
10110
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
10111
+ }
10112
+ return out;
10113
+ }
10114
+ function DrawFxLayer(_props) {
10115
+ return null;
10116
+ }
10117
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
10118
+ var init_DrawFxLayer = __esm({
10119
+ "components/game/molecules/DrawFxLayer.tsx"() {
10120
+ "use client";
10121
+ init_fx();
10122
+ DEFAULT_TICK_MS = 500;
10123
+ DEFAULT_TEXT_COLOR = "#ffe066";
10124
+ DEFAULT_SPARK_COLOR = "#ffffff";
10125
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
10126
+ }
10127
+ });
9917
10128
  function paintDrawable(painter, node, dctx) {
9918
10129
  switch (node.type) {
9919
10130
  case "draw-sprite":
@@ -9961,6 +10172,11 @@ function paintDrawable(painter, node, dctx) {
9961
10172
  case "draw-text-layer":
9962
10173
  paintTextLayer(painter, node, dctx);
9963
10174
  break;
10175
+ case "draw-fx-layer": {
10176
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
10177
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
10178
+ break;
10179
+ }
9964
10180
  }
9965
10181
  }
9966
10182
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -9975,6 +10191,7 @@ var init_paintDispatch = __esm({
9975
10191
  init_DrawSpriteLayer();
9976
10192
  init_DrawShapeLayer();
9977
10193
  init_DrawTextLayer();
10194
+ init_DrawFxLayer();
9978
10195
  paint2dLog = createLogger("almadar:ui:drawable-2d");
9979
10196
  warnedUnsupported2d = /* @__PURE__ */ new Set();
9980
10197
  warnUnsupported2d = (kind) => {
@@ -10223,6 +10440,7 @@ function Canvas2D({
10223
10440
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10224
10441
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10225
10442
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10443
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
10226
10444
  return false;
10227
10445
  };
10228
10446
  const animRafRef = useRef(0);
@@ -27617,6 +27835,196 @@ var init_PageTransition = __esm({
27617
27835
  PageTransition.displayName = "PageTransition";
27618
27836
  }
27619
27837
  });
27838
+ function ConfettiBurst({ item, lifeMs }) {
27839
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
27840
+ const left = (item.x ?? 0.5) * 100;
27841
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27842
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
27843
+ const rad = p.angle * Math.PI / 180;
27844
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
27845
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
27846
+ return /* @__PURE__ */ jsx(
27847
+ Box,
27848
+ {
27849
+ className: "absolute rounded-sm",
27850
+ style: {
27851
+ left: (p.left - 50) * 2,
27852
+ top: -10,
27853
+ width: p.size,
27854
+ height: p.size,
27855
+ backgroundColor: item.color ?? p.color,
27856
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
27857
+ opacity: 0,
27858
+ "--confetti-tx": `${tx}px`,
27859
+ "--confetti-ty": `${ty}px`,
27860
+ "--confetti-rotate": `${p.rotation}deg`
27861
+ }
27862
+ },
27863
+ p.id
27864
+ );
27865
+ }) });
27866
+ }
27867
+ function SparkleBurst({ item, lifeMs }) {
27868
+ const count = item.particleCount ?? 8;
27869
+ const scale = item.size ?? 1;
27870
+ const left = (item.x ?? 0.5) * 100;
27871
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27872
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
27873
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
27874
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
27875
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
27876
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
27877
+ return /* @__PURE__ */ jsx(
27878
+ Box,
27879
+ {
27880
+ className: "absolute rounded-full",
27881
+ style: {
27882
+ left: dx,
27883
+ top: dy,
27884
+ width: dot,
27885
+ height: dot,
27886
+ backgroundColor: item.color ?? "gold",
27887
+ opacity: 0,
27888
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
27889
+ }
27890
+ },
27891
+ i
27892
+ );
27893
+ }) });
27894
+ }
27895
+ function OverlayFxNode({ item, tickMs }) {
27896
+ const lifeMs = lifeMsOf(item, tickMs);
27897
+ switch (item.effect ?? "sparkle") {
27898
+ case "confetti":
27899
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
27900
+ case "flash":
27901
+ return /* @__PURE__ */ jsx(
27902
+ Box,
27903
+ {
27904
+ position: "absolute",
27905
+ className: "inset-0",
27906
+ style: {
27907
+ backgroundColor: item.color ?? "#ffffff",
27908
+ opacity: 0,
27909
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
27910
+ }
27911
+ }
27912
+ );
27913
+ case "streak-glow":
27914
+ return /* @__PURE__ */ jsx(
27915
+ Box,
27916
+ {
27917
+ position: "absolute",
27918
+ className: "inset-0",
27919
+ style: {
27920
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
27921
+ opacity: 0,
27922
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
27923
+ }
27924
+ }
27925
+ );
27926
+ case "float-text": {
27927
+ if (!item.message) return null;
27928
+ return /* @__PURE__ */ jsx(
27929
+ Box,
27930
+ {
27931
+ position: "absolute",
27932
+ style: {
27933
+ left: `${(item.x ?? 0.5) * 100}%`,
27934
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
27935
+ color: item.color ?? "var(--color-success)",
27936
+ fontWeight: 700,
27937
+ fontSize: 16 * (item.size ?? 1),
27938
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
27939
+ opacity: 0,
27940
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
27941
+ whiteSpace: "nowrap"
27942
+ },
27943
+ children: item.message
27944
+ }
27945
+ );
27946
+ }
27947
+ case "shake":
27948
+ return null;
27949
+ default:
27950
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
27951
+ }
27952
+ }
27953
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
27954
+ var init_FxOverlay = __esm({
27955
+ "components/core/molecules/FxOverlay.tsx"() {
27956
+ "use client";
27957
+ init_cn();
27958
+ init_Box();
27959
+ init_fx();
27960
+ DEFAULT_TICK_MS2 = 500;
27961
+ FX_OVERLAY_KEYFRAMES = `
27962
+ ${CONFETTI_BURST_KEYFRAMES}
27963
+ @keyframes fx-overlay-flash {
27964
+ 0% { opacity: 0.75; }
27965
+ 100% { opacity: 0; }
27966
+ }
27967
+ @keyframes fx-overlay-glow {
27968
+ 0% { opacity: 0.9; }
27969
+ 60% { opacity: 0.6; }
27970
+ 100% { opacity: 0; }
27971
+ }
27972
+ @keyframes fx-overlay-sparkle {
27973
+ 0% { opacity: 0; transform: scale(0); }
27974
+ 30% { opacity: 1; transform: scale(1); }
27975
+ 100% { opacity: 0; transform: scale(0.3); }
27976
+ }
27977
+ @keyframes fx-overlay-float {
27978
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
27979
+ 15% { opacity: 1; }
27980
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
27981
+ }
27982
+ @keyframes fx-overlay-shake {
27983
+ 0% { transform: translateX(0); }
27984
+ 15% { transform: translateX(-6px); }
27985
+ 30% { transform: translateX(5px); }
27986
+ 45% { transform: translateX(-4px); }
27987
+ 60% { transform: translateX(3px); }
27988
+ 75% { transform: translateX(-2px); }
27989
+ 100% { transform: translateX(0); }
27990
+ }
27991
+ `;
27992
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
27993
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
27994
+ const screenItems = (Array.isArray(items) ? items : []).filter(
27995
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
27996
+ );
27997
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
27998
+ const overlay = /* @__PURE__ */ jsxs(
27999
+ Box,
28000
+ {
28001
+ position: "absolute",
28002
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28003
+ "aria-hidden": "true",
28004
+ children: [
28005
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28006
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28007
+ ]
28008
+ }
28009
+ );
28010
+ if (children !== void 0 && children !== null) {
28011
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
28012
+ /* @__PURE__ */ jsx(
28013
+ Box,
28014
+ {
28015
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28016
+ children
28017
+ },
28018
+ shakeItem?.id ?? "steady"
28019
+ ),
28020
+ overlay
28021
+ ] });
28022
+ }
28023
+ return overlay;
28024
+ };
28025
+ FxOverlay.displayName = "FxOverlay";
28026
+ }
28027
+ });
27620
28028
  function computePopoverStyle(position, triggerRect, popoverWidth) {
27621
28029
  if (position === "left" || position === "right") {
27622
28030
  return {
@@ -29622,7 +30030,7 @@ var init_MathCanvas = __esm({
29622
30030
  x: mapX((first.x + last.x) / 2),
29623
30031
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
29624
30032
  text: region.label,
29625
- color: "#111827",
30033
+ color,
29626
30034
  fontSize: 11
29627
30035
  });
29628
30036
  }
@@ -29655,14 +30063,14 @@ var init_MathCanvas = __esm({
29655
30063
  const px = mapX(guide.at);
29656
30064
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
29657
30065
  if (guide.label) {
29658
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30066
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
29659
30067
  }
29660
30068
  } else {
29661
30069
  if (guide.at < yMin || guide.at > yMax) continue;
29662
30070
  const py = mapY(guide.at);
29663
30071
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
29664
30072
  if (guide.label) {
29665
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30073
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
29666
30074
  }
29667
30075
  }
29668
30076
  }
@@ -29728,7 +30136,7 @@ var init_MathCanvas = __esm({
29728
30136
  x: (x1 + x2) / 2,
29729
30137
  y: xAxisY - peak - 8,
29730
30138
  text: hop.label,
29731
- color: "#111827",
30139
+ color,
29732
30140
  fontSize: 10,
29733
30141
  align: "center"
29734
30142
  });
@@ -29755,7 +30163,7 @@ var init_MathCanvas = __esm({
29755
30163
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
29756
30164
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
29757
30165
  text: angle.label,
29758
- color: "#111827",
30166
+ color,
29759
30167
  fontSize: 11,
29760
30168
  align: "center"
29761
30169
  });
@@ -29773,7 +30181,7 @@ var init_MathCanvas = __esm({
29773
30181
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29774
30182
  });
29775
30183
  if (p.label) {
29776
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30184
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
29777
30185
  }
29778
30186
  }
29779
30187
  for (const v of vectors) {
@@ -29784,7 +30192,7 @@ var init_MathCanvas = __esm({
29784
30192
  const y2 = mapY(v.y + v.vy);
29785
30193
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
29786
30194
  if (v.label) {
29787
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30195
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
29788
30196
  }
29789
30197
  }
29790
30198
  out.push(...shapes);
@@ -47338,6 +47746,7 @@ var init_component_registry_generated = __esm({
47338
47746
  init_DocSidebar();
47339
47747
  init_DocTOC();
47340
47748
  init_DocumentViewer();
47749
+ init_DrawFxLayer();
47341
47750
  init_DrawGroup();
47342
47751
  init_DrawMesh();
47343
47752
  init_DrawShape();
@@ -47368,6 +47777,7 @@ var init_component_registry_generated = __esm({
47368
47777
  init_FormField();
47369
47778
  init_FormSection();
47370
47779
  init_FormSectionHeader();
47780
+ init_FxOverlay();
47371
47781
  init_GameAudioToggle();
47372
47782
  init_GameHud();
47373
47783
  init_GameIcon();
@@ -47608,6 +48018,7 @@ var init_component_registry_generated = __esm({
47608
48018
  "DocSidebar": DocSidebar,
47609
48019
  "DocTOC": DocTOC,
47610
48020
  "DocumentViewer": DocumentViewer,
48021
+ "DrawFxLayer": DrawFxLayer,
47611
48022
  "DrawGroup": DrawGroup,
47612
48023
  "DrawMesh": DrawMesh,
47613
48024
  "DrawShape": DrawShape,
@@ -47638,6 +48049,7 @@ var init_component_registry_generated = __esm({
47638
48049
  "FormField": FormField,
47639
48050
  "FormLayout": FormLayout,
47640
48051
  "FormSectionHeader": FormSectionHeader,
48052
+ "FxOverlay": FxOverlay,
47641
48053
  "GameAudioToggle": GameAudioToggle,
47642
48054
  "GameHud": GameHud,
47643
48055
  "GameIcon": GameIcon,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.152.0",
3
+ "version": "5.153.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.62.0",
121
+ "@almadar/core": "^10.63.0",
122
122
  "@almadar/evaluator": "^2.41.0",
123
123
  "@almadar/logger": "^1.11.0",
124
124
  "@almadar/runtime": "^6.55.0",
125
- "@almadar/std": "^16.178.0",
125
+ "@almadar/std": "^16.179.0",
126
126
  "@almadar/syntax": "^1.15.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",