fancy-ui-react 0.3.0 → 0.4.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.
Files changed (86) hide show
  1. package/README.md +52 -19
  2. package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
  3. package/dist/components/animated-beam/AnimatedBeam.js +293 -118
  4. package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
  5. package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
  6. package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
  7. package/dist/components/bento-grid/BentoFrame.js +28 -0
  8. package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
  9. package/dist/components/bento-grid/BentoGrid.js +71 -2
  10. package/dist/components/bento-grid/BentoGridCard.js +34 -26
  11. package/dist/components/bento-grid/BentoGridItem.js +10 -10
  12. package/dist/components/border-beam/BorderBeam.d.ts +55 -10
  13. package/dist/components/border-beam/BorderBeam.js +93 -16
  14. package/dist/components/compare/Compare.d.ts +32 -8
  15. package/dist/components/compare/Compare.js +213 -95
  16. package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
  17. package/dist/components/container-scroll/ContainerScroll.js +88 -49
  18. package/dist/components/dock/Dock.d.ts +10 -7
  19. package/dist/components/dock/Dock.js +76 -31
  20. package/dist/components/dock/DockIcon.d.ts +14 -3
  21. package/dist/components/dock/DockIcon.js +23 -19
  22. package/dist/components/dock/DockSeparator.d.ts +4 -1
  23. package/dist/components/dock/DockSeparator.js +5 -2
  24. package/dist/components/dock/types.d.ts +13 -6
  25. package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
  26. package/dist/components/editorial-engine/engine.js +1 -14
  27. package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
  28. package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
  29. package/dist/components/flip-card/FlipCard.d.ts +42 -1
  30. package/dist/components/flip-card/FlipCard.js +178 -25
  31. package/dist/components/flip-card/index.d.ts +1 -1
  32. package/dist/components/fluid-cursor/FluidCursor.js +5 -4
  33. package/dist/components/fluid-cursor/dev.d.ts +1 -0
  34. package/dist/components/fluid-cursor/dev.js +8 -0
  35. package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
  36. package/dist/components/fluid-cursor/fluid-shared.js +0 -2
  37. package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
  38. package/dist/components/glow-border/GlowBorder.d.ts +41 -1
  39. package/dist/components/glow-border/GlowBorder.js +96 -16
  40. package/dist/components/glow-border/index.d.ts +2 -2
  41. package/dist/components/gradient-button/GradientButton.d.ts +10 -5
  42. package/dist/components/gradient-button/GradientButton.js +25 -18
  43. package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
  44. package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
  45. package/dist/components/image-trail-cursor/trail-variants.js +331 -8
  46. package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
  47. package/dist/components/marquee/Marquee.d.ts +7 -3
  48. package/dist/components/marquee/Marquee.js +134 -2
  49. package/dist/components/marquee/ReviewCard.d.ts +2 -1
  50. package/dist/components/marquee/ReviewCard.js +20 -9
  51. package/dist/components/meteors/Meteors.d.ts +32 -10
  52. package/dist/components/meteors/Meteors.js +53 -20
  53. package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
  54. package/dist/components/neon-border/NeonBorder.d.ts +24 -5
  55. package/dist/components/neon-border/NeonBorder.js +37 -7
  56. package/dist/components/pagination/Pagination.js +220 -82
  57. package/dist/components/rainbow-button/RainbowButton.js +13 -5
  58. package/dist/components/ripple-button/RippleButton.d.ts +20 -1
  59. package/dist/components/ripple-button/RippleButton.js +44 -21
  60. package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
  61. package/dist/components/shimmer-button/ShimmerButton.js +17 -19
  62. package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
  63. package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
  64. package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
  65. package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
  66. package/dist/components/stepper/Step.js +32 -19
  67. package/dist/components/stepper/Stepper.js +18 -2
  68. package/dist/components/stepper/types.d.ts +13 -0
  69. package/dist/components/timeline/Timeline.d.ts +8 -1
  70. package/dist/components/timeline/Timeline.js +165 -76
  71. package/dist/index.js +2 -1
  72. package/dist/internals/markdown.d.ts +5 -0
  73. package/dist/internals/motion/haptics.d.ts +0 -4
  74. package/dist/internals/motion/presence-core.d.ts +59 -0
  75. package/dist/internals/motion/presence-core.js +162 -0
  76. package/dist/internals/motion/presence.d.ts +2 -4
  77. package/dist/internals/motion/presence.js +1 -156
  78. package/dist/internals/motion/raf.d.ts +1 -1
  79. package/dist/internals/motion/stagger.d.ts +3 -4
  80. package/dist/internals/motion/tokens.js +4 -0
  81. package/dist/internals/motion/transitions.d.ts +17 -17
  82. package/dist/internals/motion/types.d.ts +6 -7
  83. package/dist/internals/relative-time.d.ts +1 -1
  84. package/dist/styles.css +3470 -203
  85. package/package.json +18 -2
  86. package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
@@ -2,7 +2,6 @@
2
2
  import { SDR_CORE_SCALE, SDR_KNEE_START, SDR_SAT_BOOST } from "./fireworks-shared.js";
3
3
  import { STRETCH } from "./fireworks-shared.js";
4
4
  import { TRAIL_FADE_RATE } from "./webgpu-renderer.js";
5
- import { isDev } from "./dev.js";
6
5
  const MAX_DPR = 2;
7
6
  const INSTANCE_FLOATS = 8;
8
7
  const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
@@ -167,9 +166,6 @@ function compileShader(gl, type, source) {
167
166
  gl.shaderSource(shader, source);
168
167
  gl.compileShader(shader);
169
168
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
170
- if (isDev()) {
171
- console.warn("[FireworksHdr] WebGL2 shader compile failed:", gl.getShaderInfoLog(shader));
172
- }
173
169
  gl.deleteShader(shader);
174
170
  return null;
175
171
  }
@@ -195,9 +191,6 @@ function linkProgram(gl, vsSrc, fsSrc) {
195
191
  gl.deleteShader(vs);
196
192
  gl.deleteShader(fs);
197
193
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
198
- if (isDev()) {
199
- console.warn("[FireworksHdr] WebGL2 program link failed:", gl.getProgramInfoLog(program));
200
- }
201
194
  gl.deleteProgram(program);
202
195
  return null;
203
196
  }
@@ -232,18 +225,8 @@ function startWebGl2Fireworks(canvas, opts) {
232
225
  } catch {
233
226
  }
234
227
  }
235
- if (isDev()) {
236
- console.info(
237
- renderLevel === "webgl-p3" ? "[FireworksHdr] WebGL2 fallback: webgl-p3 (wide-gamut drawing buffer)" : "[FireworksHdr] WebGL2 fallback: webgl-sdr"
238
- );
239
- }
240
228
  const floatAccum = !!glc.getExtension("EXT_color_buffer_float");
241
229
  if (wantFloatAccum && !floatAccum) {
242
- if (isDev()) {
243
- console.warn(
244
- "[FireworksHdr] WebGL2 float-accum probe/context mismatch; bailing to static fallback"
245
- );
246
- }
247
230
  return null;
248
231
  }
249
232
  let renderScale = opts.renderScale ?? 1;
@@ -253,7 +236,6 @@ function startWebGl2Fireworks(canvas, opts) {
253
236
  function onContextLost(e) {
254
237
  e.preventDefault();
255
238
  lost = true;
256
- if (isDev()) console.warn("[FireworksHdr] WebGL2 context lost");
257
239
  }
258
240
  canvas.addEventListener("webglcontextlost", onContextLost, false);
259
241
  let accum = null;
@@ -285,7 +267,6 @@ function startWebGl2Fireworks(canvas, opts) {
285
267
  fadeProgram = floatAccum ? null : linkProgram(glc, FULLSCREEN_VS, FADE_FS);
286
268
  const programsOk = floatAccum ? particleProgram && decayProgram && displayProgram : particleProgram && fadeProgram;
287
269
  if (!programsOk) {
288
- if (isDev()) console.warn("[FireworksHdr] WebGL2 program setup failed");
289
270
  cleanupPartial();
290
271
  return null;
291
272
  }
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { EXPOSURE_AMBIENT } from "./fireworks-shared.js";
3
3
  import { STRETCH } from "./fireworks-shared.js";
4
- import { isDev } from "./dev.js";
5
4
  const TRAIL_FADE_RATE = 7;
6
5
  const MAX_DPR = 2;
7
6
  const ACCUM_FORMAT = "rgba16float";
@@ -268,7 +267,6 @@ async function startWebGpuFireworks(canvas, opts) {
268
267
  const adapter = await gpu.requestAdapter();
269
268
  if (!adapter) return null;
270
269
  const device = await adapter.requestDevice();
271
- const { GPUShaderStage, GPUBufferUsage, GPUTextureUsage } = globalThis;
272
270
  const configuration = {
273
271
  device,
274
272
  format: ACCUM_FORMAT,
@@ -284,11 +282,8 @@ async function startWebGpuFireworks(canvas, opts) {
284
282
  }
285
283
  probe.configure(configuration);
286
284
  probe.unconfigure();
287
- } catch (error) {
285
+ } catch {
288
286
  device.destroy();
289
- if (isDev()) {
290
- console.warn("[FireworksHdr] WebGPU canvas configuration rejected:", error);
291
- }
292
287
  return null;
293
288
  }
294
289
  const maybeContext = canvas.getContext("webgpu");
@@ -308,20 +303,9 @@ async function startWebGpuFireworks(canvas, opts) {
308
303
  const hdrOutput = extendedToneMapping && hdrDisplay;
309
304
  let destroyed = false;
310
305
  let lost = false;
311
- device.lost.then((info) => {
306
+ device.lost.then(() => {
312
307
  lost = true;
313
- if (!destroyed && isDev()) {
314
- console.warn(`[FireworksHdr] WebGPU device lost (${info.reason}): ${info.message}`);
315
- }
316
308
  });
317
- if (isDev()) {
318
- device.addEventListener("uncapturederror", (event) => {
319
- console.error(
320
- "[FireworksHdr] WebGPU uncaptured error:",
321
- event.error.message
322
- );
323
- });
324
- }
325
309
  const module = device.createShaderModule({ code: WGSL });
326
310
  let renderScale = opts.renderScale ?? 1;
327
311
  const instanceCapacity = Math.max(1, opts.maxInstances);
@@ -443,10 +427,7 @@ async function startWebGpuFireworks(canvas, opts) {
443
427
  device.destroy();
444
428
  }
445
429
  };
446
- } catch (error) {
447
- if (isDev()) {
448
- console.warn("[FireworksHdr] WebGPU init failed:", error);
449
- }
430
+ } catch {
450
431
  return null;
451
432
  }
452
433
  }
@@ -1,8 +1,49 @@
1
1
  import type { ReactNode } from "react";
2
+ export type FlipCardTrigger = "hover" | "click";
2
3
  export interface FlipCardProps {
4
+ /** Axis of rotation */
3
5
  rotate?: "x" | "y";
6
+ /**
7
+ * What flips the card. `hover` (default) flips on pointer hover and on
8
+ * keyboard focus; `click` makes the card a toggle button (click, tap,
9
+ * Enter or Space).
10
+ */
11
+ trigger?: FlipCardTrigger;
12
+ /**
13
+ * Whether the back is showing. Seeds the card on mount; changing it later
14
+ * turns the card the rest of the way. Pair with `onFlip` to keep it in sync
15
+ * (the React counterpart of the Svelte source's `bind:flipped`).
16
+ */
17
+ flipped?: boolean;
18
+ /** Called with the new state after every flip, however it happened. */
19
+ onFlip?: (flipped: boolean) => void;
20
+ /** Length of one flip in milliseconds */
21
+ duration?: number;
22
+ /** Light the faces as they turn: a sheen sweeping across and shading edge-on */
23
+ glare?: boolean;
24
+ /** Accessible name for the card (announced in click mode) */
25
+ label?: string;
26
+ /** Additional CSS classes on the card */
4
27
  className?: string;
28
+ /** Front face content */
5
29
  children?: ReactNode;
30
+ /** Back face content */
6
31
  back?: ReactNode;
7
32
  }
8
- export declare function FlipCard({ rotate, className, children, back }: FlipCardProps): import("react").JSX.Element;
33
+ /** Whether an accumulated angle (a whole number of half turns) shows the back. */
34
+ export declare function showsBack(angle: number): boolean;
35
+ /**
36
+ * Which way to turn, from where the pointer crosses the card's edge: the
37
+ * card turns the way the pointer travels, so it seems pushed by it.
38
+ * `entering` is true on enter, false on leave.
39
+ */
40
+ export declare function flipDirection(rect: {
41
+ left: number;
42
+ top: number;
43
+ width: number;
44
+ height: number;
45
+ }, point: {
46
+ x: number;
47
+ y: number;
48
+ }, axis: "x" | "y", entering: boolean): 1 | -1;
49
+ export declare function FlipCard({ rotate, trigger, flipped, onFlip, duration, glare, label, className, children, back, }: FlipCardProps): import("react").JSX.Element;
@@ -1,35 +1,188 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { useState, useRef, useEffect } from "react";
3
4
  import { cn } from "../../utils.js";
4
- const rotationClass = {
5
- x: ["group-hover:[transform:rotateX(180deg)]", "[transform:rotateX(180deg)]"],
6
- y: ["group-hover:[transform:rotateY(180deg)]", "[transform:rotateY(180deg)]"]
7
- };
8
- function FlipCard({ rotate = "y", className = "", children, back }) {
9
- const rotation = rotationClass[rotate];
10
- return /* @__PURE__ */ jsx("div", { className: cn("group h-72 w-56 [perspective:1000px]", className), children: /* @__PURE__ */ jsxs(
5
+ import { useReducedMotion } from "../../internals/motion/media-query.js";
6
+ import { useInertAttribute } from "../../internals/dom/use-inert-attribute.js";
7
+ import { useLiveRef } from "../../internals/dom/use-live-ref.js";
8
+ /* empty css */
9
+ function showsBack(angle) {
10
+ return Math.abs(Math.round(angle / 180)) % 2 === 1;
11
+ }
12
+ function flipDirection(rect, point, axis, entering) {
13
+ const along = axis === "y" ? point.x - rect.left - rect.width / 2 : point.y - rect.top - rect.height / 2;
14
+ const towardEnd = entering ? along < 0 : along >= 0;
15
+ const sign = towardEnd ? 1 : -1;
16
+ return axis === "y" ? sign : -sign;
17
+ }
18
+ const INTERACTIVE = "a, button, input, select, textarea, label, [role='button'], [contenteditable='true']";
19
+ function FlipCard({
20
+ rotate = "y",
21
+ trigger = "hover",
22
+ flipped = false,
23
+ onFlip,
24
+ duration = 700,
25
+ glare = true,
26
+ label,
27
+ className = "",
28
+ children,
29
+ back
30
+ }) {
31
+ const reduced = useReducedMotion();
32
+ const [angle, setAngle] = useState(() => flipped ? 180 : 0);
33
+ const angleRef = useRef(angle);
34
+ const showingBack = showsBack(angle);
35
+ const rootRef = useRef(null);
36
+ const liftRef = useRef(null);
37
+ const shadowRef = useRef(null);
38
+ const frontInertRef = useInertAttribute(showingBack);
39
+ const backInertRef = useInertAttribute(!showingBack);
40
+ const live = useLiveRef({ onFlip, duration, reduced });
41
+ function setAngleTo(next) {
42
+ angleRef.current = next;
43
+ setAngle(next);
44
+ }
45
+ function lift() {
46
+ const { reduced: reduced2, duration: duration2 } = live.current;
47
+ if (reduced2) return;
48
+ const opts = { duration: duration2, easing: "cubic-bezier(0.33, 1, 0.68, 1)" };
49
+ liftRef.current?.animate?.(
50
+ [
51
+ { transform: "translateZ(0) scale(1)" },
52
+ { transform: "translateZ(40px) scale(1.05)", offset: 0.45 },
53
+ { transform: "translateZ(0) scale(1)" }
54
+ ],
55
+ opts
56
+ );
57
+ shadowRef.current?.animate?.(
58
+ [
59
+ { opacity: 0.35, transform: "translateY(10px) scale(0.92)", filter: "blur(14px)" },
60
+ {
61
+ opacity: 0.18,
62
+ transform: "translateY(26px) scale(0.85)",
63
+ filter: "blur(26px)",
64
+ offset: 0.45
65
+ },
66
+ { opacity: 0.35, transform: "translateY(10px) scale(0.92)", filter: "blur(14px)" }
67
+ ],
68
+ opts
69
+ );
70
+ }
71
+ function turn(direction) {
72
+ setAngleTo(angleRef.current + 180 * direction);
73
+ const next = showsBack(angleRef.current);
74
+ live.current.onFlip?.(next);
75
+ lift();
76
+ }
77
+ const liveLift = useLiveRef(lift);
78
+ useEffect(() => {
79
+ if (flipped !== showsBack(angleRef.current)) {
80
+ setAngleTo(angleRef.current + 180);
81
+ live.current.onFlip?.(flipped);
82
+ liveLift.current();
83
+ }
84
+ }, [flipped, live, liveLift]);
85
+ function pointerCross(event, entering) {
86
+ const root = rootRef.current;
87
+ if (trigger !== "hover" || event.pointerType === "touch" || !root) return;
88
+ const wantBack = entering;
89
+ if (showsBack(angleRef.current) === wantBack) return;
90
+ const dir = flipDirection(
91
+ root.getBoundingClientRect(),
92
+ { x: event.clientX, y: event.clientY },
93
+ rotate,
94
+ entering
95
+ );
96
+ turn(dir);
97
+ }
98
+ function handleFocus(event) {
99
+ if (trigger !== "hover" || showsBack(angleRef.current)) return;
100
+ if (event.target === rootRef.current) turn(1);
101
+ }
102
+ function handleBlur(event) {
103
+ if (event.target !== rootRef.current) return;
104
+ if (trigger !== "hover" || !showsBack(angleRef.current)) return;
105
+ const root = rootRef.current;
106
+ if (root && event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;
107
+ turn(1);
108
+ }
109
+ function isInnerControl(target) {
110
+ const root = rootRef.current;
111
+ if (!(target instanceof Element) || !root) return false;
112
+ const control = target.closest(INTERACTIVE);
113
+ return control !== null && control !== root && root.contains(control);
114
+ }
115
+ function handleClick(event) {
116
+ const touchOnHover = trigger === "hover" && event.nativeEvent.pointerType === "touch";
117
+ if (trigger !== "click" && !touchOnHover) return;
118
+ if (isInnerControl(event.target)) return;
119
+ turn(1);
120
+ }
121
+ function handleKeyDown(event) {
122
+ if (trigger !== "click" || event.target !== rootRef.current) return;
123
+ if (event.key === "Enter" || event.key === " ") {
124
+ event.preventDefault();
125
+ turn(1);
126
+ }
127
+ }
128
+ const style = {
129
+ "--fc-duration": `${duration}ms`,
130
+ "--fc-target": `${angle}deg`
131
+ };
132
+ return /* @__PURE__ */ jsxs(
11
133
  "div",
12
134
  {
13
- className: cn(
14
- "relative h-full rounded-2xl transition-all duration-500 [transform-style:preserve-3d]",
15
- rotation[0]
16
- ),
135
+ ref: rootRef,
136
+ className: cn("group ft-flip-card relative h-72 w-56 [perspective:1000px]", className),
137
+ "data-axis": rotate,
138
+ "data-flipped": showingBack ? "" : void 0,
139
+ "data-trigger": trigger,
140
+ style,
141
+ role: trigger === "click" ? "button" : "group",
142
+ tabIndex: 0,
143
+ "aria-pressed": trigger === "click" ? showingBack : void 0,
144
+ "aria-label": label,
145
+ "aria-roledescription": "flip card",
146
+ onPointerEnter: (e) => pointerCross(e, true),
147
+ onPointerLeave: (e) => pointerCross(e, false),
148
+ onFocus: handleFocus,
149
+ onBlur: handleBlur,
150
+ onClick: handleClick,
151
+ onKeyDown: handleKeyDown,
17
152
  children: [
18
- /* @__PURE__ */ jsx("div", { className: "absolute size-full overflow-hidden rounded-2xl border [backface-visibility:hidden]", children }),
19
- /* @__PURE__ */ jsx(
20
- "div",
21
- {
22
- className: cn(
23
- "absolute h-full w-full overflow-hidden rounded-2xl border bg-black/80 p-4 text-slate-200 [backface-visibility:hidden]",
24
- rotation[1]
25
- ),
26
- children: back
27
- }
28
- )
153
+ /* @__PURE__ */ jsx("div", { ref: shadowRef, className: "ft-flip-card__shadow", "aria-hidden": "true" }),
154
+ /* @__PURE__ */ jsx("div", { ref: liftRef, className: "ft-flip-card__lift size-full [transform-style:preserve-3d]", children: /* @__PURE__ */ jsxs("div", { className: "ft-flip-card__inner relative size-full rounded-2xl [transform-style:preserve-3d]", children: [
155
+ /* @__PURE__ */ jsxs(
156
+ "div",
157
+ {
158
+ ref: frontInertRef,
159
+ className: "ft-flip-card__face ft-flip-card__front bg-card text-card-foreground absolute size-full overflow-hidden rounded-2xl border [backface-visibility:hidden]",
160
+ "aria-hidden": showingBack ? "true" : void 0,
161
+ children: [
162
+ children,
163
+ glare ? /* @__PURE__ */ jsx("span", { className: "ft-flip-card__light", "aria-hidden": "true" }) : null
164
+ ]
165
+ }
166
+ ),
167
+ /* @__PURE__ */ jsxs(
168
+ "div",
169
+ {
170
+ ref: backInertRef,
171
+ className: "ft-flip-card__face ft-flip-card__back bg-card text-card-foreground absolute size-full overflow-hidden rounded-2xl border p-4 [backface-visibility:hidden]",
172
+ "aria-hidden": showingBack ? void 0 : "true",
173
+ children: [
174
+ back,
175
+ glare ? /* @__PURE__ */ jsx("span", { className: "ft-flip-card__light", "aria-hidden": "true" }) : null
176
+ ]
177
+ }
178
+ )
179
+ ] }) })
29
180
  ]
30
181
  }
31
- ) });
182
+ );
32
183
  }
33
184
  export {
34
- FlipCard
185
+ FlipCard,
186
+ flipDirection,
187
+ showsBack
35
188
  };
@@ -1,2 +1,2 @@
1
1
  export { FlipCard } from "./FlipCard.js";
2
- export type { FlipCardProps } from "./FlipCard.js";
2
+ export type { FlipCardProps, FlipCardTrigger } from "./FlipCard.js";
@@ -3,7 +3,8 @@ import { jsx } from "react/jsx-runtime";
3
3
  import { useState, useRef, useEffect } from "react";
4
4
  import { useLiveRef } from "../../internals/dom/use-live-ref.js";
5
5
  import { cn } from "../../utils.js";
6
- import { hexToRgb, clickBoost, DEV, wrap, pointerPrototype, scaleRadiusForContainer, HSVtoRGB } from "./fluid-shared.js";
6
+ import { hexToRgb, clickBoost, wrap, pointerPrototype, scaleRadiusForContainer, HSVtoRGB } from "./fluid-shared.js";
7
+ import { isDev } from "./dev.js";
7
8
  import { startWebGpuFluid } from "./webgpu-engine.js";
8
9
  function FluidCursor({
9
10
  simResolution = 128,
@@ -110,7 +111,7 @@ function FluidCursor({
110
111
  function registerInstance(cleanup) {
111
112
  if (!allowMultiple) {
112
113
  if (activeInstanceRef.current) {
113
- if (DEV) {
114
+ if (isDev()) {
114
115
  console.warn(
115
116
  "[FluidCursor] Destroying previous instance. Only one instance is allowed by default. Use `allowMultiple={true}` to opt out of singleton behavior."
116
117
  );
@@ -210,7 +211,7 @@ function FluidCursor({
210
211
  activeCleanup = registerInstance(handle.cleanup);
211
212
  notifyReady(handle);
212
213
  }).catch((error) => {
213
- if (DEV) {
214
+ if (isDev()) {
214
215
  console.warn("[FluidCursor] WebGPU setup failed, falling back to WebGL:", error);
215
216
  }
216
217
  if (!disposed) activeCleanup = startWebGl() ?? null;
@@ -262,7 +263,7 @@ function FluidCursor({
262
263
  }
263
264
  } catch {
264
265
  }
265
- if (DEV) {
266
+ if (isDev()) {
266
267
  console.info(
267
268
  renderLevel === "webgl-p3" ? "[FluidCursor] HDR level: webgl-p3 (wide gamut fallback)" : "[FluidCursor] HDR level: webgl-sdr (display-p3 drawing buffer rejected)"
268
269
  );
@@ -0,0 +1 @@
1
+ export declare function isDev(): boolean;
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ function isDev() {
3
+ const env = globalThis.process?.env;
4
+ return env?.NODE_ENV !== void 0 && env.NODE_ENV !== "production";
5
+ }
6
+ export {
7
+ isDev
8
+ };
@@ -88,5 +88,3 @@ export interface FluidCursorHandle {
88
88
  burst(x: number, y: number, dx: number, dy: number, color: ColorRGB): void;
89
89
  readonly renderLevel: FluidRenderLevel;
90
90
  }
91
- /** True outside a production build: gates the engines' console diagnostics. */
92
- export declare const DEV: boolean;
@@ -110,10 +110,8 @@ function correctDeltaY(delta, width, height) {
110
110
  if (aspectRatio > 1) return delta / aspectRatio;
111
111
  return delta;
112
112
  }
113
- const DEV = typeof process !== "undefined" && process.env.NODE_ENV !== "production";
114
113
  export {
115
114
  CLICK_PEAK,
116
- DEV,
117
115
  HSVtoRGB,
118
116
  clickBoost,
119
117
  correctDeltaX,
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getSimResolution, correctRadius, scaleRadiusForContainer, DEV, pointerPrototype, correctDeltaX, correctDeltaY } from "./fluid-shared.js";
2
+ import { getSimResolution, correctRadius, scaleRadiusForContainer, pointerPrototype, correctDeltaX, correctDeltaY } from "./fluid-shared.js";
3
3
  const WGSL = (
4
4
  /* wgsl */
5
5
  `
@@ -527,11 +527,8 @@ async function createWebGpuFluid(canvas, opts) {
527
527
  }
528
528
  probe.configure(configuration);
529
529
  probe.unconfigure();
530
- } catch (error) {
530
+ } catch {
531
531
  device.destroy();
532
- if (DEV) {
533
- console.warn("[FluidCursor] WebGPU canvas configuration rejected:", error);
534
- }
535
532
  return null;
536
533
  }
537
534
  const maybeContext = canvas.getContext("webgpu");
@@ -547,24 +544,11 @@ async function createWebGpuFluid(canvas, opts) {
547
544
  extendedToneMapping = applied?.toneMapping?.mode === "extended";
548
545
  } catch {
549
546
  }
550
- if (DEV) {
551
- console.info(
552
- extendedToneMapping ? "[FluidCursor] extended tone mapping active (webgpu-hdr if the display reports high dynamic range)" : "[FluidCursor] HDR level: webgpu-sdr (float16 + P3, tone mapping clamped by browser)"
553
- );
554
- }
555
547
  let destroyed = false;
556
548
  let lost = false;
557
- device.lost.then((info) => {
549
+ device.lost.then(() => {
558
550
  lost = true;
559
- if (!destroyed && DEV) {
560
- console.warn(`[FluidCursor] WebGPU device lost (${info.reason}): ${info.message}`);
561
- }
562
551
  });
563
- if (DEV) {
564
- device.addEventListener("uncapturederror", (event) => {
565
- console.error("[FluidCursor] WebGPU uncaptured error:", event.error.message);
566
- });
567
- }
568
552
  const module = device.createShaderModule({ code: WGSL });
569
553
  const bindGroupLayout = device.createBindGroupLayout({
570
554
  entries: [
@@ -643,10 +627,7 @@ async function createWebGpuFluid(canvas, opts) {
643
627
  device.destroy();
644
628
  }
645
629
  };
646
- } catch (error) {
647
- if (DEV) {
648
- console.warn("[FluidCursor] WebGPU init failed, falling back to WebGL:", error);
649
- }
630
+ } catch {
650
631
  return null;
651
632
  }
652
633
  }
@@ -805,6 +786,7 @@ async function startWebGpuFluid(canvas, opts) {
805
786
  const pointer = pointers[0];
806
787
  for (let i = 0; i < touches.length; i++) {
807
788
  const touch = touches[i];
789
+ if (!touch) continue;
808
790
  const { x: posX, y: posY } = getCanvasPos(touch.clientX, touch.clientY);
809
791
  updatePointerDownData(pointer, touch.identifier, posX, posY);
810
792
  }
@@ -815,6 +797,7 @@ async function startWebGpuFluid(canvas, opts) {
815
797
  const pointer = pointers[0];
816
798
  for (let i = 0; i < touches.length; i++) {
817
799
  const touch = touches[i];
800
+ if (!touch) continue;
818
801
  const { x: posX, y: posY } = getCanvasPos(touch.clientX, touch.clientY);
819
802
  updatePointerDownData(pointer, touch.identifier, posX, posY);
820
803
  }
@@ -824,6 +807,7 @@ async function startWebGpuFluid(canvas, opts) {
824
807
  const pointer = pointers[0];
825
808
  for (let i = 0; i < touches.length; i++) {
826
809
  const touch = touches[i];
810
+ if (!touch) continue;
827
811
  const { x: posX, y: posY } = getCanvasPos(touch.clientX, touch.clientY);
828
812
  updatePointerMoveData(pointer, posX, posY, pointer.color);
829
813
  }
@@ -847,7 +831,9 @@ async function startWebGpuFluid(canvas, opts) {
847
831
  let observer = null;
848
832
  if (opts.pauseWhenHidden) {
849
833
  observer = new IntersectionObserver(
850
- ([entry]) => {
834
+ (entries) => {
835
+ const entry = entries[0];
836
+ if (!entry) return;
851
837
  const wasVisible = isVisible;
852
838
  isVisible = entry.isIntersecting;
853
839
  if (isVisible && !wasVisible && !stopped) {
@@ -1,8 +1,48 @@
1
+ export type GlowBorderPreset = "chromatic" | "silver" | "gold";
1
2
  export interface GlowBorderProps {
3
+ /** Additional classes on the overlay */
2
4
  className?: string;
5
+ /** Border radius in pixels (the overlay also inherits its parent's radius) */
3
6
  borderRadius?: number;
7
+ /**
8
+ * Custom metal tints: one colour or several. Overrides `preset`; neutral
9
+ * silver and shadow tones are woven in so it still reads as metal.
10
+ */
4
11
  color?: string | string[];
12
+ /** Width of the metal ring in pixels */
5
13
  borderWidth?: number;
14
+ /** Length of one flow cycle of the metal, in seconds. The glint laps in 0.4× that. */
6
15
  duration?: number;
16
+ /** Metal palette: iridescent chrome, cool steel or warm gold */
17
+ preset?: GlowBorderPreset;
18
+ /** Intensity of the glint and its glow, 0–1 */
19
+ strength?: number;
7
20
  }
8
- export declare function GlowBorder({ className, borderRadius, color, borderWidth, duration, }: GlowBorderProps): import("react").JSX.Element;
21
+ interface Palette {
22
+ /** Bright tints, the colours the reflections flash. */
23
+ tints: string[];
24
+ /** The metal's body between reflections. */
25
+ body: string;
26
+ /** The shadow a reflection falls off into. */
27
+ shadow: string;
28
+ }
29
+ /** Palettes per preset, one per theme: light pages need deeper tones to read. */
30
+ export declare const GLOW_BORDER_PRESETS: Record<GlowBorderPreset, {
31
+ dark: Palette;
32
+ light: Palette;
33
+ }>;
34
+ /** A custom `color` turned into a metal palette: the tints, over neutral body and shadow. */
35
+ export declare function customPalette(color: string | string[], theme: "dark" | "light"): Palette;
36
+ /**
37
+ * One metal field as a conic gradient: reflections (tints) alternating
38
+ * with body and shadow, spread around the turn so no two flashes touch.
39
+ * Two of these, centred apart and turning against each other, are what
40
+ * make the surface look liquid rather than spun.
41
+ */
42
+ export declare function metalField(p: Palette, angleVar: string, at: string, offset?: number): string;
43
+ /** Both metal fields, stacked for `background` (blended in CSS). */
44
+ export declare function metalBackground(p: Palette): string;
45
+ /** The glint's colour trail: a short arc that brightens to white. */
46
+ export declare function glintArc(p: Palette): string;
47
+ export declare function GlowBorder({ className, borderRadius, color, borderWidth, duration, preset, strength, }: GlowBorderProps): import("react").JSX.Element;
48
+ export {};