@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.
@@ -6245,27 +6245,61 @@ var init_InfiniteScrollSentinel = __esm({
6245
6245
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
6246
6246
  }
6247
6247
  });
6248
- function createParticles(count) {
6249
- return Array.from({ length: count }, () => {
6250
- particleIdCounter += 1;
6251
- return {
6252
- id: particleIdCounter,
6253
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
6254
- left: 30 + Math.random() * 40,
6255
- delay: Math.random() * 300,
6256
- angle: Math.random() * 360,
6257
- distance: 40 + Math.random() * 80,
6258
- rotation: Math.random() * 720 - 360,
6259
- size: 4 + Math.random() * 6
6260
- };
6261
- });
6248
+
6249
+ // components/core/atoms/fx.ts
6250
+ function resolveFxView(item, presets) {
6251
+ const row = presets?.find((p) => p.type === item.type);
6252
+ if (!row) return item;
6253
+ return {
6254
+ ...item,
6255
+ space: item.space ?? row.space,
6256
+ effect: item.effect ?? row.effect,
6257
+ color: item.color ?? row.color,
6258
+ size: item.size ?? row.size,
6259
+ vx: item.vx ?? row.vx,
6260
+ vy: item.vy ?? row.vy,
6261
+ vz: item.vz ?? row.vz,
6262
+ particleCount: item.particleCount ?? row.particleCount,
6263
+ shape: row.shape,
6264
+ count: row.count,
6265
+ glow: row.glow,
6266
+ gravity: row.gravity,
6267
+ color2: row.color2
6268
+ };
6262
6269
  }
6263
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
6264
- var init_ConfettiEffect = __esm({
6265
- "components/core/atoms/ConfettiEffect.tsx"() {
6266
- "use client";
6267
- init_cn();
6268
- init_Box();
6270
+ function fxLifecycle(item, epochNowMs, tickMs) {
6271
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
6272
+ const lifeMs = maxTtl * tickMs;
6273
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
6274
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
6275
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
6276
+ }
6277
+ function fxHash01(seed, salt) {
6278
+ let h = 2166136261 ^ salt;
6279
+ for (let i = 0; i < seed.length; i++) {
6280
+ h ^= seed.charCodeAt(i);
6281
+ h = Math.imul(h, 16777619);
6282
+ }
6283
+ h ^= h >>> 13;
6284
+ h = Math.imul(h, 1274126177);
6285
+ h ^= h >>> 16;
6286
+ return (h >>> 0) / 4294967296;
6287
+ }
6288
+ function createConfettiParticles(count, seed) {
6289
+ return Array.from({ length: count }, (_, i) => ({
6290
+ id: i,
6291
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
6292
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
6293
+ delay: fxHash01(seed, i * 7 + 3) * 300,
6294
+ angle: fxHash01(seed, i * 7 + 4) * 360,
6295
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
6296
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
6297
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
6298
+ }));
6299
+ }
6300
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
6301
+ var init_fx = __esm({
6302
+ "components/core/atoms/fx.ts"() {
6269
6303
  CONFETTI_COLORS = [
6270
6304
  "var(--color-primary)",
6271
6305
  "var(--color-success)",
@@ -6274,7 +6308,30 @@ var init_ConfettiEffect = __esm({
6274
6308
  "gold",
6275
6309
  "dodgerblue"
6276
6310
  ];
6277
- particleIdCounter = 0;
6311
+ CONFETTI_BURST_KEYFRAMES = `
6312
+ @keyframes confetti-burst {
6313
+ 0% {
6314
+ opacity: 1;
6315
+ transform: translate(0, 0) rotate(0deg) scale(1);
6316
+ }
6317
+ 70% {
6318
+ opacity: 1;
6319
+ }
6320
+ 100% {
6321
+ opacity: 0;
6322
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6323
+ }
6324
+ }
6325
+ `;
6326
+ }
6327
+ });
6328
+ var ConfettiEffect;
6329
+ var init_ConfettiEffect = __esm({
6330
+ "components/core/atoms/ConfettiEffect.tsx"() {
6331
+ "use client";
6332
+ init_cn();
6333
+ init_Box();
6334
+ init_fx();
6278
6335
  ConfettiEffect = ({
6279
6336
  trigger,
6280
6337
  duration = 2e3,
@@ -6283,11 +6340,13 @@ var init_ConfettiEffect = __esm({
6283
6340
  }) => {
6284
6341
  const [particles, setParticles] = React85.useState([]);
6285
6342
  const previousTriggerRef = React85.useRef(false);
6343
+ const burstRef = React85.useRef(0);
6286
6344
  React85.useEffect(() => {
6287
6345
  const wasFalse = !previousTriggerRef.current;
6288
6346
  previousTriggerRef.current = trigger;
6289
6347
  if (trigger && wasFalse) {
6290
- const newParticles = createParticles(particleCount);
6348
+ burstRef.current += 1;
6349
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
6291
6350
  setParticles(newParticles);
6292
6351
  const timer = window.setTimeout(() => {
6293
6352
  setParticles([]);
@@ -6335,21 +6394,7 @@ var init_ConfettiEffect = __esm({
6335
6394
  p.id
6336
6395
  );
6337
6396
  }),
6338
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
6339
- @keyframes confetti-burst {
6340
- 0% {
6341
- opacity: 1;
6342
- transform: translate(0, 0) rotate(0deg) scale(1);
6343
- }
6344
- 70% {
6345
- opacity: 1;
6346
- }
6347
- 100% {
6348
- opacity: 0;
6349
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6350
- }
6351
- }
6352
- ` })
6397
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
6353
6398
  ]
6354
6399
  }
6355
6400
  );
@@ -9798,7 +9843,7 @@ var init_DrawShape = __esm({
9798
9843
  const cx = p.x + (node.offsetX ?? 0) * tw;
9799
9844
  const cy = p.y + (node.offsetY ?? 0) * tw;
9800
9845
  const rx = (node.radiusX ?? 0) * tw;
9801
- const ry = (node.radiusY ?? rx) * tw;
9846
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9802
9847
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9803
9848
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9804
9849
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9988,6 +10033,172 @@ var init_DrawTextLayer = __esm({
9988
10033
  };
9989
10034
  }
9990
10035
  });
10036
+
10037
+ // components/game/molecules/DrawFxLayer.tsx
10038
+ function fxPosition(view, dim, ageSec) {
10039
+ const g = view.gravity ?? 0;
10040
+ const fall = 0.5 * g * ageSec * ageSec;
10041
+ {
10042
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
10043
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
10044
+ return {
10045
+ x: base2.x + (view.vx ?? 0) * ageSec,
10046
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
10047
+ };
10048
+ }
10049
+ }
10050
+ function sparkShape(view, pos, i, fade, progress) {
10051
+ const size = view.size ?? 0.5;
10052
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10053
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
10054
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
10055
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
10056
+ return {
10057
+ type: "draw-shape",
10058
+ shape: "ellipse",
10059
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
10060
+ anchor: "center",
10061
+ radiusX: r,
10062
+ fill: color,
10063
+ blendMode: "lighter",
10064
+ opacity: fade,
10065
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10066
+ };
10067
+ }
10068
+ function proceduralShapes(view, pos, fade, progress) {
10069
+ const size = view.size ?? 0.5;
10070
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
10071
+ switch (view.shape ?? "spark") {
10072
+ case "ring": {
10073
+ return [
10074
+ {
10075
+ type: "draw-shape",
10076
+ shape: "ellipse",
10077
+ position: pos,
10078
+ anchor: "center",
10079
+ radiusX: size * easeOut2(progress),
10080
+ stroke: view.color2 ?? color,
10081
+ strokeWidth: 2,
10082
+ blendMode: "lighter",
10083
+ opacity: fade,
10084
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10085
+ }
10086
+ ];
10087
+ }
10088
+ case "puff": {
10089
+ const count = view.count ?? 3;
10090
+ return Array.from({ length: count }, (_, i) => {
10091
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
10092
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
10093
+ return {
10094
+ type: "draw-shape",
10095
+ shape: "ellipse",
10096
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
10097
+ anchor: "center",
10098
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
10099
+ fill: i === 0 && view.color2 ? view.color2 : color,
10100
+ opacity: fade * 0.6,
10101
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10102
+ };
10103
+ });
10104
+ }
10105
+ case "streak": {
10106
+ const count = view.count ?? 5;
10107
+ return Array.from({ length: count }, (_, i) => {
10108
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10109
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
10110
+ const len = size * 0.35;
10111
+ return {
10112
+ type: "draw-shape",
10113
+ shape: "ellipse",
10114
+ position: pos,
10115
+ anchor: "center",
10116
+ offsetX: inner + len / 2,
10117
+ offsetY: 0,
10118
+ radiusX: len / 2,
10119
+ radiusY: size * 0.04,
10120
+ rotate: angle,
10121
+ fill: color,
10122
+ blendMode: "lighter",
10123
+ opacity: fade,
10124
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10125
+ };
10126
+ });
10127
+ }
10128
+ default: {
10129
+ const count = view.count ?? 6;
10130
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
10131
+ }
10132
+ }
10133
+ }
10134
+ function expandFxItem(view, node, epochNowMs, dim) {
10135
+ if (view.space === "screen") return [];
10136
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
10137
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
10138
+ if (progress >= 1) return [];
10139
+ const pos = fxPosition(view, dim, ageMs / 1e3);
10140
+ if (!pos) return [];
10141
+ const out = [];
10142
+ const artItems = node.art?.[view.type]?.["idle"];
10143
+ const sprite = node.sprites?.[view.type];
10144
+ if (Array.isArray(artItems)) {
10145
+ out.push({
10146
+ type: "draw-group",
10147
+ position: pos,
10148
+ opacity: fade,
10149
+ ...view.size !== void 0 ? { scale: view.size } : {},
10150
+ items: artItems
10151
+ });
10152
+ } else if (sprite?.url) {
10153
+ const spriteSize = view.size ?? 0.6;
10154
+ out.push({
10155
+ type: "draw-sprite",
10156
+ position: pos,
10157
+ asset: sprite,
10158
+ anchor: "center",
10159
+ width: spriteSize,
10160
+ height: spriteSize,
10161
+ opacity: fade
10162
+ });
10163
+ } else {
10164
+ out.push(...proceduralShapes(view, pos, fade, progress));
10165
+ }
10166
+ if (view.message) {
10167
+ const text = {
10168
+ type: "draw-text",
10169
+ text: view.message,
10170
+ position: pos,
10171
+ offsetY: -(0.15 + 0.55 * progress),
10172
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
10173
+ opacity: fade
10174
+ };
10175
+ out.push(text);
10176
+ }
10177
+ return out;
10178
+ }
10179
+ function expandFxLayer(node, epochNowMs, dim) {
10180
+ if (!Array.isArray(node.items)) return [];
10181
+ const out = [];
10182
+ for (const item of node.items) {
10183
+ if (!item || typeof item.id !== "string") continue;
10184
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
10185
+ }
10186
+ return out;
10187
+ }
10188
+ function DrawFxLayer(_props) {
10189
+ return null;
10190
+ }
10191
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
10192
+ var init_DrawFxLayer = __esm({
10193
+ "components/game/molecules/DrawFxLayer.tsx"() {
10194
+ "use client";
10195
+ init_fx();
10196
+ DEFAULT_TICK_MS = 500;
10197
+ DEFAULT_TEXT_COLOR = "#ffe066";
10198
+ DEFAULT_SPARK_COLOR = "#ffffff";
10199
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
10200
+ }
10201
+ });
9991
10202
  function paintDrawable(painter, node, dctx) {
9992
10203
  switch (node.type) {
9993
10204
  case "draw-sprite":
@@ -10035,6 +10246,11 @@ function paintDrawable(painter, node, dctx) {
10035
10246
  case "draw-text-layer":
10036
10247
  paintTextLayer(painter, node, dctx);
10037
10248
  break;
10249
+ case "draw-fx-layer": {
10250
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
10251
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
10252
+ break;
10253
+ }
10038
10254
  }
10039
10255
  }
10040
10256
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -10049,6 +10265,7 @@ var init_paintDispatch = __esm({
10049
10265
  init_DrawSpriteLayer();
10050
10266
  init_DrawShapeLayer();
10051
10267
  init_DrawTextLayer();
10268
+ init_DrawFxLayer();
10052
10269
  paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
10053
10270
  warnedUnsupported2d = /* @__PURE__ */ new Set();
10054
10271
  warnUnsupported2d = (kind) => {
@@ -10297,6 +10514,7 @@ function Canvas2D({
10297
10514
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10298
10515
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10299
10516
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10517
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
10300
10518
  return false;
10301
10519
  };
10302
10520
  const animRafRef = React85.useRef(0);
@@ -27691,6 +27909,196 @@ var init_PageTransition = __esm({
27691
27909
  PageTransition.displayName = "PageTransition";
27692
27910
  }
27693
27911
  });
27912
+ function ConfettiBurst({ item, lifeMs }) {
27913
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
27914
+ const left = (item.x ?? 0.5) * 100;
27915
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27916
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
27917
+ const rad = p.angle * Math.PI / 180;
27918
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
27919
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
27920
+ return /* @__PURE__ */ jsxRuntime.jsx(
27921
+ Box,
27922
+ {
27923
+ className: "absolute rounded-sm",
27924
+ style: {
27925
+ left: (p.left - 50) * 2,
27926
+ top: -10,
27927
+ width: p.size,
27928
+ height: p.size,
27929
+ backgroundColor: item.color ?? p.color,
27930
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
27931
+ opacity: 0,
27932
+ "--confetti-tx": `${tx}px`,
27933
+ "--confetti-ty": `${ty}px`,
27934
+ "--confetti-rotate": `${p.rotation}deg`
27935
+ }
27936
+ },
27937
+ p.id
27938
+ );
27939
+ }) });
27940
+ }
27941
+ function SparkleBurst({ item, lifeMs }) {
27942
+ const count = item.particleCount ?? 8;
27943
+ const scale = item.size ?? 1;
27944
+ const left = (item.x ?? 0.5) * 100;
27945
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27946
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
27947
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
27948
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
27949
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
27950
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
27951
+ return /* @__PURE__ */ jsxRuntime.jsx(
27952
+ Box,
27953
+ {
27954
+ className: "absolute rounded-full",
27955
+ style: {
27956
+ left: dx,
27957
+ top: dy,
27958
+ width: dot,
27959
+ height: dot,
27960
+ backgroundColor: item.color ?? "gold",
27961
+ opacity: 0,
27962
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
27963
+ }
27964
+ },
27965
+ i
27966
+ );
27967
+ }) });
27968
+ }
27969
+ function OverlayFxNode({ item, tickMs }) {
27970
+ const lifeMs = lifeMsOf(item, tickMs);
27971
+ switch (item.effect ?? "sparkle") {
27972
+ case "confetti":
27973
+ return /* @__PURE__ */ jsxRuntime.jsx(ConfettiBurst, { item, lifeMs });
27974
+ case "flash":
27975
+ return /* @__PURE__ */ jsxRuntime.jsx(
27976
+ Box,
27977
+ {
27978
+ position: "absolute",
27979
+ className: "inset-0",
27980
+ style: {
27981
+ backgroundColor: item.color ?? "#ffffff",
27982
+ opacity: 0,
27983
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
27984
+ }
27985
+ }
27986
+ );
27987
+ case "streak-glow":
27988
+ return /* @__PURE__ */ jsxRuntime.jsx(
27989
+ Box,
27990
+ {
27991
+ position: "absolute",
27992
+ className: "inset-0",
27993
+ style: {
27994
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
27995
+ opacity: 0,
27996
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
27997
+ }
27998
+ }
27999
+ );
28000
+ case "float-text": {
28001
+ if (!item.message) return null;
28002
+ return /* @__PURE__ */ jsxRuntime.jsx(
28003
+ Box,
28004
+ {
28005
+ position: "absolute",
28006
+ style: {
28007
+ left: `${(item.x ?? 0.5) * 100}%`,
28008
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
28009
+ color: item.color ?? "var(--color-success)",
28010
+ fontWeight: 700,
28011
+ fontSize: 16 * (item.size ?? 1),
28012
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
28013
+ opacity: 0,
28014
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
28015
+ whiteSpace: "nowrap"
28016
+ },
28017
+ children: item.message
28018
+ }
28019
+ );
28020
+ }
28021
+ case "shake":
28022
+ return null;
28023
+ default:
28024
+ return /* @__PURE__ */ jsxRuntime.jsx(SparkleBurst, { item, lifeMs });
28025
+ }
28026
+ }
28027
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
28028
+ var init_FxOverlay = __esm({
28029
+ "components/core/molecules/FxOverlay.tsx"() {
28030
+ "use client";
28031
+ init_cn();
28032
+ init_Box();
28033
+ init_fx();
28034
+ DEFAULT_TICK_MS2 = 500;
28035
+ FX_OVERLAY_KEYFRAMES = `
28036
+ ${CONFETTI_BURST_KEYFRAMES}
28037
+ @keyframes fx-overlay-flash {
28038
+ 0% { opacity: 0.75; }
28039
+ 100% { opacity: 0; }
28040
+ }
28041
+ @keyframes fx-overlay-glow {
28042
+ 0% { opacity: 0.9; }
28043
+ 60% { opacity: 0.6; }
28044
+ 100% { opacity: 0; }
28045
+ }
28046
+ @keyframes fx-overlay-sparkle {
28047
+ 0% { opacity: 0; transform: scale(0); }
28048
+ 30% { opacity: 1; transform: scale(1); }
28049
+ 100% { opacity: 0; transform: scale(0.3); }
28050
+ }
28051
+ @keyframes fx-overlay-float {
28052
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
28053
+ 15% { opacity: 1; }
28054
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
28055
+ }
28056
+ @keyframes fx-overlay-shake {
28057
+ 0% { transform: translateX(0); }
28058
+ 15% { transform: translateX(-6px); }
28059
+ 30% { transform: translateX(5px); }
28060
+ 45% { transform: translateX(-4px); }
28061
+ 60% { transform: translateX(3px); }
28062
+ 75% { transform: translateX(-2px); }
28063
+ 100% { transform: translateX(0); }
28064
+ }
28065
+ `;
28066
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28067
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28068
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28069
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28070
+ );
28071
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28072
+ const overlay = /* @__PURE__ */ jsxRuntime.jsxs(
28073
+ Box,
28074
+ {
28075
+ position: "absolute",
28076
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28077
+ "aria-hidden": "true",
28078
+ children: [
28079
+ screenItems.map((it) => /* @__PURE__ */ jsxRuntime.jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28080
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28081
+ ]
28082
+ }
28083
+ );
28084
+ if (children !== void 0 && children !== null) {
28085
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { position: "relative", className, children: [
28086
+ /* @__PURE__ */ jsxRuntime.jsx(
28087
+ Box,
28088
+ {
28089
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28090
+ children
28091
+ },
28092
+ shakeItem?.id ?? "steady"
28093
+ ),
28094
+ overlay
28095
+ ] });
28096
+ }
28097
+ return overlay;
28098
+ };
28099
+ FxOverlay.displayName = "FxOverlay";
28100
+ }
28101
+ });
27694
28102
  function computePopoverStyle(position, triggerRect, popoverWidth) {
27695
28103
  if (position === "left" || position === "right") {
27696
28104
  return {
@@ -29696,7 +30104,7 @@ var init_MathCanvas = __esm({
29696
30104
  x: mapX((first.x + last.x) / 2),
29697
30105
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
29698
30106
  text: region.label,
29699
- color: "#111827",
30107
+ color,
29700
30108
  fontSize: 11
29701
30109
  });
29702
30110
  }
@@ -29729,14 +30137,14 @@ var init_MathCanvas = __esm({
29729
30137
  const px = mapX(guide.at);
29730
30138
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
29731
30139
  if (guide.label) {
29732
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30140
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
29733
30141
  }
29734
30142
  } else {
29735
30143
  if (guide.at < yMin || guide.at > yMax) continue;
29736
30144
  const py = mapY(guide.at);
29737
30145
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
29738
30146
  if (guide.label) {
29739
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30147
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
29740
30148
  }
29741
30149
  }
29742
30150
  }
@@ -29802,7 +30210,7 @@ var init_MathCanvas = __esm({
29802
30210
  x: (x1 + x2) / 2,
29803
30211
  y: xAxisY - peak - 8,
29804
30212
  text: hop.label,
29805
- color: "#111827",
30213
+ color,
29806
30214
  fontSize: 10,
29807
30215
  align: "center"
29808
30216
  });
@@ -29829,7 +30237,7 @@ var init_MathCanvas = __esm({
29829
30237
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
29830
30238
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
29831
30239
  text: angle.label,
29832
- color: "#111827",
30240
+ color,
29833
30241
  fontSize: 11,
29834
30242
  align: "center"
29835
30243
  });
@@ -29847,7 +30255,7 @@ var init_MathCanvas = __esm({
29847
30255
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29848
30256
  });
29849
30257
  if (p.label) {
29850
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30258
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
29851
30259
  }
29852
30260
  }
29853
30261
  for (const v of vectors) {
@@ -29858,7 +30266,7 @@ var init_MathCanvas = __esm({
29858
30266
  const y2 = mapY(v.y + v.vy);
29859
30267
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
29860
30268
  if (v.label) {
29861
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30269
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
29862
30270
  }
29863
30271
  }
29864
30272
  out.push(...shapes);
@@ -47412,6 +47820,7 @@ var init_component_registry_generated = __esm({
47412
47820
  init_DocSidebar();
47413
47821
  init_DocTOC();
47414
47822
  init_DocumentViewer();
47823
+ init_DrawFxLayer();
47415
47824
  init_DrawGroup();
47416
47825
  init_DrawMesh();
47417
47826
  init_DrawShape();
@@ -47442,6 +47851,7 @@ var init_component_registry_generated = __esm({
47442
47851
  init_FormField();
47443
47852
  init_FormSection();
47444
47853
  init_FormSectionHeader();
47854
+ init_FxOverlay();
47445
47855
  init_GameAudioToggle();
47446
47856
  init_GameHud();
47447
47857
  init_GameIcon();
@@ -47682,6 +48092,7 @@ var init_component_registry_generated = __esm({
47682
48092
  "DocSidebar": DocSidebar,
47683
48093
  "DocTOC": DocTOC,
47684
48094
  "DocumentViewer": DocumentViewer,
48095
+ "DrawFxLayer": DrawFxLayer,
47685
48096
  "DrawGroup": DrawGroup,
47686
48097
  "DrawMesh": DrawMesh,
47687
48098
  "DrawShape": DrawShape,
@@ -47712,6 +48123,7 @@ var init_component_registry_generated = __esm({
47712
48123
  "FormField": FormField,
47713
48124
  "FormLayout": FormLayout,
47714
48125
  "FormSectionHeader": FormSectionHeader,
48126
+ "FxOverlay": FxOverlay,
47715
48127
  "GameAudioToggle": GameAudioToggle,
47716
48128
  "GameHud": GameHud,
47717
48129
  "GameIcon": GameIcon,