fancy-ui-react 0.2.1 → 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.
- package/README.md +56 -20
- package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
- package/dist/components/animated-beam/AnimatedBeam.js +293 -118
- package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
- package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
- package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
- package/dist/components/bento-grid/BentoFrame.js +28 -0
- package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
- package/dist/components/bento-grid/BentoGrid.js +71 -2
- package/dist/components/bento-grid/BentoGridCard.js +34 -26
- package/dist/components/bento-grid/BentoGridItem.js +10 -10
- package/dist/components/border-beam/BorderBeam.d.ts +55 -10
- package/dist/components/border-beam/BorderBeam.js +93 -16
- package/dist/components/button/Button.js +5 -1
- package/dist/components/compare/Compare.d.ts +32 -8
- package/dist/components/compare/Compare.js +213 -95
- package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
- package/dist/components/container-scroll/ContainerScroll.js +88 -49
- package/dist/components/datamosh-transition/DatamoshTransition.d.ts +76 -0
- package/dist/components/datamosh-transition/DatamoshTransition.js +368 -0
- package/dist/components/datamosh-transition/datamosh-transition-core.d.ts +180 -0
- package/dist/components/datamosh-transition/datamosh-transition-core.js +370 -0
- package/dist/components/datamosh-transition/index.d.ts +3 -0
- package/dist/components/dock/Dock.d.ts +10 -7
- package/dist/components/dock/Dock.js +76 -31
- package/dist/components/dock/DockIcon.d.ts +14 -3
- package/dist/components/dock/DockIcon.js +23 -19
- package/dist/components/dock/DockSeparator.d.ts +4 -1
- package/dist/components/dock/DockSeparator.js +5 -2
- package/dist/components/dock/types.d.ts +13 -6
- package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
- package/dist/components/editorial-engine/engine.js +1 -14
- package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
- package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
- package/dist/components/flip-card/FlipCard.d.ts +42 -1
- package/dist/components/flip-card/FlipCard.js +178 -25
- package/dist/components/flip-card/index.d.ts +1 -1
- package/dist/components/fluid-cursor/FluidCursor.js +5 -4
- package/dist/components/fluid-cursor/dev.d.ts +1 -0
- package/dist/components/fluid-cursor/dev.js +8 -0
- package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
- package/dist/components/fluid-cursor/fluid-shared.js +0 -2
- package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
- package/dist/components/glow-border/GlowBorder.d.ts +41 -1
- package/dist/components/glow-border/GlowBorder.js +96 -16
- package/dist/components/glow-border/index.d.ts +2 -2
- package/dist/components/gradient-button/GradientButton.d.ts +10 -5
- package/dist/components/gradient-button/GradientButton.js +25 -18
- package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
- package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
- package/dist/components/image-trail-cursor/trail-variants.js +331 -8
- package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
- package/dist/components/marquee/Marquee.d.ts +7 -3
- package/dist/components/marquee/Marquee.js +134 -2
- package/dist/components/marquee/ReviewCard.d.ts +2 -1
- package/dist/components/marquee/ReviewCard.js +20 -9
- package/dist/components/meteors/Meteors.d.ts +32 -10
- package/dist/components/meteors/Meteors.js +53 -20
- package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
- package/dist/components/neon-border/NeonBorder.d.ts +24 -5
- package/dist/components/neon-border/NeonBorder.js +37 -7
- package/dist/components/pagination/Pagination.js +220 -82
- package/dist/components/rainbow-button/RainbowButton.js +13 -5
- package/dist/components/ripple-button/RippleButton.d.ts +20 -1
- package/dist/components/ripple-button/RippleButton.js +44 -21
- package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
- package/dist/components/shimmer-button/ShimmerButton.js +17 -19
- package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
- package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
- package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
- package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
- package/dist/components/stepper/Step.js +32 -19
- package/dist/components/stepper/Stepper.js +18 -2
- package/dist/components/stepper/types.d.ts +13 -0
- package/dist/components/timeline/Timeline.d.ts +8 -1
- package/dist/components/timeline/Timeline.js +165 -76
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -1
- package/dist/internals/markdown.d.ts +5 -0
- package/dist/internals/motion/haptics.d.ts +0 -4
- package/dist/internals/motion/presence-core.d.ts +59 -0
- package/dist/internals/motion/presence-core.js +162 -0
- package/dist/internals/motion/presence.d.ts +2 -4
- package/dist/internals/motion/presence.js +1 -156
- package/dist/internals/motion/raf.d.ts +1 -1
- package/dist/internals/motion/stagger.d.ts +3 -4
- package/dist/internals/motion/tokens.js +4 -0
- package/dist/internals/motion/transitions.d.ts +17 -17
- package/dist/internals/motion/types.d.ts +6 -7
- package/dist/internals/relative-time.d.ts +1 -1
- package/dist/styles.css +3545 -204
- package/package.json +18 -2
- package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
|
@@ -1,26 +1,158 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState, useEffect } from "react";
|
|
3
4
|
import { cn } from "../../utils.js";
|
|
5
|
+
import { useReducedMotion } from "../../internals/motion/media-query.js";
|
|
4
6
|
/* empty css */
|
|
7
|
+
const EASE_TAU = 140;
|
|
8
|
+
const SETTLE_EPSILON = 2e-3;
|
|
9
|
+
const DEFAULT_DURATION_MS = 4e4;
|
|
10
|
+
function parseDuration(raw) {
|
|
11
|
+
const match = /^\s*([\d.]+)\s*(ms|s)\s*$/i.exec(raw);
|
|
12
|
+
if (!match) return DEFAULT_DURATION_MS;
|
|
13
|
+
const value = Number.parseFloat(match[1] ?? "");
|
|
14
|
+
if (!Number.isFinite(value) || value <= 0) return DEFAULT_DURATION_MS;
|
|
15
|
+
return (match[2] ?? "s").toLowerCase() === "ms" ? value : value * 1e3;
|
|
16
|
+
}
|
|
17
|
+
function createConveyor() {
|
|
18
|
+
const c = {
|
|
19
|
+
speed: 1,
|
|
20
|
+
pauseOnHover: false,
|
|
21
|
+
engaged: false,
|
|
22
|
+
factor: 1,
|
|
23
|
+
animations: [],
|
|
24
|
+
rafId: 0,
|
|
25
|
+
lastTs: 0,
|
|
26
|
+
resumeAt: null,
|
|
27
|
+
applyRate() {
|
|
28
|
+
const rate = c.speed * c.factor;
|
|
29
|
+
for (const animation of c.animations) animation.playbackRate = rate;
|
|
30
|
+
},
|
|
31
|
+
wake() {
|
|
32
|
+
if (!c.animations.length || c.rafId) return;
|
|
33
|
+
c.lastTs = 0;
|
|
34
|
+
c.rafId = requestAnimationFrame(tick);
|
|
35
|
+
},
|
|
36
|
+
setEngaged(next) {
|
|
37
|
+
c.engaged = next && c.pauseOnHover;
|
|
38
|
+
c.wake();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function tick(ts) {
|
|
42
|
+
const dt = c.lastTs ? Math.min(ts - c.lastTs, 64) : 16;
|
|
43
|
+
c.lastTs = ts;
|
|
44
|
+
const target = c.engaged ? 0 : 1;
|
|
45
|
+
c.factor += (target - c.factor) * (1 - Math.exp(-dt / EASE_TAU));
|
|
46
|
+
if (Math.abs(target - c.factor) < SETTLE_EPSILON) c.factor = target;
|
|
47
|
+
c.applyRate();
|
|
48
|
+
if (c.factor === target) {
|
|
49
|
+
c.rafId = 0;
|
|
50
|
+
c.lastTs = 0;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
c.rafId = requestAnimationFrame(tick);
|
|
54
|
+
}
|
|
55
|
+
return c;
|
|
56
|
+
}
|
|
5
57
|
function Marquee({
|
|
6
58
|
className,
|
|
7
59
|
reverse = false,
|
|
8
60
|
pauseOnHover = false,
|
|
9
61
|
vertical = false,
|
|
10
62
|
repeat = 4,
|
|
63
|
+
fade = true,
|
|
64
|
+
speed = 1,
|
|
11
65
|
children
|
|
12
66
|
}) {
|
|
67
|
+
const reduced = useReducedMotion();
|
|
68
|
+
const rootRef = useRef(null);
|
|
69
|
+
const [upgraded, setUpgraded] = useState(false);
|
|
70
|
+
const safeSpeed = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
71
|
+
const conveyorRef = useRef(null);
|
|
72
|
+
if (!conveyorRef.current) conveyorRef.current = createConveyor();
|
|
73
|
+
const conveyor = conveyorRef.current;
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
conveyor.speed = safeSpeed;
|
|
76
|
+
conveyor.applyRate();
|
|
77
|
+
}, [conveyor, safeSpeed]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
conveyor.pauseOnHover = pauseOnHover;
|
|
80
|
+
if (!pauseOnHover && conveyor.engaged) conveyor.setEngaged(false);
|
|
81
|
+
}, [conveyor, pauseOnHover]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const el = rootRef.current;
|
|
84
|
+
if (!el || reduced) return;
|
|
85
|
+
if (typeof Element === "undefined" || typeof Element.prototype.animate !== "function") return;
|
|
86
|
+
const styles = getComputedStyle(el);
|
|
87
|
+
const duration = parseDuration(styles.getPropertyValue("--duration"));
|
|
88
|
+
const gapPx = Number.parseFloat(vertical ? styles.rowGap : styles.columnGap) || 0;
|
|
89
|
+
const axis = vertical ? "translateY" : "translateX";
|
|
90
|
+
const keyframes = [
|
|
91
|
+
{ transform: `${axis}(0)` },
|
|
92
|
+
{ transform: `${axis}(calc(-100% - ${gapPx}px))` }
|
|
93
|
+
];
|
|
94
|
+
const tracks = Array.from(el.querySelectorAll(":scope > [data-marquee-track]"));
|
|
95
|
+
let startTime = 0;
|
|
96
|
+
if (conveyor.resumeAt !== null) {
|
|
97
|
+
startTime = (reverse ? 1 - conveyor.resumeAt : conveyor.resumeAt) * duration;
|
|
98
|
+
} else {
|
|
99
|
+
const running = tracks[0]?.getAnimations?.()[0];
|
|
100
|
+
if (running && typeof running.currentTime === "number") {
|
|
101
|
+
startTime = running.currentTime * conveyor.speed;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
conveyor.animations = tracks.map(
|
|
105
|
+
(track) => track.animate(keyframes, {
|
|
106
|
+
duration,
|
|
107
|
+
iterations: Infinity,
|
|
108
|
+
easing: "linear",
|
|
109
|
+
direction: reverse ? "reverse" : "normal"
|
|
110
|
+
})
|
|
111
|
+
).filter(Boolean);
|
|
112
|
+
for (const animation of conveyor.animations) animation.currentTime = startTime % duration;
|
|
113
|
+
conveyor.applyRate();
|
|
114
|
+
setUpgraded(true);
|
|
115
|
+
if (conveyor.factor !== (conveyor.engaged ? 0 : 1)) conveyor.wake();
|
|
116
|
+
return () => {
|
|
117
|
+
if (conveyor.rafId) cancelAnimationFrame(conveyor.rafId);
|
|
118
|
+
conveyor.rafId = 0;
|
|
119
|
+
const t = conveyor.animations[0]?.currentTime;
|
|
120
|
+
if (typeof t === "number") {
|
|
121
|
+
const progress = t % duration / duration;
|
|
122
|
+
conveyor.resumeAt = reverse ? 1 - progress : progress;
|
|
123
|
+
} else {
|
|
124
|
+
conveyor.resumeAt = null;
|
|
125
|
+
}
|
|
126
|
+
for (const animation of conveyor.animations) animation.cancel?.();
|
|
127
|
+
conveyor.animations = [];
|
|
128
|
+
setUpgraded(false);
|
|
129
|
+
};
|
|
130
|
+
}, [conveyor, vertical, reverse, repeat, className, reduced]);
|
|
131
|
+
function onFocusOut(event) {
|
|
132
|
+
if (!rootRef.current?.contains(event.relatedTarget)) conveyor.setEngaged(false);
|
|
133
|
+
}
|
|
13
134
|
return /* @__PURE__ */ jsx(
|
|
14
135
|
"div",
|
|
15
136
|
{
|
|
137
|
+
ref: rootRef,
|
|
16
138
|
className: cn(
|
|
17
|
-
"
|
|
18
|
-
vertical ? "flex-col" : "flex-row",
|
|
139
|
+
"marquee group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
|
|
140
|
+
vertical ? "marquee-vertical flex-col" : "flex-row",
|
|
141
|
+
fade && "marquee-fade",
|
|
142
|
+
upgraded && "marquee-upgraded",
|
|
19
143
|
className
|
|
20
144
|
),
|
|
145
|
+
style: { "--marquee-speed": safeSpeed },
|
|
146
|
+
"data-paused-on-hover": pauseOnHover ? "" : void 0,
|
|
147
|
+
onPointerEnter: () => conveyor.setEngaged(true),
|
|
148
|
+
onPointerLeave: () => conveyor.setEngaged(false),
|
|
149
|
+
onFocus: () => conveyor.setEngaged(true),
|
|
150
|
+
onBlur: onFocusOut,
|
|
21
151
|
children: Array.from({ length: repeat }, (_, index) => /* @__PURE__ */ jsx(
|
|
22
152
|
"div",
|
|
23
153
|
{
|
|
154
|
+
"data-marquee-track": "",
|
|
155
|
+
"aria-hidden": index > 0 ? "true" : void 0,
|
|
24
156
|
className: cn(
|
|
25
157
|
"flex shrink-0 justify-around [gap:var(--gap)]",
|
|
26
158
|
vertical ? "animate-marquee-vertical flex-col" : "animate-marquee flex-row",
|
|
@@ -4,5 +4,6 @@ export interface ReviewCardProps {
|
|
|
4
4
|
username: string;
|
|
5
5
|
body: string;
|
|
6
6
|
}
|
|
7
|
-
/** A ready-made card for testimonial-style marquees
|
|
7
|
+
/** A ready-made card for testimonial-style marquees: a nested double frame,
|
|
8
|
+
* a hairline shell holding a hairline inner panel. */
|
|
8
9
|
export declare function ReviewCard({ img, name, username, body }: ReviewCardProps): import("react").JSX.Element;
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
/* empty css */
|
|
3
4
|
function ReviewCard({ img, name, username, body }) {
|
|
4
|
-
return /* @__PURE__ */
|
|
5
|
-
/* @__PURE__ */
|
|
6
|
-
|
|
7
|
-
/* @__PURE__ */
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
return /* @__PURE__ */ jsx("figure", { className: "review-card relative flex w-72 shrink-0 flex-col rounded-2xl border p-1", children: /* @__PURE__ */ jsxs("div", { className: "review-card-inner relative flex-1 overflow-hidden rounded-[12px] border px-4 pt-3.5 pb-4", children: [
|
|
6
|
+
/* @__PURE__ */ jsx("span", { className: "review-card-sheen", "aria-hidden": "true" }),
|
|
7
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-3", children: [
|
|
8
|
+
/* @__PURE__ */ jsx(
|
|
9
|
+
"img",
|
|
10
|
+
{
|
|
11
|
+
src: img,
|
|
12
|
+
className: "review-card-avatar size-8 rounded-full",
|
|
13
|
+
width: "32",
|
|
14
|
+
height: "32",
|
|
15
|
+
alt: ""
|
|
16
|
+
}
|
|
17
|
+
),
|
|
18
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col leading-tight", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("span", { className: "review-card-name truncate text-[13px] font-medium tracking-[-0.01em]", children: name }),
|
|
20
|
+
/* @__PURE__ */ jsx("span", { className: "review-card-handle truncate text-[12px]", children: username })
|
|
10
21
|
] })
|
|
11
22
|
] }),
|
|
12
|
-
/* @__PURE__ */ jsx("blockquote", { className: "mt-
|
|
13
|
-
] });
|
|
23
|
+
/* @__PURE__ */ jsx("blockquote", { className: "review-card-body relative mt-3 text-[13px] leading-relaxed", children: body })
|
|
24
|
+
] }) });
|
|
14
25
|
}
|
|
15
26
|
export {
|
|
16
27
|
ReviewCard
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Meteors - Animated meteor shower effect
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A seeded field of meteors, each at its own depth: near ones are larger,
|
|
5
|
+
* brighter, longer-tailed and faster, far ones faint and slow, so the
|
|
6
|
+
* shower reads in parallax. Each meteor fades in, streaks across on a
|
|
7
|
+
* diagonal and burns out; a few flare before they go.
|
|
6
8
|
*/
|
|
7
9
|
export interface MeteorsProps {
|
|
8
10
|
/** Number of meteors to render */
|
|
9
11
|
count?: number;
|
|
10
12
|
/** Additional CSS classes applied to each meteor */
|
|
11
13
|
className?: string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** Direction of travel, in degrees (215 = down and to the right) */
|
|
15
|
+
angle?: number;
|
|
16
|
+
/** Speed multiplier: 2 is twice as fast */
|
|
17
|
+
speed?: number;
|
|
18
|
+
/** Head and tail colour. Defaults to a pale blue-white on dark pages, slate on light ones */
|
|
19
|
+
color?: string;
|
|
20
|
+
/** Seed for the field — same seed, same shower (and the same markup on server and client) */
|
|
19
21
|
seed?: number;
|
|
20
22
|
}
|
|
21
|
-
export
|
|
23
|
+
export interface Meteor {
|
|
24
|
+
/** Start position, % of the container */
|
|
25
|
+
left: number;
|
|
26
|
+
top: number;
|
|
27
|
+
/** 0 (far) to 1 (near) */
|
|
28
|
+
depth: number;
|
|
29
|
+
/** Seconds for one pass */
|
|
30
|
+
duration: number;
|
|
31
|
+
/** Negative start offset, so the shower is already under way */
|
|
32
|
+
delay: number;
|
|
33
|
+
/** Whether this one flares before it burns out */
|
|
34
|
+
flare: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The shower. Starts reach well past the left edge (meteors drift right
|
|
38
|
+
* as they fall, so a start on the left is what fills the left half) and
|
|
39
|
+
* over the upper part of the height; depth skewed toward far, the way a
|
|
40
|
+
* real sky has many faint meteors and few bright ones.
|
|
41
|
+
*/
|
|
42
|
+
export declare function meteorField(count: number, seed?: number): Meteor[];
|
|
43
|
+
export declare function Meteors({ count, className, angle, speed, color, seed, }: MeteorsProps): import("react").JSX.Element;
|
|
@@ -4,42 +4,75 @@ import { useMemo } from "react";
|
|
|
4
4
|
import { cn } from "../../utils.js";
|
|
5
5
|
/* empty css */
|
|
6
6
|
function mulberry32(seed) {
|
|
7
|
-
let
|
|
7
|
+
let a = seed >>> 0;
|
|
8
8
|
return () => {
|
|
9
|
-
|
|
10
|
-
let t =
|
|
9
|
+
a = a + 1831565813 >>> 0;
|
|
10
|
+
let t = a;
|
|
11
11
|
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
12
12
|
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
13
13
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
function meteorField(count, seed = 1) {
|
|
17
|
+
const n = Math.max(0, Math.floor(Number.isFinite(count) ? count : 0));
|
|
18
|
+
const rand = mulberry32(seed);
|
|
19
|
+
return Array.from({ length: n }, () => {
|
|
20
|
+
const depth = Math.pow(rand(), 1.6);
|
|
21
|
+
const duration = 7 - depth * 4.5 + rand() * 1.5;
|
|
22
|
+
return {
|
|
23
|
+
left: -50 + rand() * 150,
|
|
24
|
+
top: -30 + rand() * 60,
|
|
25
|
+
depth,
|
|
26
|
+
duration,
|
|
27
|
+
delay: -rand() * duration * 1.6,
|
|
28
|
+
flare: rand() < 0.2
|
|
29
|
+
};
|
|
30
|
+
});
|
|
23
31
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
32
|
+
function styleFor(m, speed, angle, color) {
|
|
33
|
+
const scale = 0.55 + m.depth * 0.9;
|
|
34
|
+
const tail = Math.round(40 + m.depth * 110);
|
|
35
|
+
const brightness = (0.35 + m.depth * 0.65).toFixed(2);
|
|
36
|
+
const style = {
|
|
37
|
+
left: `${m.left.toFixed(2)}%`,
|
|
38
|
+
top: `${m.top.toFixed(2)}%`,
|
|
39
|
+
animationDelay: `${(m.delay / speed).toFixed(2)}s`,
|
|
40
|
+
animationDuration: `${(m.duration / speed).toFixed(2)}s`,
|
|
41
|
+
"--meteor-angle": `${angle}deg`,
|
|
42
|
+
"--meteor-scale": scale.toFixed(2),
|
|
43
|
+
"--meteor-tail": `${tail}px`,
|
|
44
|
+
"--meteor-brightness": brightness,
|
|
45
|
+
"--meteor-travel": `${Math.round(700 + m.depth * 500)}px`
|
|
46
|
+
};
|
|
47
|
+
if (color) style["--meteor-color"] = color;
|
|
48
|
+
return style;
|
|
49
|
+
}
|
|
50
|
+
function Meteors({
|
|
51
|
+
count = 20,
|
|
52
|
+
className,
|
|
53
|
+
angle = 215,
|
|
54
|
+
speed = 1,
|
|
55
|
+
color,
|
|
56
|
+
seed = 1
|
|
57
|
+
}) {
|
|
58
|
+
const meteors = useMemo(() => meteorField(count, seed), [count, seed]);
|
|
59
|
+
const speedC = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
60
|
+
const angleC = Number.isFinite(angle) ? angle : 215;
|
|
26
61
|
return /* @__PURE__ */ jsx(Fragment, { children: meteors.map((meteor, i) => /* @__PURE__ */ jsx(
|
|
27
62
|
"span",
|
|
28
63
|
{
|
|
29
64
|
className: cn(
|
|
30
|
-
"meteor pointer-events-none absolute top-0 h-0.5 w-0.5 rounded-full
|
|
31
|
-
|
|
65
|
+
"meteor pointer-events-none absolute top-0 h-0.5 w-0.5 rounded-full opacity-0",
|
|
66
|
+
meteor.flare && "meteor--flare",
|
|
32
67
|
className
|
|
33
68
|
),
|
|
34
|
-
style:
|
|
35
|
-
|
|
36
|
-
animationDelay: meteor.animationDelay,
|
|
37
|
-
animationDuration: meteor.animationDuration
|
|
38
|
-
}
|
|
69
|
+
style: styleFor(meteor, speedC, angleC, color),
|
|
70
|
+
"aria-hidden": "true"
|
|
39
71
|
},
|
|
40
72
|
i
|
|
41
73
|
)) });
|
|
42
74
|
}
|
|
43
75
|
export {
|
|
44
|
-
Meteors
|
|
76
|
+
Meteors,
|
|
77
|
+
meteorField
|
|
45
78
|
};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MosaicGlow core —
|
|
2
|
+
* MosaicGlow core — the framework-free half of the component.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* First the pure math, unit-testable without a browser: a seeded PRNG, per-tile
|
|
5
|
+
* grid state, the halo falloff curve, the fps-independent heat step, ambient
|
|
6
|
+
* flicker, the colour LUT and the idle drift path. Then `createMosaicGlow`, the
|
|
7
|
+
* engine that owns the canvas, the rAF loop and the pointer listeners. No
|
|
8
|
+
* framework imports, and nothing touches `window` or `document` at module scope,
|
|
9
|
+
* so the file is safe to import on the server.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* loops are driven by the array's own length, the regex groups are guaranteed
|
|
13
|
-
* by a successful match), so the assertions change no behaviour.
|
|
11
|
+
* Compiles under `noUncheckedIndexedAccess`, which widens every indexed read
|
|
12
|
+
* to `| undefined`. The `!` assertions below are all in bounds by construction
|
|
13
|
+
* (the loops are driven by the array's own length, the regex groups are
|
|
14
|
+
* guaranteed by a successful match), so they change no behaviour.
|
|
14
15
|
*/
|
|
15
16
|
export type Rgb = [number, number, number];
|
|
16
17
|
/** Small, fast, seedable PRNG (32-bit state). Same seed → same sequence. */
|
|
@@ -92,3 +93,90 @@ export declare function releaseTau(trail: number): number;
|
|
|
92
93
|
/** `smoothing` 0..1 → pointer lag time constant in seconds (0 = instant). */
|
|
93
94
|
export declare function smoothingTau(smoothing: number): number;
|
|
94
95
|
export declare function clamp01(v: number): number;
|
|
96
|
+
/** What the halo does with no pointer: wander on its own or switch off. */
|
|
97
|
+
export type MosaicGlowIdle = "drift" | "none";
|
|
98
|
+
/** Wait after the pointer leaves before the idle drift takes over (ms). */
|
|
99
|
+
export declare const IDLE_DELAY_MS = 1500;
|
|
100
|
+
/** Ambient-only frames are drawn at most this often (ms). */
|
|
101
|
+
export declare const AMBIENT_FRAME_MS = 50;
|
|
102
|
+
/** Chance per second that a tile re-rolls its resting level. */
|
|
103
|
+
export declare const FLICKER_CHANCE = 0.15;
|
|
104
|
+
/** Steps in the tile colour ramp. */
|
|
105
|
+
export declare const LUT_SIZE = 64;
|
|
106
|
+
/** `tileSize` → tile edge in CSS px. */
|
|
107
|
+
export declare function normalizeTileSize(v: number): number;
|
|
108
|
+
/** `gap` → gap between tiles in CSS px. */
|
|
109
|
+
export declare function normalizeGap(v: number): number;
|
|
110
|
+
/** `radius` → halo radius in CSS px. */
|
|
111
|
+
export declare function normalizeRadius(v: number): number;
|
|
112
|
+
export interface MosaicGlowElements {
|
|
113
|
+
/** Positioned host: measured for the backing store, and the pointer surface. */
|
|
114
|
+
host: HTMLElement;
|
|
115
|
+
/** Canvas the mosaic is painted on. The engine owns its width/height. */
|
|
116
|
+
canvas: HTMLCanvasElement;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Mount-only options. MosaicGlow has none — every prop the wrapper passes is
|
|
120
|
+
* reacted to after mount — so the split is recorded here as "nothing is frozen".
|
|
121
|
+
*/
|
|
122
|
+
export type MosaicGlowInitOptions = Record<never, never>;
|
|
123
|
+
/**
|
|
124
|
+
* Options the engine reacts to after mount. `setOptions` acts on the keys that
|
|
125
|
+
* are present (an absent or `undefined` key is left alone), grouped by concern
|
|
126
|
+
* exactly as the wrapper's effects are:
|
|
127
|
+
* structural (`tileSize`, `gap`, `seed`, `noise`, `ambient`) rebuilds the grid,
|
|
128
|
+
* colour (`color`, `background`) drops the ramp cache, visual (`idle`,
|
|
129
|
+
* `flicker`, `radius`, `intensity`) repaints, `reducedMotion` swaps the loop for
|
|
130
|
+
* a single static frame, `interactive` attaches or detaches the pointer
|
|
131
|
+
* listeners, `visible` pauses and resumes the loop. `trail` and `smoothing` are
|
|
132
|
+
* read by the next frame and schedule nothing.
|
|
133
|
+
*/
|
|
134
|
+
export interface MosaicGlowLiveOptions {
|
|
135
|
+
/** Tile edge in CSS px. */
|
|
136
|
+
tileSize?: number;
|
|
137
|
+
/** Gap between tiles in CSS px. */
|
|
138
|
+
gap?: number;
|
|
139
|
+
/** Halo / tile colour — hex or rgb(). */
|
|
140
|
+
color?: string;
|
|
141
|
+
/** Surface colour behind the tiles — hex or rgb(). */
|
|
142
|
+
background?: string;
|
|
143
|
+
/** Halo radius in CSS px. */
|
|
144
|
+
radius?: number;
|
|
145
|
+
/** Overall brightness of lit tiles, 0–1. */
|
|
146
|
+
intensity?: number;
|
|
147
|
+
/** How long lit tiles linger after the halo moves on, 0–1. */
|
|
148
|
+
trail?: number;
|
|
149
|
+
/** Pointer lag, 0 (instant) to 1 (very laggy). */
|
|
150
|
+
smoothing?: number;
|
|
151
|
+
/** Spread of per-tile random brightness inside the halo, 0–1. */
|
|
152
|
+
noise?: number;
|
|
153
|
+
/** Visibility of the random faint tiles outside the halo, 0–1. */
|
|
154
|
+
ambient?: number;
|
|
155
|
+
/** Slowly re-roll the faint tiles over time. */
|
|
156
|
+
flicker?: boolean;
|
|
157
|
+
/** What the halo does with no pointer. */
|
|
158
|
+
idle?: MosaicGlowIdle;
|
|
159
|
+
/** Follow the pointer. Off leaves only the idle behaviour. */
|
|
160
|
+
interactive?: boolean;
|
|
161
|
+
/** Seed for the per-tile randomness — same seed, same mosaic. */
|
|
162
|
+
seed?: number;
|
|
163
|
+
/** The wrapper's `prefers-reduced-motion` gate: no loop, one settled frame. */
|
|
164
|
+
reducedMotion?: boolean;
|
|
165
|
+
/** The wrapper's visibility gate: false parks the loop. */
|
|
166
|
+
visible?: boolean;
|
|
167
|
+
}
|
|
168
|
+
export interface MosaicGlowEngine {
|
|
169
|
+
setOptions(next: Partial<MosaicGlowLiveOptions>): void;
|
|
170
|
+
/** Re-measure the host; rebuilds only when the box or the pixel ratio moved. */
|
|
171
|
+
resize(): void;
|
|
172
|
+
destroy(): void;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Drive a MosaicGlow canvas. Returns null when no 2D context is available
|
|
176
|
+
* (server, jsdom without a stub, a browser that refuses the context) — the
|
|
177
|
+
* caller then simply renders an inert canvas.
|
|
178
|
+
*
|
|
179
|
+
* `random` feeds the ambient flicker only; the per-tile field is seeded through
|
|
180
|
+
* the `seed` option and stays deterministic.
|
|
181
|
+
*/
|
|
182
|
+
export declare function createMosaicGlow(el: MosaicGlowElements, options?: MosaicGlowInitOptions & MosaicGlowLiveOptions, random?: () => number): MosaicGlowEngine | null;
|
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
2
|
/**
|
|
3
|
-
* NeonBorder -
|
|
3
|
+
* NeonBorder - a two-colour neon tube around its content
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* A faint tube runs all the way round; two beams, one per colour, chase
|
|
6
|
+
* each other along it from opposite sides. Each beam has a white-hot core
|
|
7
|
+
* and a coloured glow that spills both inside and outside the edge. The
|
|
8
|
+
* glow hums (a slow breathing), and the tube flickers once as it ignites.
|
|
7
9
|
*/
|
|
8
10
|
export interface NeonBorderProps extends Omit<HTMLAttributes<HTMLDivElement>, "className" | "style"> {
|
|
9
11
|
/** First neon color */
|
|
10
12
|
color1?: string;
|
|
11
13
|
/** Second neon color */
|
|
12
14
|
color2?: string;
|
|
13
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* How much of the tube is lit: none (two short beams, static), half
|
|
17
|
+
* (two short beams chasing), full (two long beams that nearly close the ring)
|
|
18
|
+
*/
|
|
14
19
|
animationType?: "none" | "half" | "full";
|
|
15
|
-
/**
|
|
20
|
+
/** Time for the beams to travel once around, in seconds */
|
|
16
21
|
duration?: number;
|
|
17
22
|
/** Additional CSS classes */
|
|
18
23
|
className?: string;
|
|
19
24
|
/** Content */
|
|
20
25
|
children?: ReactNode;
|
|
21
26
|
}
|
|
27
|
+
export type NeonAnimationType = NonNullable<NeonBorderProps["animationType"]>;
|
|
28
|
+
/** Share of the turn each beam covers, in %. */
|
|
29
|
+
export declare function beamArc(type: NeonAnimationType): number;
|
|
30
|
+
/**
|
|
31
|
+
* The static tube (`animationType="none"`): lit at two opposite corners,
|
|
32
|
+
* the first colour top-left and the second bottom-right.
|
|
33
|
+
*/
|
|
34
|
+
export declare function neonCorners(color1: string, color2: string, core?: boolean): string;
|
|
35
|
+
/**
|
|
36
|
+
* The two beams as one conic gradient: each fades in from its tail and
|
|
37
|
+
* cuts off sharply at its head, the second half a turn behind the first.
|
|
38
|
+
* `core` whitens each colour for the hot centre of the tube.
|
|
39
|
+
*/
|
|
40
|
+
export declare function neonBeams(color1: string, color2: string, arc: number, core?: boolean): string;
|
|
22
41
|
export declare function NeonBorder({ color1, color2, animationType, duration, className, children, ...rest }: NeonBorderProps): import("react").JSX.Element;
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from "../../utils.js";
|
|
4
4
|
/* empty css */
|
|
5
|
+
function beamArc(type) {
|
|
6
|
+
return type === "full" ? 46 : 22;
|
|
7
|
+
}
|
|
8
|
+
function neonCorners(color1, color2, core = false) {
|
|
9
|
+
const tint = (c) => core ? `color-mix(in srgb, ${c} 45%, #fff)` : c;
|
|
10
|
+
return `linear-gradient(135deg, ${tint(color1)} 0%, transparent 32%, transparent 68%, ${tint(color2)} 100%)`;
|
|
11
|
+
}
|
|
12
|
+
function neonBeams(color1, color2, arc, core = false) {
|
|
13
|
+
const tint = (c) => core ? `color-mix(in srgb, ${c} 45%, #fff)` : c;
|
|
14
|
+
const beam = (c, from) => {
|
|
15
|
+
const head = from + arc;
|
|
16
|
+
return [
|
|
17
|
+
`transparent ${from}%`,
|
|
18
|
+
`color-mix(in srgb, ${tint(c)} 55%, transparent) ${(from + arc * 0.45).toFixed(1)}%`,
|
|
19
|
+
`${tint(c)} ${(head - 0.6).toFixed(1)}%`,
|
|
20
|
+
`transparent ${head.toFixed(1)}%`
|
|
21
|
+
].join(", ");
|
|
22
|
+
};
|
|
23
|
+
return `conic-gradient(from var(--neon-angle), ${beam(color1, 0)}, ${beam(color2, 50)}, transparent 100%)`;
|
|
24
|
+
}
|
|
5
25
|
function getWidth(type) {
|
|
6
26
|
switch (type) {
|
|
7
27
|
case "none":
|
|
@@ -21,30 +41,40 @@ function NeonBorder({
|
|
|
21
41
|
children,
|
|
22
42
|
...rest
|
|
23
43
|
}) {
|
|
44
|
+
const arc = beamArc(animationType);
|
|
45
|
+
const animated = animationType !== "none";
|
|
24
46
|
const styleVars = {
|
|
25
47
|
"--neon-duration": `${duration}s`,
|
|
26
48
|
"--neon-color1": color1,
|
|
27
49
|
"--neon-color2": color2,
|
|
28
|
-
"--neon-width": `${getWidth(animationType)}
|
|
50
|
+
"--neon-width": `${getWidth(animationType)}%`,
|
|
51
|
+
"--neon-beams": animated ? neonBeams(color1, color2, arc) : neonCorners(color1, color2),
|
|
52
|
+
"--neon-core": animated ? neonBeams(color1, color2, arc, true) : neonCorners(color1, color2, true)
|
|
29
53
|
};
|
|
30
|
-
const animated = animationType !== "none";
|
|
31
54
|
return /* @__PURE__ */ jsxs(
|
|
32
55
|
"div",
|
|
33
56
|
{
|
|
34
57
|
className: cn(
|
|
35
|
-
"neon-border-container relative z-10 inline-block h-10 w-full max-w-sm
|
|
58
|
+
"neon-border-container relative z-10 inline-block h-10 w-full max-w-sm rounded-lg p-px",
|
|
59
|
+
animated && "neon-animated",
|
|
36
60
|
className
|
|
37
61
|
),
|
|
38
62
|
style: styleVars,
|
|
39
63
|
...rest,
|
|
40
64
|
children: [
|
|
41
|
-
|
|
42
|
-
/* @__PURE__ */ jsx("
|
|
43
|
-
children
|
|
65
|
+
children,
|
|
66
|
+
/* @__PURE__ */ jsx("span", { className: "neon-tube", "aria-hidden": "true" }),
|
|
67
|
+
/* @__PURE__ */ jsxs("span", { className: "neon-light", "aria-hidden": "true", children: [
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "neon-glow", children: /* @__PURE__ */ jsx("span", {}) }),
|
|
69
|
+
/* @__PURE__ */ jsx("span", { className: "neon-core" })
|
|
70
|
+
] })
|
|
44
71
|
]
|
|
45
72
|
}
|
|
46
73
|
);
|
|
47
74
|
}
|
|
48
75
|
export {
|
|
49
|
-
NeonBorder
|
|
76
|
+
NeonBorder,
|
|
77
|
+
beamArc,
|
|
78
|
+
neonBeams,
|
|
79
|
+
neonCorners
|
|
50
80
|
};
|