apex-ui-kit 2.10.0 → 2.12.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.
@@ -1,235 +1,237 @@
1
- import React, { useEffect, useRef } from "react";
2
- import { Renderer, Program, Mesh, Triangle } from "ogl";
3
-
4
- const hexToRgb = (hex) => {
5
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6
- if (!result) return [1, 0.5, 0.2];
7
- return [
8
- parseInt(result[1], 16) / 255,
9
- parseInt(result[2], 16) / 255,
10
- parseInt(result[3], 16) / 255,
11
- ];
12
- };
13
-
14
- const vertex = `#version 300 es
15
- precision highp float;
16
- in vec2 position;
17
- in vec2 uv;
18
- out vec2 vUv;
19
- void main() {
20
- vUv = uv;
21
- gl_Position = vec4(position, 0.0, 1.0);
22
- }
23
- `;
24
-
25
- const fragment = `#version 300 es
26
- precision highp float;
27
- uniform vec2 iResolution;
28
- uniform float iTime;
29
- uniform vec3 uCustomColor;
30
- uniform float uUseCustomColor;
31
- uniform float uSpeed;
32
- uniform float uDirection;
33
- uniform float uScale;
34
- uniform float uOpacity;
35
- uniform vec2 uMouse;
36
- uniform float uMouseInteractive;
37
- out vec4 fragColor;
38
-
39
- void mainImage(out vec4 o, vec2 C) {
40
- vec2 center = iResolution.xy * 0.5;
41
- C = (C - center) / uScale + center;
42
-
43
- vec2 mouseOffset = (uMouse - center) * 0.0002;
44
- C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);
45
-
46
- float i, d, z, T = iTime * uSpeed * uDirection;
47
- vec3 O, p, S;
48
-
49
- // loop count reduced to 30
50
- for (vec2 r = iResolution.xy, Q; ++i < 35.; O += o.w/d*o.xyz) {
51
- p = z*normalize(vec3(C-.5*r,r.y));
52
- p.z -= 4.;
53
- S = p;
54
- d = p.y-T;
55
-
56
- p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
57
- Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
58
- z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;
59
- o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
60
- }
61
-
62
- o.xyz = tanh(O/1e4);
63
- }
64
-
65
- bool finite1(float x){ return !(isnan(x) || isinf(x)); }
66
- vec3 sanitize(vec3 c){
67
- return vec3(
68
- finite1(c.r) ? c.r : 0.0,
69
- finite1(c.g) ? c.g : 0.0,
70
- finite1(c.b) ? c.b : 0.0
71
- );
72
- }
73
-
74
- void main() {
75
- vec4 o = vec4(0.0);
76
- mainImage(o, gl_FragCoord.xy);
77
- vec3 rgb = sanitize(o.rgb);
78
-
79
- float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
80
- vec3 customColor = intensity * uCustomColor;
81
- vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));
82
-
83
- float alpha = length(rgb) * uOpacity;
84
- fragColor = vec4(finalColor, alpha);
85
- }
86
- `;
87
-
88
-
89
- export const SmokeBackground = ({
90
- color = "#00c950",
91
- speed = 1,
92
- direction = "forward",
93
- scale = 1,
94
- opacity = 1,
95
- mouseInteractive = true,
96
- className = "",
97
- maxWidth = 'max-w-4xl',
98
- }) => {
99
- const containerRef = useRef(null);
100
- const mousePos = useRef({ x: 0, y: 0 });
101
-
102
- useEffect(() => {
103
- const mountNode = containerRef.current;
104
- if (!mountNode) return;
105
-
106
- const useCustomColor = color ? 1.0 : 0.0;
107
- const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
108
-
109
- let directionMultiplier = direction === "backward" ? -1.0 : 1.0;
110
-
111
- // cap DPR depending on mount width to avoid heavy pixel loads on small devices
112
- const mountWidth = mountNode.clientWidth || window.innerWidth;
113
- const dprCap = mountWidth > 1200 ? 1.8 : (mountWidth > 768 ? 1.4 : 1.0);
114
- const dpr = Math.min(window.devicePixelRatio || 1, dprCap);
115
-
116
- const renderer = new Renderer({
117
- webgl: 2,
118
- alpha: true,
119
- antialias: false,
120
- dpr,
121
- });
122
-
123
- const gl = renderer.gl;
124
- const canvas = gl.canvas;
125
- canvas.style.display = "block";
126
- canvas.style.width = "100%";
127
- canvas.style.height = "100%";
128
- mountNode.appendChild(canvas);
129
-
130
- const geometry = new Triangle(gl);
131
-
132
- const program = new Program(gl, {
133
- vertex: vertex,
134
- fragment: fragment,
135
- uniforms: {
136
- iTime: { value: 0 },
137
- iResolution: { value: new Float32Array([1, 1]) },
138
- uCustomColor: { value: new Float32Array(customColorRgb) },
139
- uUseCustomColor: { value: useCustomColor },
140
- uSpeed: { value: speed * 0.4 },
141
- uDirection: { value: directionMultiplier },
142
- uScale: { value: scale },
143
- uOpacity: { value: opacity },
144
- uMouse: { value: new Float32Array([0, 0]) },
145
- uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },
146
- },
147
- });
148
-
149
- const mesh = new Mesh(gl, { geometry, program });
150
-
151
- // 🖱 Mouse throttled to RAF
152
- let mouseNeedsUpdate = false;
153
- const handleMouseMove = (e) => {
154
- if (!mouseInteractive) return;
155
- const rect = mountNode.getBoundingClientRect();
156
- mousePos.current.x = e.clientX - rect.left;
157
- mousePos.current.y = rect.height - (e.clientY - rect.top);
158
- mouseNeedsUpdate = true;
159
- };
160
- if (mouseInteractive) {
161
- mountNode.addEventListener("mousemove", handleMouseMove, { passive: true });
162
- }
163
-
164
- const setSize = () => {
165
- const rect = mountNode.getBoundingClientRect();
166
- const width = Math.max(1, Math.floor(rect.width));
167
- const height = Math.max(1, Math.floor(rect.height));
168
- renderer.setSize(width, height);
169
- const res = program.uniforms.iResolution.value;
170
- res[0] = gl.drawingBufferWidth;
171
- res[1] = gl.drawingBufferHeight;
172
- };
173
-
174
- const ro = new ResizeObserver(() => {
175
- requestAnimationFrame(setSize);
176
- });
177
- ro.observe(mountNode);
178
- setSize();
179
-
180
- let raf = 0;
181
- // Only render while the background is on screen.
182
- let visible = true;
183
- const t0 = performance.now();
184
- const loop = (t) => {
185
- raf = 0;
186
- if (!visible) return;
187
- let timeValue = (t - t0) * 0.001;
188
-
189
- if (direction === "funny") {
190
- const cycle = Math.sin(timeValue * 0.5);
191
- program.uniforms.uDirection.value = cycle;
192
- }
193
-
194
- if (mouseNeedsUpdate) {
195
- const mouseUniform = program.uniforms.uMouse.value;
196
- mouseUniform[0] = mousePos.current.x;
197
- mouseUniform[1] = mousePos.current.y;
198
- mouseNeedsUpdate = false;
199
- }
200
-
201
- program.uniforms.iTime.value = timeValue;
202
- renderer.render({ scene: mesh });
203
- raf = requestAnimationFrame(loop);
204
- };
205
- raf = requestAnimationFrame(loop);
206
- const visibilityObserver = new IntersectionObserver(([entry]) => {
207
- visible = entry.isIntersecting;
208
- if (visible && !raf) raf = requestAnimationFrame(loop);
209
- });
210
- visibilityObserver.observe(mountNode);
211
-
212
- return () => {
213
- cancelAnimationFrame(raf);
214
- visibilityObserver.disconnect();
215
- ro.disconnect();
216
- if (mouseInteractive && mountNode) {
217
- mountNode.removeEventListener("mousemove", handleMouseMove);
218
- }
219
- try {
220
- mountNode.removeChild(canvas);
221
- } catch (error) { console.error("Error removing canvas:", error); }
222
- gl.getExtension("WEBGL_lose_context")?.loseContext();
223
- };
224
- }, [color, speed, direction, scale, opacity, mouseInteractive, className]);
225
-
226
- return (
227
- <div className={`w-full ${className}`}>
228
- <div className={`mx-auto w-full ${maxWidth} h-[100vh] lg:h-[90vh] overflow-hidden relative`}>
229
- <div ref={containerRef} className="absolute inset-0 w-full h-full" />
230
- </div>
231
- </div>
232
- );
233
- };
234
-
235
- export default SmokeBackground;
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Renderer, Program, Mesh, Triangle } from "ogl";
3
+
4
+ const hexToRgb = (hex) => {
5
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6
+ if (!result) return [1, 0.5, 0.2];
7
+ return [
8
+ parseInt(result[1], 16) / 255,
9
+ parseInt(result[2], 16) / 255,
10
+ parseInt(result[3], 16) / 255,
11
+ ];
12
+ };
13
+
14
+ const vertex = `#version 300 es
15
+ precision highp float;
16
+ in vec2 position;
17
+ in vec2 uv;
18
+ out vec2 vUv;
19
+ void main() {
20
+ vUv = uv;
21
+ gl_Position = vec4(position, 0.0, 1.0);
22
+ }
23
+ `;
24
+
25
+ const fragment = `#version 300 es
26
+ precision highp float;
27
+ uniform vec2 iResolution;
28
+ uniform float iTime;
29
+ uniform vec3 uCustomColor;
30
+ uniform float uUseCustomColor;
31
+ uniform float uSpeed;
32
+ uniform float uDirection;
33
+ uniform float uScale;
34
+ uniform float uOpacity;
35
+ uniform vec2 uMouse;
36
+ uniform float uMouseInteractive;
37
+ out vec4 fragColor;
38
+
39
+ void mainImage(out vec4 o, vec2 C) {
40
+ vec2 center = iResolution.xy * 0.5;
41
+ C = (C - center) / uScale + center;
42
+
43
+ vec2 mouseOffset = (uMouse - center) * 0.0002;
44
+ C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);
45
+
46
+ float i, d, z, T = iTime * uSpeed * uDirection;
47
+ vec3 O, p, S;
48
+
49
+ // loop count reduced to 30
50
+ for (vec2 r = iResolution.xy, Q; ++i < 35.; O += o.w/d*o.xyz) {
51
+ p = z*normalize(vec3(C-.5*r,r.y));
52
+ p.z -= 4.;
53
+ S = p;
54
+ d = p.y-T;
55
+
56
+ p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
57
+ Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
58
+ z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;
59
+ o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
60
+ }
61
+
62
+ o.xyz = tanh(O/1e4);
63
+ }
64
+
65
+ bool finite1(float x){ return !(isnan(x) || isinf(x)); }
66
+ vec3 sanitize(vec3 c){
67
+ return vec3(
68
+ finite1(c.r) ? c.r : 0.0,
69
+ finite1(c.g) ? c.g : 0.0,
70
+ finite1(c.b) ? c.b : 0.0
71
+ );
72
+ }
73
+
74
+ void main() {
75
+ vec4 o = vec4(0.0);
76
+ mainImage(o, gl_FragCoord.xy);
77
+ vec3 rgb = sanitize(o.rgb);
78
+
79
+ float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
80
+ vec3 customColor = intensity * uCustomColor;
81
+ vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));
82
+
83
+ float alpha = length(rgb) * uOpacity;
84
+ fragColor = vec4(finalColor, alpha);
85
+ }
86
+ `;
87
+
88
+
89
+ export const SmokeBackground = ({
90
+ color = "#00c950",
91
+ speed = 1,
92
+ direction = "forward",
93
+ scale = 1,
94
+ opacity = 1,
95
+ mouseInteractive = true,
96
+ className = "",
97
+ maxWidth = 'max-w-4xl',
98
+ }) => {
99
+ const containerRef = useRef(null);
100
+ const mousePos = useRef({ x: 0, y: 0 });
101
+
102
+ useEffect(() => {
103
+ const mountNode = containerRef.current;
104
+ if (!mountNode) return;
105
+
106
+ const useCustomColor = color ? 1.0 : 0.0;
107
+ const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
108
+
109
+ let directionMultiplier = direction === "backward" ? -1.0 : 1.0;
110
+
111
+ // cap DPR depending on mount width to avoid heavy pixel loads on small devices
112
+ const mountWidth = mountNode.clientWidth || window.innerWidth;
113
+ const dprCap = mountWidth > 1200 ? 1.8 : (mountWidth > 768 ? 1.4 : 1.0);
114
+ // The smoke is soft, so rendering at ~70% resolution and letting the browser upscale
115
+ // looks the same while the 35-step raymarch runs on half the pixels.
116
+ const dpr = Math.min(window.devicePixelRatio || 1, dprCap) * 0.7;
117
+
118
+ const renderer = new Renderer({
119
+ webgl: 2,
120
+ alpha: true,
121
+ antialias: false,
122
+ dpr,
123
+ });
124
+
125
+ const gl = renderer.gl;
126
+ const canvas = gl.canvas;
127
+ canvas.style.display = "block";
128
+ canvas.style.width = "100%";
129
+ canvas.style.height = "100%";
130
+ mountNode.appendChild(canvas);
131
+
132
+ const geometry = new Triangle(gl);
133
+
134
+ const program = new Program(gl, {
135
+ vertex: vertex,
136
+ fragment: fragment,
137
+ uniforms: {
138
+ iTime: { value: 0 },
139
+ iResolution: { value: new Float32Array([1, 1]) },
140
+ uCustomColor: { value: new Float32Array(customColorRgb) },
141
+ uUseCustomColor: { value: useCustomColor },
142
+ uSpeed: { value: speed * 0.4 },
143
+ uDirection: { value: directionMultiplier },
144
+ uScale: { value: scale },
145
+ uOpacity: { value: opacity },
146
+ uMouse: { value: new Float32Array([0, 0]) },
147
+ uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },
148
+ },
149
+ });
150
+
151
+ const mesh = new Mesh(gl, { geometry, program });
152
+
153
+ // 🖱 Mouse throttled to RAF
154
+ let mouseNeedsUpdate = false;
155
+ const handleMouseMove = (e) => {
156
+ if (!mouseInteractive) return;
157
+ const rect = mountNode.getBoundingClientRect();
158
+ mousePos.current.x = e.clientX - rect.left;
159
+ mousePos.current.y = rect.height - (e.clientY - rect.top);
160
+ mouseNeedsUpdate = true;
161
+ };
162
+ if (mouseInteractive) {
163
+ mountNode.addEventListener("mousemove", handleMouseMove, { passive: true });
164
+ }
165
+
166
+ const setSize = () => {
167
+ const rect = mountNode.getBoundingClientRect();
168
+ const width = Math.max(1, Math.floor(rect.width));
169
+ const height = Math.max(1, Math.floor(rect.height));
170
+ renderer.setSize(width, height);
171
+ const res = program.uniforms.iResolution.value;
172
+ res[0] = gl.drawingBufferWidth;
173
+ res[1] = gl.drawingBufferHeight;
174
+ };
175
+
176
+ const ro = new ResizeObserver(() => {
177
+ requestAnimationFrame(setSize);
178
+ });
179
+ ro.observe(mountNode);
180
+ setSize();
181
+
182
+ let raf = 0;
183
+ // Only render while the background is on screen.
184
+ let visible = true;
185
+ const t0 = performance.now();
186
+ const loop = (t) => {
187
+ raf = 0;
188
+ if (!visible) return;
189
+ let timeValue = (t - t0) * 0.001;
190
+
191
+ if (direction === "funny") {
192
+ const cycle = Math.sin(timeValue * 0.5);
193
+ program.uniforms.uDirection.value = cycle;
194
+ }
195
+
196
+ if (mouseNeedsUpdate) {
197
+ const mouseUniform = program.uniforms.uMouse.value;
198
+ mouseUniform[0] = mousePos.current.x;
199
+ mouseUniform[1] = mousePos.current.y;
200
+ mouseNeedsUpdate = false;
201
+ }
202
+
203
+ program.uniforms.iTime.value = timeValue;
204
+ renderer.render({ scene: mesh });
205
+ raf = requestAnimationFrame(loop);
206
+ };
207
+ raf = requestAnimationFrame(loop);
208
+ const visibilityObserver = new IntersectionObserver(([entry]) => {
209
+ visible = entry.isIntersecting;
210
+ if (visible && !raf) raf = requestAnimationFrame(loop);
211
+ });
212
+ visibilityObserver.observe(mountNode);
213
+
214
+ return () => {
215
+ cancelAnimationFrame(raf);
216
+ visibilityObserver.disconnect();
217
+ ro.disconnect();
218
+ if (mouseInteractive && mountNode) {
219
+ mountNode.removeEventListener("mousemove", handleMouseMove);
220
+ }
221
+ try {
222
+ mountNode.removeChild(canvas);
223
+ } catch (error) { console.error("Error removing canvas:", error); }
224
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
225
+ };
226
+ }, [color, speed, direction, scale, opacity, mouseInteractive, className]);
227
+
228
+ return (
229
+ <div className={`w-full ${className}`}>
230
+ <div className={`mx-auto w-full ${maxWidth} h-[100vh] lg:h-[90vh] overflow-hidden relative`}>
231
+ <div ref={containerRef} className="absolute inset-0 w-full h-full" />
232
+ </div>
233
+ </div>
234
+ );
235
+ };
236
+
237
+ export default SmokeBackground;