shaderz 1.0.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 ADDED
@@ -0,0 +1,91 @@
1
+ # Shaderz CLI
2
+
3
+ Add beautiful WebGL shaders to your React/Next.js project with a simple CLI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install shaderz
9
+ ```
10
+
11
+ Or use with npx (no installation required):
12
+
13
+ ```bash
14
+ npx shaderz add
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Interactive Installation
20
+
21
+ Install shaders interactively by selecting from a list:
22
+
23
+ ```bash
24
+ npx shaderz add
25
+ ```
26
+
27
+ This will:
28
+ 1. Show a checkbox list of all available shaders
29
+ 2. Let you select multiple shaders with Space
30
+ 3. Install selected shaders to `components/shaders/`
31
+ 4. Copy video files to `public/videos/` (for video shaders)
32
+ 5. Check and remind you to install required dependencies
33
+ 6. Show usage examples
34
+
35
+ ## Available Shaders
36
+
37
+ **WebGL Shaders:**
38
+ - `liquid-orange` - Flowing liquid shader with warm orange tones
39
+ - `ocean-waves` - Dynamic ocean waves shader
40
+ - `neon-fluid` - Vibrant neon fluid shader
41
+ - `gradient-waves` - Smooth gradient waves shader
42
+ - `cosmic-nebula` - Space-themed nebula shader
43
+ - `glossy-ribbon` - Glossy ribbon flow shader
44
+ - `silk-flow` - Smooth silk flow shader
45
+ - `glass-twist` - Glass twist effect shader
46
+ - `plasma` - Classic plasma shader
47
+
48
+ **Video Background:**
49
+ - `glossy-film` - MP4 video background (copies video to public/videos/)
50
+
51
+ ## Usage in Your Project
52
+
53
+ After installation:
54
+
55
+ ```tsx
56
+ import LiquidOrangeShader from '@/components/shaders/LiquidOrangeShader';
57
+
58
+ function App() {
59
+ return (
60
+ <div className="relative w-full h-screen">
61
+ <LiquidOrangeShader />
62
+ {/* Your content */}
63
+ </div>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ### Video Background
69
+
70
+ ```tsx
71
+ import VideoBackground from '@/components/shaders/VideoBackground';
72
+
73
+ function App() {
74
+ return (
75
+ <div className="relative w-full h-screen">
76
+ <VideoBackground src="/videos/glossy-film.mp4" />
77
+ {/* Your content */}
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ## Requirements
84
+
85
+ - React 18+ or 19+
86
+ - No additional dependencies required (WebGL is built into modern browsers)
87
+ - Video shader requires the video file to be in public/videos/ (automatically handled by CLI)
88
+
89
+ ## License
90
+
91
+ MIT © harsh and shubham
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_commander = require("commander");
28
+ var import_prompts = __toESM(require("prompts"));
29
+ var import_chalk = __toESM(require("chalk"));
30
+ var import_ora = __toESM(require("ora"));
31
+ var import_fs_extra = __toESM(require("fs-extra"));
32
+ var import_path = __toESM(require("path"));
33
+ var program = new import_commander.Command();
34
+ var SHADERS = [
35
+ {
36
+ name: "liquid-orange",
37
+ title: "Liquid Orange",
38
+ description: "Flowing liquid shader with warm orange tones",
39
+ file: "LiquidOrangeShader"
40
+ },
41
+ {
42
+ name: "ocean-waves",
43
+ title: "Ocean Waves",
44
+ description: "Dynamic ocean waves shader",
45
+ file: "OceanWavesShader"
46
+ },
47
+ {
48
+ name: "neon-fluid",
49
+ title: "Neon Fluid",
50
+ description: "Vibrant neon fluid shader",
51
+ file: "NeonFluidShader"
52
+ },
53
+ {
54
+ name: "gradient-waves",
55
+ title: "Gradient Waves",
56
+ description: "Smooth gradient waves shader",
57
+ file: "GradientWavesShader"
58
+ },
59
+ {
60
+ name: "cosmic-nebula",
61
+ title: "Cosmic Nebula",
62
+ description: "Space-themed nebula shader",
63
+ file: "CosmicNebulaShader"
64
+ },
65
+ {
66
+ name: "glossy-ribbon",
67
+ title: "Glossy Ribbon",
68
+ description: "Glossy ribbon flow shader",
69
+ file: "GlossyRibbonShader"
70
+ },
71
+ {
72
+ name: "silk-flow",
73
+ title: "Silk Flow",
74
+ description: "Smooth silk flow shader",
75
+ file: "SilkFlowShader"
76
+ },
77
+ {
78
+ name: "glass-twist",
79
+ title: "Glass Twist",
80
+ description: "Glass twist effect shader",
81
+ file: "GlassTwistShader"
82
+ },
83
+ {
84
+ name: "plasma",
85
+ title: "Plasma",
86
+ description: "Classic plasma shader",
87
+ file: "PlasmaShader"
88
+ },
89
+ {
90
+ name: "glossy-film",
91
+ title: "Glossy Film (Video)",
92
+ description: "MP4 video background shader",
93
+ file: "VideoBackground",
94
+ isVideo: true,
95
+ videoFile: "glossy-film.mp4"
96
+ }
97
+ ];
98
+ async function addShaders() {
99
+ console.log(import_chalk.default.bold.cyan("\n\u2728 Welcome to Shaderz!\n"));
100
+ const response = await (0, import_prompts.default)({
101
+ type: "multiselect",
102
+ name: "shaders",
103
+ message: "Select shaders to add to your project:",
104
+ choices: SHADERS.map((shader) => ({
105
+ title: `${shader.title} - ${import_chalk.default.gray(shader.description)}`,
106
+ value: shader.name,
107
+ selected: false
108
+ })),
109
+ hint: "- Space to select. Return to submit"
110
+ });
111
+ if (!response.shaders || response.shaders.length === 0) {
112
+ console.log(import_chalk.default.yellow("No shaders selected. Exiting."));
113
+ process.exit(0);
114
+ }
115
+ const targetDir = process.cwd();
116
+ const possiblePaths = [
117
+ "src/components",
118
+ "app/components",
119
+ "components"
120
+ ];
121
+ let componentsBase = "";
122
+ for (const possiblePath of possiblePaths) {
123
+ if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, possiblePath))) {
124
+ componentsBase = possiblePath;
125
+ break;
126
+ }
127
+ }
128
+ if (!componentsBase) {
129
+ if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, "src"))) {
130
+ componentsBase = "src/components";
131
+ } else if (import_fs_extra.default.existsSync(import_path.default.join(targetDir, "app"))) {
132
+ componentsBase = "app/components";
133
+ } else {
134
+ componentsBase = "components";
135
+ }
136
+ console.log(import_chalk.default.yellow(`
137
+ No components directory found. Will create: ${componentsBase}/shaders/`));
138
+ const { createDir } = await (0, import_prompts.default)({
139
+ type: "confirm",
140
+ name: "createDir",
141
+ message: "Continue?",
142
+ initial: true
143
+ });
144
+ if (!createDir) {
145
+ console.log(import_chalk.default.red("Installation cancelled."));
146
+ process.exit(1);
147
+ }
148
+ }
149
+ const componentsDir = import_path.default.join(targetDir, componentsBase, "shaders");
150
+ const publicVideosDir = import_path.default.join(targetDir, "public", "videos");
151
+ const useTypeScript = import_fs_extra.default.existsSync(import_path.default.join(targetDir, "tsconfig.json"));
152
+ const fileExtension = useTypeScript ? ".tsx" : ".jsx";
153
+ await import_fs_extra.default.ensureDir(componentsDir);
154
+ const spinner = (0, import_ora.default)("Installing shaders...").start();
155
+ try {
156
+ for (const shaderName of response.shaders) {
157
+ const shader = SHADERS.find((s) => s.name === shaderName);
158
+ if (!shader) continue;
159
+ if (shader.isVideo && shader.videoFile) {
160
+ await import_fs_extra.default.ensureDir(publicVideosDir);
161
+ const sourceVideo = import_path.default.join(__dirname, "..", "videos", shader.videoFile);
162
+ const targetVideo = import_path.default.join(publicVideosDir, shader.videoFile);
163
+ await import_fs_extra.default.copy(sourceVideo, targetVideo);
164
+ spinner.succeed(`Added ${import_chalk.default.green(shader.title)} (video copied to public/videos/)`);
165
+ spinner.start();
166
+ }
167
+ const sourceFile = import_path.default.join(__dirname, "..", "shaders", `${shader.file}.tsx`);
168
+ const targetFile = import_path.default.join(componentsDir, `${shader.file}${fileExtension}`);
169
+ await import_fs_extra.default.copy(sourceFile, targetFile);
170
+ spinner.succeed(`Added ${import_chalk.default.green(shader.title)}`);
171
+ spinner.start();
172
+ }
173
+ spinner.stop();
174
+ console.log(import_chalk.default.bold.green("\n\u2705 Shaders installed successfully!\n"));
175
+ console.log(import_chalk.default.gray("Location:"), import_chalk.default.cyan(`${componentsBase}/shaders/
176
+ `));
177
+ console.log(import_chalk.default.bold("Usage:"));
178
+ response.shaders.forEach((shaderName) => {
179
+ const shader = SHADERS.find((s) => s.name === shaderName);
180
+ if (shader) {
181
+ console.log(import_chalk.default.gray(` import ${shader.file} from '@/${componentsBase}/shaders/${shader.file}';`));
182
+ if (shader.isVideo) {
183
+ console.log(import_chalk.default.gray(` // Video file: /videos/${shader.videoFile}`));
184
+ }
185
+ }
186
+ });
187
+ console.log("");
188
+ const packageJsonPath = import_path.default.join(targetDir, "package.json");
189
+ if (import_fs_extra.default.existsSync(packageJsonPath)) {
190
+ const packageJson = await import_fs_extra.default.readJson(packageJsonPath);
191
+ const hasThree = packageJson.dependencies?.three || packageJson.devDependencies?.three;
192
+ if (!hasThree) {
193
+ console.log(import_chalk.default.yellow("\u26A0\uFE0F Required dependency not found!\n"));
194
+ console.log(import_chalk.default.gray("Install it with:\n"));
195
+ console.log(import_chalk.default.cyan(" npm install three @types/three"));
196
+ console.log(import_chalk.default.gray(" or"));
197
+ console.log(import_chalk.default.cyan(" pnpm add three @types/three\n"));
198
+ }
199
+ }
200
+ } catch (error) {
201
+ spinner.fail("Failed to install shaders");
202
+ console.error(import_chalk.default.red(error));
203
+ process.exit(1);
204
+ }
205
+ }
206
+ program.name("shaderz").description("CLI to add beautiful WebGL shaders to your project").version("1.0.0");
207
+ program.command("add").description("Add shaders to your project").action(addShaders);
208
+ program.parse();
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/index.ts
17
+ var index_exports = {};
18
+ module.exports = __toCommonJS(index_exports);
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "shaderz",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to add beautiful WebGL shaders to your React/Next.js project",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "shaderz": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "shaders",
12
+ "videos"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsup src/cli.ts src/index.ts --format cjs --dts --shims",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "shaderz",
20
+ "shader",
21
+ "webgl",
22
+ "cli",
23
+ "components",
24
+ "react",
25
+ "nextjs"
26
+ ],
27
+ "author": "harsh and shubham",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "commander": "^12.0.0",
31
+ "prompts": "^2.4.2",
32
+ "chalk": "^4.1.2",
33
+ "ora": "^5.4.1",
34
+ "fs-extra": "^11.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/prompts": "^2.4.9",
38
+ "@types/fs-extra": "^11.0.4",
39
+ "@types/node": "^22.10.2",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "^5.7.2"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/harsh-and-shubham/shaderz"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/harsh-and-shubham/shaderz/issues"
49
+ },
50
+ "homepage": "https://shaderz.vercel.app"
51
+ }
@@ -0,0 +1,265 @@
1
+ 'use client';
2
+ import React, { useRef, useEffect } from 'react';
3
+
4
+ const CosmicNebulaShader: React.FC = () => {
5
+ const canvasRef = useRef<HTMLCanvasElement>(null);
6
+ const containerRef = useRef<HTMLDivElement>(null);
7
+ const animationRef = useRef<number | null>(null);
8
+
9
+ useEffect(() => {
10
+ const canvas = canvasRef.current;
11
+ const container = containerRef.current;
12
+ if (!canvas || !container) return;
13
+
14
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
15
+ if (!gl) return;
16
+
17
+ const resizeCanvas = () => {
18
+ const rect = container.getBoundingClientRect();
19
+ canvas.width = rect.width;
20
+ canvas.height = rect.height;
21
+ gl.viewport(0, 0, canvas.width, canvas.height);
22
+ };
23
+
24
+ resizeCanvas();
25
+ window.addEventListener('resize', resizeCanvas);
26
+
27
+ const vertexShaderSource = `
28
+ attribute vec2 position;
29
+ void main() {
30
+ gl_Position = vec4(position, 0.0, 1.0);
31
+ }
32
+ `;
33
+
34
+ const fragmentShaderSource = `
35
+ precision highp float;
36
+ uniform vec2 resolution;
37
+ uniform float time;
38
+
39
+ // Hash for pseudo-random values
40
+ float hash(vec3 p) {
41
+ p = fract(p * 0.3183099 + 0.1);
42
+ p *= 17.0;
43
+ return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
44
+ }
45
+
46
+ // 3D noise
47
+ float noise(vec3 x) {
48
+ vec3 p = floor(x);
49
+ vec3 f = fract(x);
50
+ f = f * f * (3.0 - 2.0 * f);
51
+
52
+ return mix(
53
+ mix(mix(hash(p), hash(p + vec3(1,0,0)), f.x),
54
+ mix(hash(p + vec3(0,1,0)), hash(p + vec3(1,1,0)), f.x), f.y),
55
+ mix(mix(hash(p + vec3(0,0,1)), hash(p + vec3(1,0,1)), f.x),
56
+ mix(hash(p + vec3(0,1,1)), hash(p + vec3(1,1,1)), f.x), f.y),
57
+ f.z);
58
+ }
59
+
60
+ // Fractal Brownian Motion for nebula clouds
61
+ float fbm(vec3 p) {
62
+ float value = 0.0;
63
+ float amplitude = 0.5;
64
+ float frequency = 1.0;
65
+
66
+ for(int i = 0; i < 6; i++) {
67
+ value += amplitude * noise(p * frequency);
68
+ frequency *= 2.1;
69
+ amplitude *= 0.45;
70
+ }
71
+ return value;
72
+ }
73
+
74
+ // Domain warping for nebula distortion
75
+ vec3 domainWarp(vec3 p, float time) {
76
+ vec3 q = vec3(
77
+ fbm(p + vec3(0.0, 0.0, time * 0.1)),
78
+ fbm(p + vec3(5.2, 1.3, time * 0.15)),
79
+ fbm(p + vec3(1.7, 9.2, time * 0.08))
80
+ );
81
+
82
+ return p + q * 0.5;
83
+ }
84
+
85
+ // Stars
86
+ float stars(vec2 p, float count) {
87
+ vec2 pos = floor(p * count);
88
+ float star = hash(vec3(pos, 1.0));
89
+
90
+ if(star > 0.98) {
91
+ vec2 center = (pos + 0.5) / count;
92
+ float dist = length(p - center);
93
+ float brightness = hash(vec3(pos, 2.0));
94
+ float twinkle = sin(time * 2.0 + brightness * 10.0) * 0.5 + 0.5;
95
+ return (1.0 - smoothstep(0.0, 0.002, dist)) * brightness * twinkle;
96
+ }
97
+ return 0.0;
98
+ }
99
+
100
+ void main() {
101
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
102
+ vec2 p = (uv - 0.5) * 2.0;
103
+ p.x *= resolution.x / resolution.y;
104
+
105
+ float t = time * 0.15;
106
+
107
+ // 3D position for nebula
108
+ vec3 pos = vec3(p * 1.5, t);
109
+
110
+ // Domain warp for swirling nebula
111
+ vec3 warped = domainWarp(pos, t);
112
+
113
+ // Layered nebula clouds
114
+ float nebula1 = fbm(warped * 2.0);
115
+ float nebula2 = fbm(warped * 3.0 + vec3(2.0, 1.0, 0.0));
116
+ float nebula3 = fbm(warped * 4.0 - vec3(1.0, 2.0, 0.0));
117
+
118
+ // Combine nebula layers
119
+ float nebulaDensity = nebula1 * 0.6 + nebula2 * 0.3 + nebula3 * 0.1;
120
+ nebulaDensity = pow(nebulaDensity, 1.5);
121
+
122
+ // Cosmic color palette
123
+ vec3 deepPurple = vec3(0.15, 0.05, 0.3);
124
+ vec3 magenta = vec3(0.8, 0.1, 0.6);
125
+ vec3 pink = vec3(1.0, 0.3, 0.7);
126
+ vec3 cyan = vec3(0.2, 0.7, 1.0);
127
+ vec3 violet = vec3(0.5, 0.2, 0.9);
128
+
129
+ // Color mixing based on density and position
130
+ vec3 nebulaColor = mix(deepPurple, magenta, nebulaDensity);
131
+ nebulaColor = mix(nebulaColor, pink, nebula2 * 0.7);
132
+ nebulaColor = mix(nebulaColor, violet, nebula3 * 0.5);
133
+
134
+ // Add cyan highlights in dense areas
135
+ float highlights = smoothstep(0.6, 0.9, nebulaDensity);
136
+ nebulaColor = mix(nebulaColor, cyan, highlights * 0.4);
137
+
138
+ // Glow effect
139
+ float glow = pow(nebulaDensity, 0.8) * 1.5;
140
+ nebulaColor *= glow;
141
+
142
+ // Add stars
143
+ float starField = stars(uv, 200.0);
144
+ starField += stars(uv * 1.5, 300.0) * 0.7;
145
+ starField += stars(uv * 2.0, 400.0) * 0.5;
146
+
147
+ vec3 starColor = vec3(1.0, 0.95, 0.9) * starField;
148
+
149
+ // Combine nebula and stars
150
+ vec3 finalColor = nebulaColor + starColor;
151
+
152
+ // Add subtle color shift animation
153
+ float colorShift = sin(t * 0.5 + length(p)) * 0.5 + 0.5;
154
+ finalColor += vec3(0.1, 0.0, 0.15) * colorShift * nebulaDensity * 0.3;
155
+
156
+ // Center bright spot
157
+ float centerGlow = 1.0 - length(p * 0.5);
158
+ centerGlow = pow(centerGlow, 3.0) * 0.15;
159
+ finalColor += vec3(0.6, 0.2, 0.8) * centerGlow;
160
+
161
+ // Vignette
162
+ float vignette = 1.0 - length(uv - 0.5) * 0.7;
163
+ finalColor *= vignette;
164
+
165
+ gl_FragColor = vec4(finalColor, 1.0);
166
+ }
167
+ `;
168
+
169
+ const createShader = (gl: WebGLRenderingContext, type: number, source: string) => {
170
+ const shader = gl.createShader(type);
171
+ if (!shader) return null;
172
+
173
+ gl.shaderSource(shader, source);
174
+ gl.compileShader(shader);
175
+
176
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
177
+ console.error('Shader compile error:', gl.getShaderInfoLog(shader));
178
+ gl.deleteShader(shader);
179
+ return null;
180
+ }
181
+
182
+ return shader;
183
+ };
184
+
185
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
186
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
187
+
188
+ if (!vertexShader || !fragmentShader) return;
189
+
190
+ const program = gl.createProgram();
191
+ if (!program) return;
192
+
193
+ gl.attachShader(program, vertexShader);
194
+ gl.attachShader(program, fragmentShader);
195
+ gl.linkProgram(program);
196
+
197
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
198
+ console.error('Program link error:', gl.getProgramInfoLog(program));
199
+ return;
200
+ }
201
+
202
+ const positionBuffer = gl.createBuffer();
203
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
204
+ gl.bufferData(
205
+ gl.ARRAY_BUFFER,
206
+ new Float32Array([
207
+ -1, -1,
208
+ 1, -1,
209
+ -1, 1,
210
+ 1, 1,
211
+ ]),
212
+ gl.STATIC_DRAW
213
+ );
214
+
215
+ const positionLocation = gl.getAttribLocation(program, 'position');
216
+ const resolutionLocation = gl.getUniformLocation(program, 'resolution');
217
+ const timeLocation = gl.getUniformLocation(program, 'time');
218
+
219
+ let startTime = Date.now();
220
+
221
+ const render = () => {
222
+ if (!gl || !canvas) return;
223
+
224
+ gl.clearColor(0, 0, 0, 1);
225
+ gl.clear(gl.COLOR_BUFFER_BIT);
226
+
227
+ gl.useProgram(program);
228
+
229
+ gl.enableVertexAttribArray(positionLocation);
230
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
231
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
232
+
233
+ gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
234
+ gl.uniform1f(timeLocation, (Date.now() - startTime) / 1000);
235
+
236
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
237
+
238
+ animationRef.current = requestAnimationFrame(render);
239
+ };
240
+
241
+ render();
242
+
243
+ return () => {
244
+ window.removeEventListener('resize', resizeCanvas);
245
+ if (animationRef.current) {
246
+ cancelAnimationFrame(animationRef.current);
247
+ }
248
+ gl.deleteProgram(program);
249
+ gl.deleteShader(vertexShader);
250
+ gl.deleteShader(fragmentShader);
251
+ gl.deleteBuffer(positionBuffer);
252
+ };
253
+ }, []);
254
+
255
+ return (
256
+ <div ref={containerRef} className="absolute inset-0 w-full h-full bg-black overflow-hidden">
257
+ <canvas
258
+ ref={canvasRef}
259
+ className="absolute top-0 left-0 w-full h-full"
260
+ />
261
+ </div>
262
+ );
263
+ };
264
+
265
+ export default CosmicNebulaShader;