apex-ui-kit 2.4.0 → 2.5.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
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/apex-ui-kit)
|
|
5
5
|
[](./Licence.md)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**96 free, animated UI components for React.** Pick one, add it with a single command, and the source lands in your project so you can read it and change anything.
|
|
8
8
|
|
|
9
9
|
Built with React, Tailwind CSS and Framer Motion, plus GSAP, Canvas and WebGL where a component needs them.
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ Built with React, Tailwind CSS and Framer Motion, plus GSAP, Canvas and WebGL wh
|
|
|
15
15
|
|
|
16
16
|
## Why ApexUI
|
|
17
17
|
|
|
18
|
-
- **
|
|
18
|
+
- **96 components** across backgrounds, cards, text effects, navigation, buttons, interactive UI, AI interfaces, loaders and maps.
|
|
19
19
|
- **You own the code.** The CLI copies plain `.jsx` files into your project, no runtime package to keep in sync.
|
|
20
20
|
- **Every docs page has** a live preview, a playground to tweak props, a Desktop / Tablet / Mobile preview, the full props table and a ready prompt you can paste into ChatGPT or Claude.
|
|
21
21
|
- **Smooth on real devices.** Animations stick to transform and opacity, respect reduced motion, and work with mouse, touch and keyboard.
|
|
@@ -99,13 +99,14 @@ Every component's props, variants and copy-ready code are on its docs page.
|
|
|
99
99
|
|
|
100
100
|
## Components
|
|
101
101
|
|
|
102
|
-
### Backgrounds (
|
|
102
|
+
### Backgrounds (14)
|
|
103
103
|
|
|
104
104
|
| Component | CLI name |
|
|
105
105
|
| --- | --- |
|
|
106
106
|
| [Aurora Background](https://apex-ui.in/components/aurora-background) | `aurora-background` |
|
|
107
107
|
| [BluePrint Background](https://apex-ui.in/components/blueprint-background) | `blueprint-background` |
|
|
108
108
|
| [Dot Wave Background](https://apex-ui.in/components/dot-wave-background) | `dot-wave-background` |
|
|
109
|
+
| [Galaxy 3D](https://apex-ui.in/components/galaxy-3d) | `galaxy-3d` |
|
|
109
110
|
| [Kinetic Threads Background](https://apex-ui.in/components/kinetic-threads-background) | `kinetic-threads-background` |
|
|
110
111
|
| [Lens Flare Background](https://apex-ui.in/components/lens-flare-background) | `lens-flare-background` |
|
|
111
112
|
| [Liquid Gradient](https://apex-ui.in/components/liquid-gradient) | `liquid-gradient` |
|
|
@@ -115,8 +116,9 @@ Every component's props, variants and copy-ready code are on its docs page.
|
|
|
115
116
|
| [Retro Grid 3D](https://apex-ui.in/components/retro-grid-3d) | `retro-grid-3d` |
|
|
116
117
|
| [Ripple Background](https://apex-ui.in/components/ripple-background) | `ripple-background` |
|
|
117
118
|
| [Smoke Background](https://apex-ui.in/components/smoke-background) | `smoke-background` |
|
|
119
|
+
| [Warp Tunnel](https://apex-ui.in/components/warp-tunnel) | `warp-tunnel` |
|
|
118
120
|
|
|
119
|
-
### Cards (
|
|
121
|
+
### Cards (18)
|
|
120
122
|
|
|
121
123
|
| Component | CLI name |
|
|
122
124
|
| --- | --- |
|
|
@@ -124,6 +126,7 @@ Every component's props, variants and copy-ready code are on its docs page.
|
|
|
124
126
|
| [Card Stack](https://apex-ui.in/components/card-stack) | `card-stack` |
|
|
125
127
|
| [Card Swap 3D](https://apex-ui.in/components/card-swap-3d) | `card-swap-3d` |
|
|
126
128
|
| [Cards](https://apex-ui.in/components/cards) | `basic-card`, `meteor-card` |
|
|
129
|
+
| [Distortion Image](https://apex-ui.in/components/distortion-image) | `distortion-image` |
|
|
127
130
|
| [Folder](https://apex-ui.in/components/folder) | `folder` |
|
|
128
131
|
| [Glare Card](https://apex-ui.in/components/glare-card) | `glare-card` |
|
|
129
132
|
| [Holo Card](https://apex-ui.in/components/holo-card) | `holo-card` |
|
|
@@ -237,9 +240,9 @@ Every component's props, variants and copy-ready code are on its docs page.
|
|
|
237
240
|
| [Dot Globe](https://apex-ui.in/components/dot-globe) | `dot-globe` |
|
|
238
241
|
| [World Map](https://apex-ui.in/components/world-map) | `world-map` |
|
|
239
242
|
|
|
240
|
-
## What's new in 2.
|
|
243
|
+
## What's new in 2.5
|
|
241
244
|
|
|
242
|
-
-
|
|
245
|
+
- 27 new components, including WebGL and three.js (Galaxy 3D, Warp Tunnel, Distortion Image, Liquid Blob 3D, Particle Morph, Retro Grid 3D), the AI voice family (Wave, Bars, Ring, Blob), Gooey Nav (WebGL metaball pill), Coverflow 3D, Card Swap 3D, 3D Text Ring, Elastic Slider, Stepper and Pixel Transition.
|
|
243
246
|
- AI Model Prompt now exports `DEFAULT_LLM_MODELS` and an index file, so you can import both from the folder.
|
|
244
247
|
- Loading Screens show real progress: `useLoadProgress` loads your images, files or code and drives the bar and the count.
|
|
245
248
|
- Hold to Confirm follows a delete flow with an `UndoNotice` that counts down before the action is final.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, SRGBColorSpace, TextureLoader, Vector2, WebGLRenderer } from "three";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Distortion Image: an image drawn in WebGL that ripples where the pointer
|
|
6
|
+
* moves. The ripple bends the picture, splits it into red / green / blue
|
|
7
|
+
* fringes and fades out smoothly when the pointer leaves. Image is "cover"
|
|
8
|
+
* fitted like CSS object-fit. Pauses off screen, frees the GPU on unmount.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const VERT = `varying vec2 vUv; void main(){ vUv = uv; gl_Position = vec4(position.xy, 0., 1.); }`;
|
|
12
|
+
|
|
13
|
+
const FRAG = `
|
|
14
|
+
uniform sampler2D uTex; uniform vec2 uPointer; uniform float uHover; uniform float uTime;
|
|
15
|
+
uniform float uStrength; uniform float uRgb; uniform vec2 uCover; uniform float uAspect;
|
|
16
|
+
varying vec2 vUv;
|
|
17
|
+
void main(){
|
|
18
|
+
// object-fit: cover
|
|
19
|
+
vec2 uv = (vUv - .5) * uCover + .5;
|
|
20
|
+
vec2 d = vUv - uPointer; d.x *= uAspect;
|
|
21
|
+
float dist = length(d);
|
|
22
|
+
float wave = sin(dist * 28. - uTime * 6.) * exp(-dist * 5.) * uHover;
|
|
23
|
+
vec2 dir = dist > 0. ? normalize(d) : vec2(0.);
|
|
24
|
+
vec2 offset = dir * wave * .03 * uStrength;
|
|
25
|
+
float split = uRgb * uHover * exp(-dist * 4.) * .012;
|
|
26
|
+
float r = texture2D(uTex, uv + offset + dir * split).r;
|
|
27
|
+
float g = texture2D(uTex, uv + offset).g;
|
|
28
|
+
float b = texture2D(uTex, uv + offset - dir * split).b;
|
|
29
|
+
gl_FragColor = vec4(r, g, b, 1.);
|
|
30
|
+
}`;
|
|
31
|
+
|
|
32
|
+
const DistortionImage = ({ src = "https://ik.imagekit.io/ny6yinyut/apexui/assets/doctorStrange.png?tr=f-auto,q-85,w-1000", alt = "", strength = 1, rgbSplit = 1, aspectRatio = "4 / 5", width = 22, rounded = "1.25rem", className = "" }) => {
|
|
33
|
+
const mount = useRef(null);
|
|
34
|
+
const opts = useRef({ strength, rgbSplit });
|
|
35
|
+
opts.current = { strength, rgbSplit };
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const el = mount.current;
|
|
39
|
+
const renderer = new WebGLRenderer({ antialias: false, alpha: true, powerPreference: "high-performance" });
|
|
40
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
41
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
42
|
+
Object.assign(renderer.domElement.style, { display: "block", width: "100%", height: "100%" });
|
|
43
|
+
el.appendChild(renderer.domElement);
|
|
44
|
+
const scene = new Scene();
|
|
45
|
+
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
|
46
|
+
const uniforms = {
|
|
47
|
+
uTex: { value: null },
|
|
48
|
+
uPointer: { value: new Vector2(0.5, 0.5) },
|
|
49
|
+
uHover: { value: 0 },
|
|
50
|
+
uTime: { value: 0 },
|
|
51
|
+
uStrength: { value: strength },
|
|
52
|
+
uRgb: { value: rgbSplit },
|
|
53
|
+
uCover: { value: new Vector2(1, 1) },
|
|
54
|
+
uAspect: { value: 1 },
|
|
55
|
+
};
|
|
56
|
+
const geometry = new PlaneGeometry(2, 2);
|
|
57
|
+
const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms });
|
|
58
|
+
scene.add(new Mesh(geometry, material));
|
|
59
|
+
|
|
60
|
+
let imgAspect = 1;
|
|
61
|
+
const fit = () => {
|
|
62
|
+
const { width, height } = el.getBoundingClientRect();
|
|
63
|
+
if (!width || !height) return;
|
|
64
|
+
renderer.setSize(width, height, false);
|
|
65
|
+
const box = width / height;
|
|
66
|
+
uniforms.uAspect.value = box;
|
|
67
|
+
// Scale UVs so the image covers the box without stretching.
|
|
68
|
+
uniforms.uCover.value.set(box < imgAspect ? box / imgAspect : 1, box < imgAspect ? 1 : imgAspect / box);
|
|
69
|
+
};
|
|
70
|
+
let texture = null;
|
|
71
|
+
let disposed = false;
|
|
72
|
+
new TextureLoader().setCrossOrigin("anonymous").load(src, (t) => {
|
|
73
|
+
if (disposed) return t.dispose();
|
|
74
|
+
t.colorSpace = SRGBColorSpace;
|
|
75
|
+
texture = t;
|
|
76
|
+
imgAspect = t.image.width / t.image.height;
|
|
77
|
+
uniforms.uTex.value = t;
|
|
78
|
+
fit();
|
|
79
|
+
});
|
|
80
|
+
const ro = new ResizeObserver(fit);
|
|
81
|
+
ro.observe(el);
|
|
82
|
+
fit();
|
|
83
|
+
|
|
84
|
+
const target = { x: 0.5, y: 0.5, hover: 0 };
|
|
85
|
+
const onMove = (e) => {
|
|
86
|
+
const r = el.getBoundingClientRect();
|
|
87
|
+
target.x = (e.clientX - r.left) / r.width;
|
|
88
|
+
target.y = 1 - (e.clientY - r.top) / r.height;
|
|
89
|
+
target.hover = 1;
|
|
90
|
+
};
|
|
91
|
+
const onLeave = () => (target.hover = 0);
|
|
92
|
+
el.addEventListener("pointermove", onMove);
|
|
93
|
+
el.addEventListener("pointerleave", onLeave);
|
|
94
|
+
let visible = true;
|
|
95
|
+
const io = new IntersectionObserver(([entry]) => (visible = entry.isIntersecting));
|
|
96
|
+
io.observe(el);
|
|
97
|
+
|
|
98
|
+
let raf = 0;
|
|
99
|
+
let last = performance.now();
|
|
100
|
+
const frame = (now) => {
|
|
101
|
+
raf = requestAnimationFrame(frame);
|
|
102
|
+
const dt = Math.min(0.05, (now - last) / 1000);
|
|
103
|
+
last = now;
|
|
104
|
+
if (!visible || !uniforms.uTex.value) return;
|
|
105
|
+
uniforms.uTime.value += dt;
|
|
106
|
+
uniforms.uPointer.value.x += (target.x - uniforms.uPointer.value.x) * 0.12;
|
|
107
|
+
uniforms.uPointer.value.y += (target.y - uniforms.uPointer.value.y) * 0.12;
|
|
108
|
+
uniforms.uHover.value += (target.hover - uniforms.uHover.value) * 0.06;
|
|
109
|
+
uniforms.uStrength.value = opts.current.strength;
|
|
110
|
+
uniforms.uRgb.value = opts.current.rgbSplit;
|
|
111
|
+
renderer.render(scene, camera);
|
|
112
|
+
};
|
|
113
|
+
raf = requestAnimationFrame(frame);
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
disposed = true;
|
|
117
|
+
cancelAnimationFrame(raf);
|
|
118
|
+
ro.disconnect();
|
|
119
|
+
io.disconnect();
|
|
120
|
+
el.removeEventListener("pointermove", onMove);
|
|
121
|
+
el.removeEventListener("pointerleave", onLeave);
|
|
122
|
+
texture?.dispose();
|
|
123
|
+
geometry.dispose();
|
|
124
|
+
material.dispose();
|
|
125
|
+
renderer.dispose();
|
|
126
|
+
renderer.forceContextLoss();
|
|
127
|
+
renderer.domElement.remove();
|
|
128
|
+
};
|
|
129
|
+
// Strength and RGB split are read live from opts; only a new image reloads.
|
|
130
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
131
|
+
}, [src]);
|
|
132
|
+
|
|
133
|
+
return <div ref={mount} role="img" aria-label={alt} className={`relative overflow-hidden bg-neutral-900 ${className}`} style={{ width: `min(${width}rem, 88vw)`, aspectRatio, borderRadius: rounded }} />;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default DistortionImage;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { AdditiveBlending, BufferAttribute, BufferGeometry, Color, PerspectiveCamera, Points, Scene, ShaderMaterial, WebGLRenderer } from "three";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Galaxy 3D: a spiral galaxy of glowing stars that slowly turns, tilted so
|
|
6
|
+
* you see its arms in depth. Colors blend from a hot core to cool arm tips,
|
|
7
|
+
* and the camera drifts with the pointer for a soft parallax. One draw call;
|
|
8
|
+
* the spin runs in the shader, so the CPU does almost nothing per frame.
|
|
9
|
+
* Put your content on top as children.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const VERT = `
|
|
13
|
+
attribute float aSize; attribute vec3 aColor; attribute float aRadius; attribute float aAngle; attribute float aHeight;
|
|
14
|
+
uniform float uTime; uniform float uSpeed; uniform float uDpr; uniform float uSize;
|
|
15
|
+
varying vec3 vColor;
|
|
16
|
+
void main(){
|
|
17
|
+
// Inner stars orbit faster than outer ones.
|
|
18
|
+
float a = aAngle + uTime * uSpeed * (0.6 / (0.35 + aRadius));
|
|
19
|
+
vec3 p = vec3(cos(a) * aRadius, aHeight, sin(a) * aRadius);
|
|
20
|
+
vec4 mv = modelViewMatrix * vec4(p, 1.);
|
|
21
|
+
gl_PointSize = aSize * uSize * uDpr * (18. / -mv.z);
|
|
22
|
+
gl_Position = projectionMatrix * mv;
|
|
23
|
+
vColor = aColor;
|
|
24
|
+
}`;
|
|
25
|
+
|
|
26
|
+
const FRAG = `
|
|
27
|
+
varying vec3 vColor;
|
|
28
|
+
void main(){
|
|
29
|
+
float d = length(gl_PointCoord - .5);
|
|
30
|
+
float a = pow(smoothstep(.5, 0., d), 1.8);
|
|
31
|
+
gl_FragColor = vec4(vColor * a * 1.35, a);
|
|
32
|
+
}`;
|
|
33
|
+
|
|
34
|
+
const Galaxy3D = ({ count = 30000, arms = 4, spin = 1.2, colors = ["#fde68a", "#a78bfa", "#22d3ee"], speed = 1, starSize = 1, tilt = 0.9, height = "100%", children, className = "" }) => {
|
|
35
|
+
const mount = useRef(null);
|
|
36
|
+
const opts = useRef({ speed, starSize });
|
|
37
|
+
opts.current = { speed, starSize };
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const el = mount.current;
|
|
41
|
+
const renderer = new WebGLRenderer({ antialias: false, alpha: true, powerPreference: "high-performance" });
|
|
42
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 1.75);
|
|
43
|
+
renderer.setPixelRatio(dpr);
|
|
44
|
+
Object.assign(renderer.domElement.style, { display: "block", position: "absolute", inset: "0", width: "100%", height: "100%" });
|
|
45
|
+
el.appendChild(renderer.domElement);
|
|
46
|
+
const scene = new Scene();
|
|
47
|
+
const camera = new PerspectiveCamera(55, 1, 0.1, 100);
|
|
48
|
+
|
|
49
|
+
const radius = new Float32Array(count);
|
|
50
|
+
const angle = new Float32Array(count);
|
|
51
|
+
const heightArr = new Float32Array(count);
|
|
52
|
+
const sizes = new Float32Array(count);
|
|
53
|
+
const cols = new Float32Array(count * 3);
|
|
54
|
+
const inner = new Color(colors[0]);
|
|
55
|
+
const mid = new Color(colors[1] ?? colors[0]);
|
|
56
|
+
const outer = new Color(colors[2] ?? colors[1] ?? colors[0]);
|
|
57
|
+
const c = new Color();
|
|
58
|
+
const R = 5;
|
|
59
|
+
for (let i = 0; i < count; i++) {
|
|
60
|
+
const r = Math.pow(Math.random(), 1.6) * R;
|
|
61
|
+
const arm = ((i % arms) / arms) * Math.PI * 2;
|
|
62
|
+
// Scatter around the arm, tighter in the core.
|
|
63
|
+
const scatter = (Math.random() - 0.5) * (0.35 + r * 0.12);
|
|
64
|
+
radius[i] = r + (Math.random() - 0.5) * 0.25;
|
|
65
|
+
angle[i] = arm + r * spin + scatter;
|
|
66
|
+
heightArr[i] = (Math.random() - 0.5) * 0.35 * Math.exp(-r * 0.45);
|
|
67
|
+
sizes[i] = 0.5 + Math.random() * 1.6 * (r < 0.6 ? 1.8 : 1);
|
|
68
|
+
const t = r / R;
|
|
69
|
+
if (t < 0.5) c.copy(inner).lerp(mid, t * 2);
|
|
70
|
+
else c.copy(mid).lerp(outer, (t - 0.5) * 2);
|
|
71
|
+
cols.set([c.r, c.g, c.b], i * 3);
|
|
72
|
+
}
|
|
73
|
+
const geometry = new BufferGeometry();
|
|
74
|
+
geometry.setAttribute("position", new BufferAttribute(new Float32Array(count * 3), 3));
|
|
75
|
+
geometry.setAttribute("aRadius", new BufferAttribute(radius, 1));
|
|
76
|
+
geometry.setAttribute("aAngle", new BufferAttribute(angle, 1));
|
|
77
|
+
geometry.setAttribute("aHeight", new BufferAttribute(heightArr, 1));
|
|
78
|
+
geometry.setAttribute("aSize", new BufferAttribute(sizes, 1));
|
|
79
|
+
geometry.setAttribute("aColor", new BufferAttribute(cols, 3));
|
|
80
|
+
geometry.boundingSphere = null;
|
|
81
|
+
const uniforms = { uTime: { value: 0 }, uSpeed: { value: speed }, uDpr: { value: dpr }, uSize: { value: starSize } };
|
|
82
|
+
const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms, transparent: true, depthWrite: false, blending: AdditiveBlending });
|
|
83
|
+
const points = new Points(geometry, material);
|
|
84
|
+
points.frustumCulled = false;
|
|
85
|
+
points.rotation.x = tilt;
|
|
86
|
+
scene.add(points);
|
|
87
|
+
|
|
88
|
+
const resize = () => {
|
|
89
|
+
const { width, height: h } = el.getBoundingClientRect();
|
|
90
|
+
if (!width || !h) return;
|
|
91
|
+
renderer.setSize(width, h, false);
|
|
92
|
+
camera.aspect = width / h;
|
|
93
|
+
camera.updateProjectionMatrix();
|
|
94
|
+
};
|
|
95
|
+
const ro = new ResizeObserver(resize);
|
|
96
|
+
ro.observe(el);
|
|
97
|
+
resize();
|
|
98
|
+
|
|
99
|
+
const pointer = { x: 0, y: 0 };
|
|
100
|
+
const onMove = (e) => {
|
|
101
|
+
const r = el.getBoundingClientRect();
|
|
102
|
+
pointer.x = ((e.clientX - r.left) / r.width) * 2 - 1;
|
|
103
|
+
pointer.y = ((e.clientY - r.top) / r.height) * 2 - 1;
|
|
104
|
+
};
|
|
105
|
+
el.addEventListener("pointermove", onMove);
|
|
106
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
107
|
+
let visible = true;
|
|
108
|
+
const io = new IntersectionObserver(([entry]) => (visible = entry.isIntersecting));
|
|
109
|
+
io.observe(el);
|
|
110
|
+
|
|
111
|
+
let raf = 0;
|
|
112
|
+
let last = performance.now();
|
|
113
|
+
const cam = { x: 0, y: 2.2 };
|
|
114
|
+
const frame = (now) => {
|
|
115
|
+
raf = requestAnimationFrame(frame);
|
|
116
|
+
const dt = Math.min(0.05, (now - last) / 1000);
|
|
117
|
+
last = now;
|
|
118
|
+
if (!visible) return;
|
|
119
|
+
uniforms.uTime.value += dt * (reduced ? 0.1 : 1);
|
|
120
|
+
uniforms.uSpeed.value = opts.current.speed;
|
|
121
|
+
uniforms.uSize.value = opts.current.starSize;
|
|
122
|
+
cam.x += (pointer.x * 1.2 - cam.x) * 0.03;
|
|
123
|
+
cam.y += (2.2 - pointer.y * 0.8 - cam.y) * 0.03;
|
|
124
|
+
camera.position.set(cam.x, cam.y, 7.5);
|
|
125
|
+
camera.lookAt(0, 0, 0);
|
|
126
|
+
renderer.render(scene, camera);
|
|
127
|
+
};
|
|
128
|
+
raf = requestAnimationFrame(frame);
|
|
129
|
+
|
|
130
|
+
return () => {
|
|
131
|
+
cancelAnimationFrame(raf);
|
|
132
|
+
ro.disconnect();
|
|
133
|
+
io.disconnect();
|
|
134
|
+
el.removeEventListener("pointermove", onMove);
|
|
135
|
+
geometry.dispose();
|
|
136
|
+
material.dispose();
|
|
137
|
+
renderer.dispose();
|
|
138
|
+
renderer.forceContextLoss();
|
|
139
|
+
renderer.domElement.remove();
|
|
140
|
+
};
|
|
141
|
+
// Star layout rebuilds on these; speed and size are read live.
|
|
142
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
143
|
+
}, [count, arms, spin, tilt, colors.join(",")]);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div ref={mount} className={`relative isolate w-full overflow-hidden bg-[radial-gradient(ellipse_at_center,#0b1026_0%,#02030a_70%)] ${className}`} style={{ height }}>
|
|
147
|
+
{children && <div className="pointer-events-none relative z-10 flex h-full w-full items-center justify-center [&>*]:pointer-events-auto">{children}</div>}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default Galaxy3D;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { Color, Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, Vector2, WebGLRenderer } from "three";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Warp Tunnel: a hyperspace tunnel drawn entirely in one fragment shader.
|
|
6
|
+
* Glowing rings and streaks rush toward you and the tunnel bends toward the
|
|
7
|
+
* pointer. Hold the mouse (or touch) to boost into warp speed. Cheap: one
|
|
8
|
+
* full-screen triangle pair, no geometry to update. Content goes on top.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const VERT = `varying vec2 vUv; void main(){ vUv = uv; gl_Position = vec4(position.xy, 0., 1.); }`;
|
|
12
|
+
|
|
13
|
+
const FRAG = `
|
|
14
|
+
uniform float uTime; uniform vec2 uRes; uniform vec2 uBend; uniform float uBoost;
|
|
15
|
+
uniform vec3 uC1; uniform vec3 uC2; uniform float uDensity;
|
|
16
|
+
varying vec2 vUv;
|
|
17
|
+
float hash(vec2 p){ return fract(sin(dot(p, vec2(41.3, 289.1))) * 45758.5453); }
|
|
18
|
+
void main(){
|
|
19
|
+
vec2 p = (vUv - .5) * vec2(uRes.x / uRes.y, 1.);
|
|
20
|
+
p -= uBend * .25 * length(p); // bend toward the pointer
|
|
21
|
+
float r = length(p);
|
|
22
|
+
float a = atan(p.y, p.x);
|
|
23
|
+
float depth = .35 / max(r, .001) + uTime; // tunnel coordinate
|
|
24
|
+
// Rings rushing forward
|
|
25
|
+
float rings = smoothstep(.92, 1., sin(depth * 6.2831 * .5 * uDensity) * .5 + .5);
|
|
26
|
+
// Streaks: random angular slices that flicker along the tunnel
|
|
27
|
+
float slice = floor(a / 6.2831 * 64.);
|
|
28
|
+
float h = hash(vec2(slice, floor(depth * 2.)));
|
|
29
|
+
float streak = step(.82, h) * smoothstep(.0, .8, fract(depth * 2.)) * (1. - fract(depth * 2.));
|
|
30
|
+
float glow = (rings * .8 + streak * (1.2 + uBoost * 2.)) * smoothstep(.0, .25, r);
|
|
31
|
+
vec3 col = mix(uC1, uC2, .5 + .5 * sin(a * 2. + depth * .6));
|
|
32
|
+
float core = smoothstep(.5, .0, r) * (.25 + uBoost * .5);
|
|
33
|
+
vec3 c = col * glow * (1.4 - r) + col * core;
|
|
34
|
+
c *= smoothstep(1.2, .2, r); // vignette
|
|
35
|
+
gl_FragColor = vec4(c, 1.);
|
|
36
|
+
}`;
|
|
37
|
+
|
|
38
|
+
const WarpTunnel = ({ colors = ["#a3e635", "#8b5cf6"], speed = 1, density = 1, interactive = true, height = "100%", children, className = "" }) => {
|
|
39
|
+
const mount = useRef(null);
|
|
40
|
+
const opts = useRef({ colors, speed, density, interactive });
|
|
41
|
+
opts.current = { colors, speed, density, interactive };
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const el = mount.current;
|
|
45
|
+
const renderer = new WebGLRenderer({ antialias: false, powerPreference: "high-performance" });
|
|
46
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
|
|
47
|
+
Object.assign(renderer.domElement.style, { display: "block", position: "absolute", inset: "0", width: "100%", height: "100%" });
|
|
48
|
+
el.appendChild(renderer.domElement);
|
|
49
|
+
const scene = new Scene();
|
|
50
|
+
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
|
51
|
+
const uniforms = {
|
|
52
|
+
uTime: { value: 0 },
|
|
53
|
+
uRes: { value: new Vector2(1, 1) },
|
|
54
|
+
uBend: { value: new Vector2(0, 0) },
|
|
55
|
+
uBoost: { value: 0 },
|
|
56
|
+
uC1: { value: new Color(colors[0]) },
|
|
57
|
+
uC2: { value: new Color(colors[1] ?? colors[0]) },
|
|
58
|
+
uDensity: { value: density },
|
|
59
|
+
};
|
|
60
|
+
const geometry = new PlaneGeometry(2, 2);
|
|
61
|
+
const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms });
|
|
62
|
+
scene.add(new Mesh(geometry, material));
|
|
63
|
+
|
|
64
|
+
const resize = () => {
|
|
65
|
+
const { width, height: h } = el.getBoundingClientRect();
|
|
66
|
+
if (!width || !h) return;
|
|
67
|
+
renderer.setSize(width, h, false);
|
|
68
|
+
uniforms.uRes.value.set(width, h);
|
|
69
|
+
};
|
|
70
|
+
const ro = new ResizeObserver(resize);
|
|
71
|
+
ro.observe(el);
|
|
72
|
+
resize();
|
|
73
|
+
|
|
74
|
+
const target = { x: 0, y: 0, boost: 0 };
|
|
75
|
+
const onMove = (e) => {
|
|
76
|
+
const r = el.getBoundingClientRect();
|
|
77
|
+
target.x = ((e.clientX - r.left) / r.width) * 2 - 1;
|
|
78
|
+
target.y = -(((e.clientY - r.top) / r.height) * 2 - 1);
|
|
79
|
+
};
|
|
80
|
+
const onDown = () => (target.boost = 1);
|
|
81
|
+
const onUp = () => (target.boost = 0);
|
|
82
|
+
const onLeave = () => {
|
|
83
|
+
target.x = 0;
|
|
84
|
+
target.y = 0;
|
|
85
|
+
target.boost = 0;
|
|
86
|
+
};
|
|
87
|
+
el.addEventListener("pointermove", onMove);
|
|
88
|
+
el.addEventListener("pointerdown", onDown);
|
|
89
|
+
el.addEventListener("pointerup", onUp);
|
|
90
|
+
el.addEventListener("pointerleave", onLeave);
|
|
91
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
92
|
+
let visible = true;
|
|
93
|
+
const io = new IntersectionObserver(([entry]) => (visible = entry.isIntersecting));
|
|
94
|
+
io.observe(el);
|
|
95
|
+
|
|
96
|
+
let raf = 0;
|
|
97
|
+
let last = performance.now();
|
|
98
|
+
const frame = (now) => {
|
|
99
|
+
raf = requestAnimationFrame(frame);
|
|
100
|
+
const dt = Math.min(0.05, (now - last) / 1000);
|
|
101
|
+
last = now;
|
|
102
|
+
if (!visible) return;
|
|
103
|
+
const o = opts.current;
|
|
104
|
+
const active = o.interactive ? target : { x: 0, y: 0, boost: 0 };
|
|
105
|
+
uniforms.uBoost.value += (active.boost - uniforms.uBoost.value) * 0.05;
|
|
106
|
+
uniforms.uTime.value += dt * o.speed * (reduced ? 0.15 : 1) * (1 + uniforms.uBoost.value * 3);
|
|
107
|
+
uniforms.uBend.value.x += (active.x - uniforms.uBend.value.x) * 0.05;
|
|
108
|
+
uniforms.uBend.value.y += (active.y - uniforms.uBend.value.y) * 0.05;
|
|
109
|
+
uniforms.uC1.value.set(o.colors[0]);
|
|
110
|
+
uniforms.uC2.value.set(o.colors[1] ?? o.colors[0]);
|
|
111
|
+
uniforms.uDensity.value = o.density;
|
|
112
|
+
renderer.render(scene, camera);
|
|
113
|
+
};
|
|
114
|
+
raf = requestAnimationFrame(frame);
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
cancelAnimationFrame(raf);
|
|
118
|
+
ro.disconnect();
|
|
119
|
+
io.disconnect();
|
|
120
|
+
el.removeEventListener("pointermove", onMove);
|
|
121
|
+
el.removeEventListener("pointerdown", onDown);
|
|
122
|
+
el.removeEventListener("pointerup", onUp);
|
|
123
|
+
el.removeEventListener("pointerleave", onLeave);
|
|
124
|
+
geometry.dispose();
|
|
125
|
+
material.dispose();
|
|
126
|
+
renderer.dispose();
|
|
127
|
+
renderer.forceContextLoss();
|
|
128
|
+
renderer.domElement.remove();
|
|
129
|
+
};
|
|
130
|
+
// Everything visual is read live from opts.
|
|
131
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div ref={mount} className={`relative isolate w-full touch-none select-none overflow-hidden bg-black ${className}`} style={{ height }}>
|
|
136
|
+
{children && <div className="pointer-events-none relative z-10 flex h-full w-full items-center justify-center [&>*]:pointer-events-auto">{children}</div>}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export default WarpTunnel;
|
package/dist/cli/index.js
CHANGED
|
@@ -117,6 +117,9 @@ const componentMap = {
|
|
|
117
117
|
"liquid-blob-3d": "LiquidBlob3D",
|
|
118
118
|
"particle-morph": "ParticleMorph",
|
|
119
119
|
"retro-grid-3d": "RetroGrid3D",
|
|
120
|
+
"distortion-image": "DistortionImage",
|
|
121
|
+
"galaxy-3d": "Galaxy3D",
|
|
122
|
+
"warp-tunnel": "WarpTunnel",
|
|
120
123
|
"water-drop-reveal": "MaskView",
|
|
121
124
|
};
|
|
122
125
|
// Packages only some components need, installed on top of the shared ones.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apex-ui-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "96 free, animated UI components for React, styled with Tailwind CSS. Add any of them with one CLI command.",
|
|
6
6
|
"homepage": "https://apex-ui.in",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|