@toriistudio/shader-ui 0.0.1

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Torii Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # 🎨 Shader UI
2
+
3
+ Shader components FTW!
4
+
5
+ ---
6
+
7
+ ## πŸš€ Installation
8
+
9
+ Install the package and its peer dependencies:
10
+
11
+ ```bash
12
+ npm install @toriistudio/shader-ui
13
+ # or
14
+ yarn add @toriistudio/shader-ui
15
+ ```
16
+
17
+ ## πŸ“„ License
18
+
19
+ MIT
20
+
21
+ ## 🀝 Contributing
22
+
23
+ We welcome contributions!
24
+
25
+ If you'd like to improve the playground, add new features, or fix bugs:
26
+
27
+ 1. **Fork** this repository
28
+ 2. **Clone** your fork: `git clone https://github.com/your-username/shader-ui`
29
+ 3. **Install** dependencies: `yarn` or `npm install`
30
+ 4. Make your changes in a branch: `git checkout -b my-new-feature`
31
+ 5. **Push** your branch and open a pull request
32
+
33
+ Before submitting a PR:
34
+
35
+ - Run `yarn build` to ensure everything compiles
36
+ - Make sure the playground runs without errors (`yalc push` or `npm link` for local testing)
37
+ - Keep the code style clean and consistent
38
+
39
+ We’re excited to see what you’ll build πŸŽ¨πŸš€
@@ -0,0 +1,45 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComponentProps } from 'react';
3
+
4
+ type ShaderUniforms = {
5
+ uIterations: number;
6
+ uAmplitude: number;
7
+ uFreq: number;
8
+ };
9
+ type ShaderArtProps = {
10
+ uniforms: ShaderUniforms;
11
+ } & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
12
+ width?: string | number;
13
+ height?: string | number;
14
+ };
15
+ declare function ShaderArt({ uniforms, className, style, width, height, ...divProps }: ShaderArtProps): react_jsx_runtime.JSX.Element;
16
+
17
+ type FractalFlowerProps = {
18
+ timeScale: number;
19
+ petalRadius: number;
20
+ scale: number;
21
+ intensity: number;
22
+ morphCycle: number;
23
+ color: string;
24
+ } & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
25
+ width?: string | number;
26
+ height?: string | number;
27
+ };
28
+ declare function FractalFlower({ timeScale, petalRadius, scale, intensity, morphCycle, color, className, style, width, height, ...divProps }: FractalFlowerProps): react_jsx_runtime.JSX.Element;
29
+
30
+ type OranoParticlesUniforms = {
31
+ color: string;
32
+ alpha: number;
33
+ wind: number;
34
+ baseSize: number;
35
+ distanceOffset: number;
36
+ distanceStrength: number;
37
+ particleCount: number;
38
+ };
39
+ type OranoParticlesProps = OranoParticlesUniforms & Omit<ComponentProps<"div">, keyof OranoParticlesUniforms | "ref" | "width" | "height"> & {
40
+ width?: string | number;
41
+ height?: string | number;
42
+ };
43
+ declare function OranoParticles({ className, style, width, height, color, alpha, wind, baseSize, distanceOffset, distanceStrength, particleCount, ...divProps }: OranoParticlesProps): react_jsx_runtime.JSX.Element;
44
+
45
+ export { FractalFlower, OranoParticles, ShaderArt };
@@ -0,0 +1,45 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComponentProps } from 'react';
3
+
4
+ type ShaderUniforms = {
5
+ uIterations: number;
6
+ uAmplitude: number;
7
+ uFreq: number;
8
+ };
9
+ type ShaderArtProps = {
10
+ uniforms: ShaderUniforms;
11
+ } & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
12
+ width?: string | number;
13
+ height?: string | number;
14
+ };
15
+ declare function ShaderArt({ uniforms, className, style, width, height, ...divProps }: ShaderArtProps): react_jsx_runtime.JSX.Element;
16
+
17
+ type FractalFlowerProps = {
18
+ timeScale: number;
19
+ petalRadius: number;
20
+ scale: number;
21
+ intensity: number;
22
+ morphCycle: number;
23
+ color: string;
24
+ } & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
25
+ width?: string | number;
26
+ height?: string | number;
27
+ };
28
+ declare function FractalFlower({ timeScale, petalRadius, scale, intensity, morphCycle, color, className, style, width, height, ...divProps }: FractalFlowerProps): react_jsx_runtime.JSX.Element;
29
+
30
+ type OranoParticlesUniforms = {
31
+ color: string;
32
+ alpha: number;
33
+ wind: number;
34
+ baseSize: number;
35
+ distanceOffset: number;
36
+ distanceStrength: number;
37
+ particleCount: number;
38
+ };
39
+ type OranoParticlesProps = OranoParticlesUniforms & Omit<ComponentProps<"div">, keyof OranoParticlesUniforms | "ref" | "width" | "height"> & {
40
+ width?: string | number;
41
+ height?: string | number;
42
+ };
43
+ declare function OranoParticles({ className, style, width, height, color, alpha, wind, baseSize, distanceOffset, distanceStrength, particleCount, ...divProps }: OranoParticlesProps): react_jsx_runtime.JSX.Element;
44
+
45
+ export { FractalFlower, OranoParticles, ShaderArt };
package/dist/index.js ADDED
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ FractalFlower: () => FractalFlower,
34
+ OranoParticles: () => OranoParticles,
35
+ ShaderArt: () => ShaderArt
36
+ });
37
+ module.exports = __toCommonJS(src_exports);
38
+
39
+ // src/components/ShaderArt.tsx
40
+ var import_react2 = require("react");
41
+ var THREE2 = __toESM(require("three"));
42
+
43
+ // src/hooks/useScene.ts
44
+ var import_react = require("react");
45
+ var THREE = __toESM(require("three"));
46
+ var EMPTY_DEPS = [];
47
+ function useScene({
48
+ renderer: rendererOptions,
49
+ camera: cameraOptions,
50
+ pixelRatio,
51
+ onCreate,
52
+ onRender,
53
+ onResize,
54
+ deps
55
+ } = {}) {
56
+ const containerRef = (0, import_react.useRef)(null);
57
+ const contextRef = (0, import_react.useRef)(null);
58
+ const onCreateRef = (0, import_react.useRef)(onCreate);
59
+ const onRenderRef = (0, import_react.useRef)(onRender);
60
+ const onResizeRef = (0, import_react.useRef)(onResize);
61
+ (0, import_react.useEffect)(() => {
62
+ onCreateRef.current = onCreate;
63
+ }, [onCreate]);
64
+ (0, import_react.useEffect)(() => {
65
+ onRenderRef.current = onRender;
66
+ }, [onRender]);
67
+ (0, import_react.useEffect)(() => {
68
+ onResizeRef.current = onResize;
69
+ }, [onResize]);
70
+ const effectDeps = deps ?? EMPTY_DEPS;
71
+ (0, import_react.useEffect)(() => {
72
+ const container = containerRef.current;
73
+ if (!container) return;
74
+ const resolvedPixelRatio = pixelRatio ?? (typeof window !== "undefined" ? Math.min(window.devicePixelRatio, 2) : 1);
75
+ const renderer = new THREE.WebGLRenderer({
76
+ alpha: true,
77
+ antialias: true,
78
+ ...rendererOptions
79
+ });
80
+ renderer.setPixelRatio(resolvedPixelRatio);
81
+ renderer.setSize(container.clientWidth, container.clientHeight, false);
82
+ renderer.setClearColor(0, 0);
83
+ renderer.domElement.style.width = "100%";
84
+ renderer.domElement.style.height = "100%";
85
+ container.appendChild(renderer.domElement);
86
+ const scene = new THREE.Scene();
87
+ const camera = new THREE.PerspectiveCamera(
88
+ cameraOptions?.fov ?? 55,
89
+ container.clientWidth / Math.max(1, container.clientHeight),
90
+ cameraOptions?.near ?? 0.1,
91
+ cameraOptions?.far ?? 500
92
+ );
93
+ const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
94
+ camera.position.set(x, y, z);
95
+ const clock = new THREE.Clock();
96
+ const context = { renderer, scene, camera, clock };
97
+ contextRef.current = context;
98
+ const teardownCreate = onCreateRef.current?.(context);
99
+ let animationFrameId = 0;
100
+ const renderLoop = () => {
101
+ onRenderRef.current?.(context);
102
+ renderer.render(scene, camera);
103
+ animationFrameId = requestAnimationFrame(renderLoop);
104
+ };
105
+ animationFrameId = requestAnimationFrame(renderLoop);
106
+ const resizeObserver = new ResizeObserver(() => {
107
+ const width = container.clientWidth;
108
+ const height = container.clientHeight;
109
+ renderer.setSize(width, height, false);
110
+ camera.aspect = width / Math.max(1, height);
111
+ camera.updateProjectionMatrix();
112
+ onResizeRef.current?.(context, { width, height });
113
+ });
114
+ resizeObserver.observe(container);
115
+ return () => {
116
+ teardownCreate?.();
117
+ cancelAnimationFrame(animationFrameId);
118
+ resizeObserver.disconnect();
119
+ scene.clear();
120
+ renderer.dispose();
121
+ if (renderer.domElement.parentNode === container) {
122
+ container.removeChild(renderer.domElement);
123
+ }
124
+ contextRef.current = null;
125
+ };
126
+ }, effectDeps);
127
+ return { containerRef, contextRef };
128
+ }
129
+
130
+ // src/shaders/shader-art/fragment.glsl
131
+ var fragment_default = "precision highp float;\n\nvarying vec2 vUv;\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uIterations;\nuniform float uAmplitude;\nuniform float uFreq;\n\nvec3 palette(float t) {\n vec3 a = vec3(0.5, 0.5, 0.5);\n vec3 b = vec3(0.5, 0.5, 0.5);\n vec3 c = vec3(1.0, 1.0, 1.0);\n vec3 d = vec3(0.263, 0.416, 0.557);\n return a + b * cos(6.28318 * (c * t + d));\n}\n\nvoid main() {\n vec2 fragCoord = vec2(vUv.x * iResolution.x, vUv.y * iResolution.y);\n vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;\n vec2 uv0 = uv;\n vec3 finalColor = vec3(0.0);\n const int MAX_ITERATIONS = 8;\n for (int idx = 0; idx < MAX_ITERATIONS; idx++) {\n float i = float(idx);\n if (i >= uIterations) {\n break;\n }\n uv = fract(uv * uAmplitude) - 0.5;\n float d = length(uv) * exp(-length(uv0));\n vec3 col = palette(length(uv0) + i * 0.4 + iTime * uFreq);\n d = sin(d * 8.0 + iTime) / 8.0;\n d = abs(d);\n d = pow(0.01 / max(d, 1e-4), 1.9);\n finalColor += col * d;\n }\n gl_FragColor = vec4(finalColor, 1.0);\n}";
132
+
133
+ // src/shaders/shader-art/vertex.glsl
134
+ var vertex_default = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}";
135
+
136
+ // src/components/ShaderArt.tsx
137
+ var import_jsx_runtime = require("react/jsx-runtime");
138
+ function ShaderArt({
139
+ uniforms,
140
+ className,
141
+ style,
142
+ width,
143
+ height,
144
+ ...divProps
145
+ }) {
146
+ const shaderUniformsRef = (0, import_react2.useRef)({
147
+ iTime: { value: 0 },
148
+ iResolution: { value: new THREE2.Vector3(1, 1, 1) },
149
+ uIterations: { value: Math.max(1, uniforms.uIterations) },
150
+ uAmplitude: { value: uniforms.uAmplitude },
151
+ uFreq: { value: uniforms.uFreq }
152
+ });
153
+ const assetsRef = (0, import_react2.useRef)(null);
154
+ const handleCreate = (0, import_react2.useCallback)(({ scene }) => {
155
+ const geometry = new THREE2.PlaneGeometry(2, 2);
156
+ const material = new THREE2.ShaderMaterial({
157
+ vertexShader: vertex_default,
158
+ fragmentShader: fragment_default,
159
+ uniforms: shaderUniformsRef.current
160
+ });
161
+ const mesh = new THREE2.Mesh(geometry, material);
162
+ scene.add(mesh);
163
+ assetsRef.current = { mesh, geometry, material };
164
+ return () => {
165
+ scene.remove(mesh);
166
+ geometry.dispose();
167
+ material.dispose();
168
+ assetsRef.current = null;
169
+ };
170
+ }, []);
171
+ const handleRender = (0, import_react2.useCallback)((context) => {
172
+ shaderUniformsRef.current.iTime.value = context.clock.getElapsedTime();
173
+ const canvas = context.renderer.domElement;
174
+ shaderUniformsRef.current.iResolution.value.set(
175
+ canvas.width,
176
+ canvas.height,
177
+ 1
178
+ );
179
+ }, []);
180
+ const { containerRef } = useScene({
181
+ onCreate: handleCreate,
182
+ onRender: handleRender
183
+ });
184
+ (0, import_react2.useEffect)(() => {
185
+ shaderUniformsRef.current.uIterations.value = Math.max(
186
+ 1,
187
+ uniforms.uIterations
188
+ );
189
+ shaderUniformsRef.current.uAmplitude.value = uniforms.uAmplitude;
190
+ shaderUniformsRef.current.uFreq.value = uniforms.uFreq;
191
+ }, [uniforms.uAmplitude, uniforms.uFreq, uniforms.uIterations]);
192
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
193
+ "div",
194
+ {
195
+ ref: containerRef,
196
+ className,
197
+ style: {
198
+ width: width ?? "100%",
199
+ height: height ?? "100%",
200
+ ...style
201
+ },
202
+ ...divProps
203
+ }
204
+ );
205
+ }
206
+
207
+ // src/components/FractalFlower.tsx
208
+ var import_react3 = require("react");
209
+ var THREE3 = __toESM(require("three"));
210
+
211
+ // src/shaders/fractal-flower/fragment.glsl
212
+ var fragment_default2 = "precision highp float;\n\nuniform float uIntensity;\nuniform float uExposure;\nuniform float uPetalRadius;\nuniform float uMorph;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nvoid main() {\n vec2 uv = gl_PointCoord - vec2(0.5);\n float sigma = max(0.3, uPetalRadius * 10.0);\n float dist2 = dot(uv, uv);\n float alpha = exp(-dist2 / (sigma * sigma));\n\n if (alpha <= 0.01) {\n discard;\n }\n\n float weight = 0.6 + 0.4 * vWeight;\n float falloff = pow(alpha, 0.8);\n float morphBoost = mix(1.0, 1.35, uMorph);\n vec3 color = clamp(vColor * weight * falloff * (uIntensity * morphBoost), 0.0, 1.0);\n\n float outAlpha = clamp(alpha * mix(0.6, 0.85, uMorph), 0.0, 1.0);\n gl_FragColor = vec4(color, outAlpha);\n}\n";
213
+
214
+ // src/shaders/fractal-flower/vertex.glsl
215
+ var vertex_default2 = "precision highp float;\n\nattribute float aK;\nattribute float aE;\nattribute float aRotation;\n\nuniform float uTime;\nuniform float uTimeScale;\nuniform float uSpread;\nuniform float uPointSize;\nuniform float uMorph;\nuniform vec3 uColor;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nmat2 rotation(float angle) {\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n float time = uTime * uTimeScale;\n float d = 7.0 * cos(length(vec2(aK, aE)) / 3.0 + time * 0.5);\n\n vec2 basePoint = vec2(\n aK * 4.0 + d * aK * sin(d + aE / 9.0 + time),\n aE * 2.0 - d * 9.0 - d * 9.0 * cos(d + time)\n );\n\n vec2 altPoint = vec2(\n aK * 6.0 + sin(time * 1.3 + aE * 2.0) * (42.0 + 12.0 * sin(aE * 1.15)),\n aE * 3.4 - d * 12.5 + 28.0 * cos(time * 0.75 + aK * 0.35)\n );\n\n vec2 morphed = mix(basePoint, altPoint, uMorph);\n float swirl = aRotation + uMorph * (0.8 + 0.25 * sin(time * 0.4 + aE));\n vec2 rotated = rotation(swirl) * morphed;\n\n float spreadMix = mix(uSpread, uSpread * 0.65, uMorph);\n vec2 scaled = rotated / spreadMix;\n vec3 modelPosition = vec3(scaled, 0.0);\n\n float radial = length(morphed) / (spreadMix * 0.9);\n vWeight = clamp(1.0 - radial * radial, 0.0, 1.0);\n\n vColor = uColor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(modelPosition, 1.0);\n gl_PointSize = uPointSize * mix(1.0, 1.35, uMorph);\n}\n";
216
+
217
+ // src/components/FractalFlower.tsx
218
+ var import_jsx_runtime2 = require("react/jsx-runtime");
219
+ var BASE_POINT_COUNT = 2e4;
220
+ var ROTATIONS = [
221
+ 0,
222
+ Math.PI / 3,
223
+ 2 * Math.PI / 3,
224
+ Math.PI,
225
+ 4 * Math.PI / 3,
226
+ 5 * Math.PI / 3
227
+ ];
228
+ var ROTATION_COUNT = ROTATIONS.length;
229
+ var TOTAL_POINTS = ROTATION_COUNT * BASE_POINT_COUNT;
230
+ var BASE_SPREAD = 200;
231
+ var MORPH_SPEED = 0.6;
232
+ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
233
+ function FractalFlower({
234
+ timeScale,
235
+ petalRadius,
236
+ scale,
237
+ intensity,
238
+ morphCycle,
239
+ color,
240
+ className,
241
+ style,
242
+ width,
243
+ height,
244
+ ...divProps
245
+ }) {
246
+ const attributes = (0, import_react3.useMemo)(() => {
247
+ const positions = new Float32Array(TOTAL_POINTS * 3);
248
+ const kValues = new Float32Array(TOTAL_POINTS);
249
+ const eValues = new Float32Array(TOTAL_POINTS);
250
+ const rotations = new Float32Array(TOTAL_POINTS);
251
+ for (let copy = 0; copy < ROTATION_COUNT; copy++) {
252
+ const angle = ROTATIONS[copy];
253
+ for (let i = 0; i < BASE_POINT_COUNT; i++) {
254
+ const index = copy * BASE_POINT_COUNT + i;
255
+ const k = i % 25 - 12;
256
+ const e = i / 800;
257
+ positions[index * 3] = 0;
258
+ positions[index * 3 + 1] = 0;
259
+ positions[index * 3 + 2] = 0;
260
+ kValues[index] = k;
261
+ eValues[index] = e;
262
+ rotations[index] = angle;
263
+ }
264
+ }
265
+ return {
266
+ positions,
267
+ kValues,
268
+ eValues,
269
+ rotations
270
+ };
271
+ }, []);
272
+ const uniformsRef = (0, import_react3.useRef)({
273
+ uTime: { value: 0 },
274
+ uResolution: { value: new THREE3.Vector2(1, 1) },
275
+ uTimeScale: { value: 0.78539816339 },
276
+ uPetalRadius: { value: 0.02 },
277
+ uIntensity: { value: 0.25 },
278
+ uExposure: { value: 5 },
279
+ uSpread: { value: BASE_SPREAD / Math.max(scale, 1e-4) },
280
+ uPointSize: { value: 2.5 },
281
+ uColor: { value: new THREE3.Color(color) },
282
+ uMorph: { value: 0 }
283
+ });
284
+ const morphRef = (0, import_react3.useRef)({
285
+ progress: uniformsRef.current.uMorph.value,
286
+ target: 0,
287
+ cycle: morphCycle
288
+ });
289
+ const assetsRef = (0, import_react3.useRef)(null);
290
+ const handleCreate = (0, import_react3.useCallback)(
291
+ ({ scene }) => {
292
+ const geometry = new THREE3.BufferGeometry();
293
+ geometry.setAttribute(
294
+ "position",
295
+ new THREE3.BufferAttribute(attributes.positions, 3)
296
+ );
297
+ geometry.setAttribute(
298
+ "aK",
299
+ new THREE3.BufferAttribute(attributes.kValues, 1)
300
+ );
301
+ geometry.setAttribute(
302
+ "aE",
303
+ new THREE3.BufferAttribute(attributes.eValues, 1)
304
+ );
305
+ geometry.setAttribute(
306
+ "aRotation",
307
+ new THREE3.BufferAttribute(attributes.rotations, 1)
308
+ );
309
+ const uniformValues = uniformsRef.current;
310
+ const material = new THREE3.ShaderMaterial({
311
+ vertexShader: vertex_default2,
312
+ fragmentShader: fragment_default2,
313
+ uniforms: uniformValues,
314
+ blending: THREE3.AdditiveBlending,
315
+ depthWrite: false,
316
+ transparent: true
317
+ });
318
+ const points = new THREE3.Points(geometry, material);
319
+ points.frustumCulled = false;
320
+ scene.add(points);
321
+ assetsRef.current = { points, geometry, material, uniforms: uniformValues };
322
+ return () => {
323
+ scene.remove(points);
324
+ geometry.dispose();
325
+ material.dispose();
326
+ assetsRef.current = null;
327
+ };
328
+ },
329
+ [attributes.eValues, attributes.kValues, attributes.positions, attributes.rotations]
330
+ );
331
+ const handleRender = (0, import_react3.useCallback)(
332
+ (context) => {
333
+ const assets = assetsRef.current;
334
+ if (!assets) return;
335
+ const uniforms = assets.uniforms;
336
+ uniforms.uTime.value = context.clock.getElapsedTime();
337
+ const canvas = context.renderer.domElement;
338
+ uniforms.uResolution.value.set(canvas.width, canvas.height);
339
+ const basePointSize = Math.max(
340
+ 2,
341
+ canvas.height / 400 * (uniforms.uPetalRadius.value * 32)
342
+ );
343
+ const pointSize = Math.min(basePointSize, 6);
344
+ uniforms.uPointSize.value = pointSize;
345
+ const morph = morphRef.current;
346
+ if (Math.abs(morph.target - morph.progress) > 1e-3) {
347
+ const delta = context.clock.getDelta();
348
+ const direction = Math.sign(morph.target - morph.progress);
349
+ morph.progress = clamp(
350
+ morph.progress + direction * delta * MORPH_SPEED,
351
+ 0,
352
+ 1
353
+ );
354
+ }
355
+ uniforms.uMorph.value = morph.progress;
356
+ },
357
+ []
358
+ );
359
+ const { containerRef } = useScene({
360
+ onCreate: handleCreate,
361
+ onRender: handleRender
362
+ });
363
+ (0, import_react3.useEffect)(() => {
364
+ const uniforms = uniformsRef.current;
365
+ uniforms.uTimeScale.value = timeScale;
366
+ uniforms.uPetalRadius.value = petalRadius;
367
+ uniforms.uSpread.value = BASE_SPREAD / Math.max(scale, 1e-4);
368
+ uniforms.uIntensity.value = intensity;
369
+ uniforms.uColor.value.set(color);
370
+ uniforms.uMorph.value = morphRef.current.progress;
371
+ }, [color, intensity, petalRadius, scale, timeScale]);
372
+ (0, import_react3.useEffect)(() => {
373
+ if (morphRef.current.cycle === morphCycle) {
374
+ return;
375
+ }
376
+ morphRef.current.cycle = morphCycle;
377
+ morphRef.current.target = morphRef.current.target < 0.5 ? 1 : 0;
378
+ }, [morphCycle]);
379
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
380
+ "div",
381
+ {
382
+ ref: containerRef,
383
+ className,
384
+ style: {
385
+ width: width ?? "100%",
386
+ height: height ?? "100%",
387
+ ...style
388
+ },
389
+ ...divProps
390
+ }
391
+ );
392
+ }
393
+
394
+ // src/components/OranoParticles.tsx
395
+ var import_react4 = require("react");
396
+ var THREE4 = __toESM(require("three"));
397
+
398
+ // src/shaders/orano-particles/fragment.glsl
399
+ var fragment_default3 = "uniform vec3 uColor;\nuniform float uAlpha;\n\nvarying float vAlpha;\n\nvoid main() {\n gl_FragColor = vec4(uColor, vAlpha * uAlpha);\n}\n";
400
+
401
+ // src/shaders/orano-particles/vertex.glsl
402
+ var vertex_default3 = "#define M_PI 3.1415926535897932384626433832795\n\nuniform float uTime;\nuniform float uWind;\nuniform float uBaseSize;\nuniform float uDistanceOffset;\nuniform float uDistanceStrength;\n\nvarying float vAlpha;\n\nhighp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, M_PI);\n\n return fract(sin(sn) * c);\n}\n\nvoid main() {\n vec4 modelPosition = modelMatrix * vec4(position, 1.0);\n\n modelPosition.x = mod(\n modelPosition.x + uTime * ((uWind * 0.5) + (uWind * 0.5) * random(modelPosition.yz)),\n 20.0\n ) -\n 10.0;\n\n modelPosition.y =\n modelPosition.y + sin(modelPosition.x) * 0.3 +\n modelPosition.y + sin(modelPosition.x * 0.357) * 0.3;\n\n vec4 viewPosition = viewMatrix * modelPosition;\n float cameraDistance = distance(vec4(0.0), viewPosition);\n\n float sizeFalloff =\n clamp((cameraDistance - uDistanceOffset) * uDistanceStrength, 0.0, uBaseSize);\n\n gl_PointSize = uBaseSize - sizeFalloff;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = 1.0 - clamp(cameraDistance * 0.5 / 12.0, 0.0, 1.0);\n}\n";
403
+
404
+ // src/components/OranoParticles.tsx
405
+ var import_jsx_runtime3 = require("react/jsx-runtime");
406
+ var X_RANGE = 20;
407
+ var Y_RANGE = { min: -8, max: 2 };
408
+ var Z_RANGE = { min: -5, max: 215 };
409
+ function createParticleGeometry(count) {
410
+ const geometry = new THREE4.BufferGeometry();
411
+ const positions = new Float32Array(count * 3);
412
+ for (let i = 0; i < count; i += 1) {
413
+ const x = (Math.random() - 0.5) * X_RANGE;
414
+ const y = Math.random() * (Y_RANGE.max - Y_RANGE.min) + Y_RANGE.min;
415
+ const z = Math.random() * (Z_RANGE.max - Z_RANGE.min) + Z_RANGE.min;
416
+ positions[i * 3] = x;
417
+ positions[i * 3 + 1] = y;
418
+ positions[i * 3 + 2] = z;
419
+ }
420
+ geometry.setAttribute("position", new THREE4.BufferAttribute(positions, 3));
421
+ geometry.computeBoundingSphere();
422
+ return geometry;
423
+ }
424
+ function buildUniforms({
425
+ color,
426
+ alpha,
427
+ wind,
428
+ baseSize,
429
+ distanceOffset,
430
+ distanceStrength
431
+ }) {
432
+ return {
433
+ uColor: { value: new THREE4.Color(color) },
434
+ uAlpha: { value: alpha },
435
+ uTime: { value: 0 },
436
+ uWind: { value: wind },
437
+ uBaseSize: { value: baseSize },
438
+ uDistanceOffset: { value: distanceOffset },
439
+ uDistanceStrength: { value: distanceStrength }
440
+ };
441
+ }
442
+ function OranoParticles({
443
+ className,
444
+ style,
445
+ width,
446
+ height,
447
+ color,
448
+ alpha,
449
+ wind,
450
+ baseSize,
451
+ distanceOffset,
452
+ distanceStrength,
453
+ particleCount,
454
+ ...divProps
455
+ }) {
456
+ const particlesRef = (0, import_react4.useRef)(null);
457
+ const initialUniformsRef = (0, import_react4.useRef)({
458
+ color,
459
+ alpha,
460
+ wind,
461
+ baseSize,
462
+ distanceOffset,
463
+ distanceStrength,
464
+ particleCount
465
+ });
466
+ initialUniformsRef.current = {
467
+ color,
468
+ alpha,
469
+ wind,
470
+ baseSize,
471
+ distanceOffset,
472
+ distanceStrength,
473
+ particleCount
474
+ };
475
+ const handleCreate = (0, import_react4.useCallback)(({ scene }) => {
476
+ const uniforms = buildUniforms(initialUniformsRef.current);
477
+ const geometry = createParticleGeometry(
478
+ Math.max(1, Math.floor(initialUniformsRef.current.particleCount))
479
+ );
480
+ const material = new THREE4.ShaderMaterial({
481
+ fragmentShader: fragment_default3,
482
+ vertexShader: vertex_default3,
483
+ uniforms,
484
+ transparent: true,
485
+ depthWrite: false
486
+ });
487
+ const points = new THREE4.Points(geometry, material);
488
+ points.frustumCulled = false;
489
+ scene.add(points);
490
+ particlesRef.current = { points, geometry, material, uniforms };
491
+ return () => {
492
+ scene.remove(points);
493
+ geometry.dispose();
494
+ material.dispose();
495
+ particlesRef.current = null;
496
+ };
497
+ }, []);
498
+ const handleRender = (0, import_react4.useCallback)((context) => {
499
+ const assets = particlesRef.current;
500
+ if (!assets) return;
501
+ assets.uniforms.uTime.value = context.clock.getElapsedTime();
502
+ }, []);
503
+ const { containerRef } = useScene({
504
+ onCreate: handleCreate,
505
+ onRender: handleRender
506
+ });
507
+ (0, import_react4.useEffect)(() => {
508
+ const assets = particlesRef.current;
509
+ if (!assets) return;
510
+ assets.uniforms.uColor.value.set(color);
511
+ }, [color]);
512
+ (0, import_react4.useEffect)(() => {
513
+ const assets = particlesRef.current;
514
+ if (!assets) return;
515
+ assets.uniforms.uAlpha.value = alpha;
516
+ }, [alpha]);
517
+ (0, import_react4.useEffect)(() => {
518
+ const assets = particlesRef.current;
519
+ if (!assets) return;
520
+ assets.uniforms.uWind.value = wind;
521
+ }, [wind]);
522
+ (0, import_react4.useEffect)(() => {
523
+ const assets = particlesRef.current;
524
+ if (!assets) return;
525
+ assets.uniforms.uBaseSize.value = baseSize;
526
+ }, [baseSize]);
527
+ (0, import_react4.useEffect)(() => {
528
+ const assets = particlesRef.current;
529
+ if (!assets) return;
530
+ assets.uniforms.uDistanceOffset.value = distanceOffset;
531
+ }, [distanceOffset]);
532
+ (0, import_react4.useEffect)(() => {
533
+ const assets = particlesRef.current;
534
+ if (!assets) return;
535
+ assets.uniforms.uDistanceStrength.value = distanceStrength;
536
+ }, [distanceStrength]);
537
+ (0, import_react4.useEffect)(() => {
538
+ const assets = particlesRef.current;
539
+ if (!assets) return;
540
+ const geometry = createParticleGeometry(
541
+ Math.max(1, Math.floor(particleCount))
542
+ );
543
+ assets.points.geometry.dispose();
544
+ assets.points.geometry = geometry;
545
+ assets.geometry = geometry;
546
+ }, [particleCount]);
547
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
548
+ "div",
549
+ {
550
+ ref: containerRef,
551
+ className,
552
+ style: {
553
+ width: width ?? "100%",
554
+ height: height ?? "100%",
555
+ ...style
556
+ },
557
+ ...divProps
558
+ }
559
+ );
560
+ }
561
+ // Annotate the CommonJS export names for ESM import in node:
562
+ 0 && (module.exports = {
563
+ FractalFlower,
564
+ OranoParticles,
565
+ ShaderArt
566
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,532 @@
1
+ // src/components/ShaderArt.tsx
2
+ import { useCallback, useEffect as useEffect2, useRef as useRef2 } from "react";
3
+ import * as THREE2 from "three";
4
+
5
+ // src/hooks/useScene.ts
6
+ import { useEffect, useRef } from "react";
7
+ import * as THREE from "three";
8
+ var EMPTY_DEPS = [];
9
+ function useScene({
10
+ renderer: rendererOptions,
11
+ camera: cameraOptions,
12
+ pixelRatio,
13
+ onCreate,
14
+ onRender,
15
+ onResize,
16
+ deps
17
+ } = {}) {
18
+ const containerRef = useRef(null);
19
+ const contextRef = useRef(null);
20
+ const onCreateRef = useRef(onCreate);
21
+ const onRenderRef = useRef(onRender);
22
+ const onResizeRef = useRef(onResize);
23
+ useEffect(() => {
24
+ onCreateRef.current = onCreate;
25
+ }, [onCreate]);
26
+ useEffect(() => {
27
+ onRenderRef.current = onRender;
28
+ }, [onRender]);
29
+ useEffect(() => {
30
+ onResizeRef.current = onResize;
31
+ }, [onResize]);
32
+ const effectDeps = deps ?? EMPTY_DEPS;
33
+ useEffect(() => {
34
+ const container = containerRef.current;
35
+ if (!container) return;
36
+ const resolvedPixelRatio = pixelRatio ?? (typeof window !== "undefined" ? Math.min(window.devicePixelRatio, 2) : 1);
37
+ const renderer = new THREE.WebGLRenderer({
38
+ alpha: true,
39
+ antialias: true,
40
+ ...rendererOptions
41
+ });
42
+ renderer.setPixelRatio(resolvedPixelRatio);
43
+ renderer.setSize(container.clientWidth, container.clientHeight, false);
44
+ renderer.setClearColor(0, 0);
45
+ renderer.domElement.style.width = "100%";
46
+ renderer.domElement.style.height = "100%";
47
+ container.appendChild(renderer.domElement);
48
+ const scene = new THREE.Scene();
49
+ const camera = new THREE.PerspectiveCamera(
50
+ cameraOptions?.fov ?? 55,
51
+ container.clientWidth / Math.max(1, container.clientHeight),
52
+ cameraOptions?.near ?? 0.1,
53
+ cameraOptions?.far ?? 500
54
+ );
55
+ const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
56
+ camera.position.set(x, y, z);
57
+ const clock = new THREE.Clock();
58
+ const context = { renderer, scene, camera, clock };
59
+ contextRef.current = context;
60
+ const teardownCreate = onCreateRef.current?.(context);
61
+ let animationFrameId = 0;
62
+ const renderLoop = () => {
63
+ onRenderRef.current?.(context);
64
+ renderer.render(scene, camera);
65
+ animationFrameId = requestAnimationFrame(renderLoop);
66
+ };
67
+ animationFrameId = requestAnimationFrame(renderLoop);
68
+ const resizeObserver = new ResizeObserver(() => {
69
+ const width = container.clientWidth;
70
+ const height = container.clientHeight;
71
+ renderer.setSize(width, height, false);
72
+ camera.aspect = width / Math.max(1, height);
73
+ camera.updateProjectionMatrix();
74
+ onResizeRef.current?.(context, { width, height });
75
+ });
76
+ resizeObserver.observe(container);
77
+ return () => {
78
+ teardownCreate?.();
79
+ cancelAnimationFrame(animationFrameId);
80
+ resizeObserver.disconnect();
81
+ scene.clear();
82
+ renderer.dispose();
83
+ if (renderer.domElement.parentNode === container) {
84
+ container.removeChild(renderer.domElement);
85
+ }
86
+ contextRef.current = null;
87
+ };
88
+ }, effectDeps);
89
+ return { containerRef, contextRef };
90
+ }
91
+
92
+ // src/shaders/shader-art/fragment.glsl
93
+ var fragment_default = "precision highp float;\n\nvarying vec2 vUv;\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uIterations;\nuniform float uAmplitude;\nuniform float uFreq;\n\nvec3 palette(float t) {\n vec3 a = vec3(0.5, 0.5, 0.5);\n vec3 b = vec3(0.5, 0.5, 0.5);\n vec3 c = vec3(1.0, 1.0, 1.0);\n vec3 d = vec3(0.263, 0.416, 0.557);\n return a + b * cos(6.28318 * (c * t + d));\n}\n\nvoid main() {\n vec2 fragCoord = vec2(vUv.x * iResolution.x, vUv.y * iResolution.y);\n vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;\n vec2 uv0 = uv;\n vec3 finalColor = vec3(0.0);\n const int MAX_ITERATIONS = 8;\n for (int idx = 0; idx < MAX_ITERATIONS; idx++) {\n float i = float(idx);\n if (i >= uIterations) {\n break;\n }\n uv = fract(uv * uAmplitude) - 0.5;\n float d = length(uv) * exp(-length(uv0));\n vec3 col = palette(length(uv0) + i * 0.4 + iTime * uFreq);\n d = sin(d * 8.0 + iTime) / 8.0;\n d = abs(d);\n d = pow(0.01 / max(d, 1e-4), 1.9);\n finalColor += col * d;\n }\n gl_FragColor = vec4(finalColor, 1.0);\n}";
94
+
95
+ // src/shaders/shader-art/vertex.glsl
96
+ var vertex_default = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}";
97
+
98
+ // src/components/ShaderArt.tsx
99
+ import { jsx } from "react/jsx-runtime";
100
+ function ShaderArt({
101
+ uniforms,
102
+ className,
103
+ style,
104
+ width,
105
+ height,
106
+ ...divProps
107
+ }) {
108
+ const shaderUniformsRef = useRef2({
109
+ iTime: { value: 0 },
110
+ iResolution: { value: new THREE2.Vector3(1, 1, 1) },
111
+ uIterations: { value: Math.max(1, uniforms.uIterations) },
112
+ uAmplitude: { value: uniforms.uAmplitude },
113
+ uFreq: { value: uniforms.uFreq }
114
+ });
115
+ const assetsRef = useRef2(null);
116
+ const handleCreate = useCallback(({ scene }) => {
117
+ const geometry = new THREE2.PlaneGeometry(2, 2);
118
+ const material = new THREE2.ShaderMaterial({
119
+ vertexShader: vertex_default,
120
+ fragmentShader: fragment_default,
121
+ uniforms: shaderUniformsRef.current
122
+ });
123
+ const mesh = new THREE2.Mesh(geometry, material);
124
+ scene.add(mesh);
125
+ assetsRef.current = { mesh, geometry, material };
126
+ return () => {
127
+ scene.remove(mesh);
128
+ geometry.dispose();
129
+ material.dispose();
130
+ assetsRef.current = null;
131
+ };
132
+ }, []);
133
+ const handleRender = useCallback((context) => {
134
+ shaderUniformsRef.current.iTime.value = context.clock.getElapsedTime();
135
+ const canvas = context.renderer.domElement;
136
+ shaderUniformsRef.current.iResolution.value.set(
137
+ canvas.width,
138
+ canvas.height,
139
+ 1
140
+ );
141
+ }, []);
142
+ const { containerRef } = useScene({
143
+ onCreate: handleCreate,
144
+ onRender: handleRender
145
+ });
146
+ useEffect2(() => {
147
+ shaderUniformsRef.current.uIterations.value = Math.max(
148
+ 1,
149
+ uniforms.uIterations
150
+ );
151
+ shaderUniformsRef.current.uAmplitude.value = uniforms.uAmplitude;
152
+ shaderUniformsRef.current.uFreq.value = uniforms.uFreq;
153
+ }, [uniforms.uAmplitude, uniforms.uFreq, uniforms.uIterations]);
154
+ return /* @__PURE__ */ jsx(
155
+ "div",
156
+ {
157
+ ref: containerRef,
158
+ className,
159
+ style: {
160
+ width: width ?? "100%",
161
+ height: height ?? "100%",
162
+ ...style
163
+ },
164
+ ...divProps
165
+ }
166
+ );
167
+ }
168
+
169
+ // src/components/FractalFlower.tsx
170
+ import {
171
+ useCallback as useCallback2,
172
+ useEffect as useEffect3,
173
+ useMemo,
174
+ useRef as useRef3
175
+ } from "react";
176
+ import * as THREE3 from "three";
177
+
178
+ // src/shaders/fractal-flower/fragment.glsl
179
+ var fragment_default2 = "precision highp float;\n\nuniform float uIntensity;\nuniform float uExposure;\nuniform float uPetalRadius;\nuniform float uMorph;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nvoid main() {\n vec2 uv = gl_PointCoord - vec2(0.5);\n float sigma = max(0.3, uPetalRadius * 10.0);\n float dist2 = dot(uv, uv);\n float alpha = exp(-dist2 / (sigma * sigma));\n\n if (alpha <= 0.01) {\n discard;\n }\n\n float weight = 0.6 + 0.4 * vWeight;\n float falloff = pow(alpha, 0.8);\n float morphBoost = mix(1.0, 1.35, uMorph);\n vec3 color = clamp(vColor * weight * falloff * (uIntensity * morphBoost), 0.0, 1.0);\n\n float outAlpha = clamp(alpha * mix(0.6, 0.85, uMorph), 0.0, 1.0);\n gl_FragColor = vec4(color, outAlpha);\n}\n";
180
+
181
+ // src/shaders/fractal-flower/vertex.glsl
182
+ var vertex_default2 = "precision highp float;\n\nattribute float aK;\nattribute float aE;\nattribute float aRotation;\n\nuniform float uTime;\nuniform float uTimeScale;\nuniform float uSpread;\nuniform float uPointSize;\nuniform float uMorph;\nuniform vec3 uColor;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nmat2 rotation(float angle) {\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n float time = uTime * uTimeScale;\n float d = 7.0 * cos(length(vec2(aK, aE)) / 3.0 + time * 0.5);\n\n vec2 basePoint = vec2(\n aK * 4.0 + d * aK * sin(d + aE / 9.0 + time),\n aE * 2.0 - d * 9.0 - d * 9.0 * cos(d + time)\n );\n\n vec2 altPoint = vec2(\n aK * 6.0 + sin(time * 1.3 + aE * 2.0) * (42.0 + 12.0 * sin(aE * 1.15)),\n aE * 3.4 - d * 12.5 + 28.0 * cos(time * 0.75 + aK * 0.35)\n );\n\n vec2 morphed = mix(basePoint, altPoint, uMorph);\n float swirl = aRotation + uMorph * (0.8 + 0.25 * sin(time * 0.4 + aE));\n vec2 rotated = rotation(swirl) * morphed;\n\n float spreadMix = mix(uSpread, uSpread * 0.65, uMorph);\n vec2 scaled = rotated / spreadMix;\n vec3 modelPosition = vec3(scaled, 0.0);\n\n float radial = length(morphed) / (spreadMix * 0.9);\n vWeight = clamp(1.0 - radial * radial, 0.0, 1.0);\n\n vColor = uColor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(modelPosition, 1.0);\n gl_PointSize = uPointSize * mix(1.0, 1.35, uMorph);\n}\n";
183
+
184
+ // src/components/FractalFlower.tsx
185
+ import { jsx as jsx2 } from "react/jsx-runtime";
186
+ var BASE_POINT_COUNT = 2e4;
187
+ var ROTATIONS = [
188
+ 0,
189
+ Math.PI / 3,
190
+ 2 * Math.PI / 3,
191
+ Math.PI,
192
+ 4 * Math.PI / 3,
193
+ 5 * Math.PI / 3
194
+ ];
195
+ var ROTATION_COUNT = ROTATIONS.length;
196
+ var TOTAL_POINTS = ROTATION_COUNT * BASE_POINT_COUNT;
197
+ var BASE_SPREAD = 200;
198
+ var MORPH_SPEED = 0.6;
199
+ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
200
+ function FractalFlower({
201
+ timeScale,
202
+ petalRadius,
203
+ scale,
204
+ intensity,
205
+ morphCycle,
206
+ color,
207
+ className,
208
+ style,
209
+ width,
210
+ height,
211
+ ...divProps
212
+ }) {
213
+ const attributes = useMemo(() => {
214
+ const positions = new Float32Array(TOTAL_POINTS * 3);
215
+ const kValues = new Float32Array(TOTAL_POINTS);
216
+ const eValues = new Float32Array(TOTAL_POINTS);
217
+ const rotations = new Float32Array(TOTAL_POINTS);
218
+ for (let copy = 0; copy < ROTATION_COUNT; copy++) {
219
+ const angle = ROTATIONS[copy];
220
+ for (let i = 0; i < BASE_POINT_COUNT; i++) {
221
+ const index = copy * BASE_POINT_COUNT + i;
222
+ const k = i % 25 - 12;
223
+ const e = i / 800;
224
+ positions[index * 3] = 0;
225
+ positions[index * 3 + 1] = 0;
226
+ positions[index * 3 + 2] = 0;
227
+ kValues[index] = k;
228
+ eValues[index] = e;
229
+ rotations[index] = angle;
230
+ }
231
+ }
232
+ return {
233
+ positions,
234
+ kValues,
235
+ eValues,
236
+ rotations
237
+ };
238
+ }, []);
239
+ const uniformsRef = useRef3({
240
+ uTime: { value: 0 },
241
+ uResolution: { value: new THREE3.Vector2(1, 1) },
242
+ uTimeScale: { value: 0.78539816339 },
243
+ uPetalRadius: { value: 0.02 },
244
+ uIntensity: { value: 0.25 },
245
+ uExposure: { value: 5 },
246
+ uSpread: { value: BASE_SPREAD / Math.max(scale, 1e-4) },
247
+ uPointSize: { value: 2.5 },
248
+ uColor: { value: new THREE3.Color(color) },
249
+ uMorph: { value: 0 }
250
+ });
251
+ const morphRef = useRef3({
252
+ progress: uniformsRef.current.uMorph.value,
253
+ target: 0,
254
+ cycle: morphCycle
255
+ });
256
+ const assetsRef = useRef3(null);
257
+ const handleCreate = useCallback2(
258
+ ({ scene }) => {
259
+ const geometry = new THREE3.BufferGeometry();
260
+ geometry.setAttribute(
261
+ "position",
262
+ new THREE3.BufferAttribute(attributes.positions, 3)
263
+ );
264
+ geometry.setAttribute(
265
+ "aK",
266
+ new THREE3.BufferAttribute(attributes.kValues, 1)
267
+ );
268
+ geometry.setAttribute(
269
+ "aE",
270
+ new THREE3.BufferAttribute(attributes.eValues, 1)
271
+ );
272
+ geometry.setAttribute(
273
+ "aRotation",
274
+ new THREE3.BufferAttribute(attributes.rotations, 1)
275
+ );
276
+ const uniformValues = uniformsRef.current;
277
+ const material = new THREE3.ShaderMaterial({
278
+ vertexShader: vertex_default2,
279
+ fragmentShader: fragment_default2,
280
+ uniforms: uniformValues,
281
+ blending: THREE3.AdditiveBlending,
282
+ depthWrite: false,
283
+ transparent: true
284
+ });
285
+ const points = new THREE3.Points(geometry, material);
286
+ points.frustumCulled = false;
287
+ scene.add(points);
288
+ assetsRef.current = { points, geometry, material, uniforms: uniformValues };
289
+ return () => {
290
+ scene.remove(points);
291
+ geometry.dispose();
292
+ material.dispose();
293
+ assetsRef.current = null;
294
+ };
295
+ },
296
+ [attributes.eValues, attributes.kValues, attributes.positions, attributes.rotations]
297
+ );
298
+ const handleRender = useCallback2(
299
+ (context) => {
300
+ const assets = assetsRef.current;
301
+ if (!assets) return;
302
+ const uniforms = assets.uniforms;
303
+ uniforms.uTime.value = context.clock.getElapsedTime();
304
+ const canvas = context.renderer.domElement;
305
+ uniforms.uResolution.value.set(canvas.width, canvas.height);
306
+ const basePointSize = Math.max(
307
+ 2,
308
+ canvas.height / 400 * (uniforms.uPetalRadius.value * 32)
309
+ );
310
+ const pointSize = Math.min(basePointSize, 6);
311
+ uniforms.uPointSize.value = pointSize;
312
+ const morph = morphRef.current;
313
+ if (Math.abs(morph.target - morph.progress) > 1e-3) {
314
+ const delta = context.clock.getDelta();
315
+ const direction = Math.sign(morph.target - morph.progress);
316
+ morph.progress = clamp(
317
+ morph.progress + direction * delta * MORPH_SPEED,
318
+ 0,
319
+ 1
320
+ );
321
+ }
322
+ uniforms.uMorph.value = morph.progress;
323
+ },
324
+ []
325
+ );
326
+ const { containerRef } = useScene({
327
+ onCreate: handleCreate,
328
+ onRender: handleRender
329
+ });
330
+ useEffect3(() => {
331
+ const uniforms = uniformsRef.current;
332
+ uniforms.uTimeScale.value = timeScale;
333
+ uniforms.uPetalRadius.value = petalRadius;
334
+ uniforms.uSpread.value = BASE_SPREAD / Math.max(scale, 1e-4);
335
+ uniforms.uIntensity.value = intensity;
336
+ uniforms.uColor.value.set(color);
337
+ uniforms.uMorph.value = morphRef.current.progress;
338
+ }, [color, intensity, petalRadius, scale, timeScale]);
339
+ useEffect3(() => {
340
+ if (morphRef.current.cycle === morphCycle) {
341
+ return;
342
+ }
343
+ morphRef.current.cycle = morphCycle;
344
+ morphRef.current.target = morphRef.current.target < 0.5 ? 1 : 0;
345
+ }, [morphCycle]);
346
+ return /* @__PURE__ */ jsx2(
347
+ "div",
348
+ {
349
+ ref: containerRef,
350
+ className,
351
+ style: {
352
+ width: width ?? "100%",
353
+ height: height ?? "100%",
354
+ ...style
355
+ },
356
+ ...divProps
357
+ }
358
+ );
359
+ }
360
+
361
+ // src/components/OranoParticles.tsx
362
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
363
+ import * as THREE4 from "three";
364
+
365
+ // src/shaders/orano-particles/fragment.glsl
366
+ var fragment_default3 = "uniform vec3 uColor;\nuniform float uAlpha;\n\nvarying float vAlpha;\n\nvoid main() {\n gl_FragColor = vec4(uColor, vAlpha * uAlpha);\n}\n";
367
+
368
+ // src/shaders/orano-particles/vertex.glsl
369
+ var vertex_default3 = "#define M_PI 3.1415926535897932384626433832795\n\nuniform float uTime;\nuniform float uWind;\nuniform float uBaseSize;\nuniform float uDistanceOffset;\nuniform float uDistanceStrength;\n\nvarying float vAlpha;\n\nhighp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, M_PI);\n\n return fract(sin(sn) * c);\n}\n\nvoid main() {\n vec4 modelPosition = modelMatrix * vec4(position, 1.0);\n\n modelPosition.x = mod(\n modelPosition.x + uTime * ((uWind * 0.5) + (uWind * 0.5) * random(modelPosition.yz)),\n 20.0\n ) -\n 10.0;\n\n modelPosition.y =\n modelPosition.y + sin(modelPosition.x) * 0.3 +\n modelPosition.y + sin(modelPosition.x * 0.357) * 0.3;\n\n vec4 viewPosition = viewMatrix * modelPosition;\n float cameraDistance = distance(vec4(0.0), viewPosition);\n\n float sizeFalloff =\n clamp((cameraDistance - uDistanceOffset) * uDistanceStrength, 0.0, uBaseSize);\n\n gl_PointSize = uBaseSize - sizeFalloff;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = 1.0 - clamp(cameraDistance * 0.5 / 12.0, 0.0, 1.0);\n}\n";
370
+
371
+ // src/components/OranoParticles.tsx
372
+ import { jsx as jsx3 } from "react/jsx-runtime";
373
+ var X_RANGE = 20;
374
+ var Y_RANGE = { min: -8, max: 2 };
375
+ var Z_RANGE = { min: -5, max: 215 };
376
+ function createParticleGeometry(count) {
377
+ const geometry = new THREE4.BufferGeometry();
378
+ const positions = new Float32Array(count * 3);
379
+ for (let i = 0; i < count; i += 1) {
380
+ const x = (Math.random() - 0.5) * X_RANGE;
381
+ const y = Math.random() * (Y_RANGE.max - Y_RANGE.min) + Y_RANGE.min;
382
+ const z = Math.random() * (Z_RANGE.max - Z_RANGE.min) + Z_RANGE.min;
383
+ positions[i * 3] = x;
384
+ positions[i * 3 + 1] = y;
385
+ positions[i * 3 + 2] = z;
386
+ }
387
+ geometry.setAttribute("position", new THREE4.BufferAttribute(positions, 3));
388
+ geometry.computeBoundingSphere();
389
+ return geometry;
390
+ }
391
+ function buildUniforms({
392
+ color,
393
+ alpha,
394
+ wind,
395
+ baseSize,
396
+ distanceOffset,
397
+ distanceStrength
398
+ }) {
399
+ return {
400
+ uColor: { value: new THREE4.Color(color) },
401
+ uAlpha: { value: alpha },
402
+ uTime: { value: 0 },
403
+ uWind: { value: wind },
404
+ uBaseSize: { value: baseSize },
405
+ uDistanceOffset: { value: distanceOffset },
406
+ uDistanceStrength: { value: distanceStrength }
407
+ };
408
+ }
409
+ function OranoParticles({
410
+ className,
411
+ style,
412
+ width,
413
+ height,
414
+ color,
415
+ alpha,
416
+ wind,
417
+ baseSize,
418
+ distanceOffset,
419
+ distanceStrength,
420
+ particleCount,
421
+ ...divProps
422
+ }) {
423
+ const particlesRef = useRef4(null);
424
+ const initialUniformsRef = useRef4({
425
+ color,
426
+ alpha,
427
+ wind,
428
+ baseSize,
429
+ distanceOffset,
430
+ distanceStrength,
431
+ particleCount
432
+ });
433
+ initialUniformsRef.current = {
434
+ color,
435
+ alpha,
436
+ wind,
437
+ baseSize,
438
+ distanceOffset,
439
+ distanceStrength,
440
+ particleCount
441
+ };
442
+ const handleCreate = useCallback3(({ scene }) => {
443
+ const uniforms = buildUniforms(initialUniformsRef.current);
444
+ const geometry = createParticleGeometry(
445
+ Math.max(1, Math.floor(initialUniformsRef.current.particleCount))
446
+ );
447
+ const material = new THREE4.ShaderMaterial({
448
+ fragmentShader: fragment_default3,
449
+ vertexShader: vertex_default3,
450
+ uniforms,
451
+ transparent: true,
452
+ depthWrite: false
453
+ });
454
+ const points = new THREE4.Points(geometry, material);
455
+ points.frustumCulled = false;
456
+ scene.add(points);
457
+ particlesRef.current = { points, geometry, material, uniforms };
458
+ return () => {
459
+ scene.remove(points);
460
+ geometry.dispose();
461
+ material.dispose();
462
+ particlesRef.current = null;
463
+ };
464
+ }, []);
465
+ const handleRender = useCallback3((context) => {
466
+ const assets = particlesRef.current;
467
+ if (!assets) return;
468
+ assets.uniforms.uTime.value = context.clock.getElapsedTime();
469
+ }, []);
470
+ const { containerRef } = useScene({
471
+ onCreate: handleCreate,
472
+ onRender: handleRender
473
+ });
474
+ useEffect4(() => {
475
+ const assets = particlesRef.current;
476
+ if (!assets) return;
477
+ assets.uniforms.uColor.value.set(color);
478
+ }, [color]);
479
+ useEffect4(() => {
480
+ const assets = particlesRef.current;
481
+ if (!assets) return;
482
+ assets.uniforms.uAlpha.value = alpha;
483
+ }, [alpha]);
484
+ useEffect4(() => {
485
+ const assets = particlesRef.current;
486
+ if (!assets) return;
487
+ assets.uniforms.uWind.value = wind;
488
+ }, [wind]);
489
+ useEffect4(() => {
490
+ const assets = particlesRef.current;
491
+ if (!assets) return;
492
+ assets.uniforms.uBaseSize.value = baseSize;
493
+ }, [baseSize]);
494
+ useEffect4(() => {
495
+ const assets = particlesRef.current;
496
+ if (!assets) return;
497
+ assets.uniforms.uDistanceOffset.value = distanceOffset;
498
+ }, [distanceOffset]);
499
+ useEffect4(() => {
500
+ const assets = particlesRef.current;
501
+ if (!assets) return;
502
+ assets.uniforms.uDistanceStrength.value = distanceStrength;
503
+ }, [distanceStrength]);
504
+ useEffect4(() => {
505
+ const assets = particlesRef.current;
506
+ if (!assets) return;
507
+ const geometry = createParticleGeometry(
508
+ Math.max(1, Math.floor(particleCount))
509
+ );
510
+ assets.points.geometry.dispose();
511
+ assets.points.geometry = geometry;
512
+ assets.geometry = geometry;
513
+ }, [particleCount]);
514
+ return /* @__PURE__ */ jsx3(
515
+ "div",
516
+ {
517
+ ref: containerRef,
518
+ className,
519
+ style: {
520
+ width: width ?? "100%",
521
+ height: height ?? "100%",
522
+ ...style
523
+ },
524
+ ...divProps
525
+ }
526
+ );
527
+ }
528
+ export {
529
+ FractalFlower,
530
+ OranoParticles,
531
+ ShaderArt
532
+ };
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@toriistudio/shader-ui",
3
+ "version": "0.0.1",
4
+ "description": "Shader components",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/types/index.d.ts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "./preset": {
20
+ "import": "./dist/preset.mjs",
21
+ "require": "./dist/preset.js"
22
+ },
23
+ "./styles.css": "./dist/index.css"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "sideEffects": false,
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "local:push": "yarn build && yalc push",
34
+ "release:patch": "npm version patch && git push --follow-tags && npm publish",
35
+ "release:minor": "npm version minor && git push --follow-tags && npm publish",
36
+ "release:major": "npm version major && git push --follow-tags && npm publish",
37
+ "test": "echo \"Error: no test specified\" && exit 1"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/toriistudio/shader-ui.git"
42
+ },
43
+ "keywords": [
44
+ "react",
45
+ "component",
46
+ "headless",
47
+ "three",
48
+ "shaders"
49
+ ],
50
+ "author": "toriistudio",
51
+ "license": "MIT",
52
+ "bugs": {
53
+ "url": "https://github.com/toriistudio/shader-ui/issues"
54
+ },
55
+ "homepage": "https://github.com/toriistudio/shader-ui#readme",
56
+ "peerDependencies": {
57
+ "react": ">=19 <20",
58
+ "react-dom": ">=19 <20",
59
+ "three": ">=0.180 <1",
60
+ "three-stdlib": ">=2.36.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "three": {
64
+ "optional": true
65
+ },
66
+ "three-stdlib": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "devDependencies": {
71
+ "@types/node": "^24.0.3",
72
+ "@types/react": "^19.0.0",
73
+ "@types/react-dom": "^19.0.0",
74
+ "three": "^0.177.0",
75
+ "three-stdlib": "^2.36.0",
76
+ "tsup": "^8.4.0",
77
+ "typescript": "^5.8.3"
78
+ },
79
+ "publishConfig": {
80
+ "access": "public"
81
+ },
82
+ "engines": {
83
+ "node": ">=18"
84
+ },
85
+ "tsup": {
86
+ "entry": [
87
+ "src/index.ts"
88
+ ],
89
+ "dts": true,
90
+ "format": [
91
+ "cjs",
92
+ "esm"
93
+ ],
94
+ "sourcemap": true,
95
+ "clean": true,
96
+ "external": [
97
+ "react",
98
+ "react-dom",
99
+ "three",
100
+ "three-stdlib"
101
+ ]
102
+ }
103
+ }