beads-kanban-ui 0.1.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.
Files changed (154) hide show
  1. package/.designs/beads-kanban-ui-bj0.md +73 -0
  2. package/.designs/beads-kanban-ui-qxq.md +144 -0
  3. package/.designs/epic-support.md +282 -0
  4. package/.env.local.example +2 -0
  5. package/.eslintrc.json +3 -0
  6. package/.gitattributes +3 -0
  7. package/.github/workflows/release.yml +123 -0
  8. package/.history/README_20260121193710.md +227 -0
  9. package/.history/README_20260121193918.md +227 -0
  10. package/.history/README_20260121193921.md +227 -0
  11. package/.history/README_20260121193933.md +227 -0
  12. package/.history/README_20260121193934.md +227 -0
  13. package/.history/README_20260121193944.md +227 -0
  14. package/.history/README_20260121193953.md +227 -0
  15. package/.history/src/app/page_20260121133429.tsx +134 -0
  16. package/.history/src/app/page_20260121133928.tsx +134 -0
  17. package/.history/src/app/page_20260121144850.tsx +138 -0
  18. package/.history/src/app/page_20260121144854.tsx +138 -0
  19. package/.history/src/app/page_20260121144858.tsx +138 -0
  20. package/.history/src/app/page_20260121144902.tsx +138 -0
  21. package/.history/src/app/page_20260121144906.tsx +138 -0
  22. package/.history/src/app/page_20260121144911.tsx +138 -0
  23. package/.history/src/app/page_20260121144928.tsx +138 -0
  24. package/.playwright-mcp/.playwright-mcp/morphing-dialog-wheel-scroll-fix.png +0 -0
  25. package/.playwright-mcp/beams-test.png +0 -0
  26. package/.playwright-mcp/card-verification.png +0 -0
  27. package/.playwright-mcp/design-doc-dialog-fix-verification.png +0 -0
  28. package/.playwright-mcp/dialog-width-test.png +0 -0
  29. package/.playwright-mcp/homepage.png +0 -0
  30. package/.playwright-mcp/morphing-dialog-expanded.png +0 -0
  31. package/.playwright-mcp/morphing-dialog-fixes-final.png +0 -0
  32. package/.playwright-mcp/morphing-dialog-open.png +0 -0
  33. package/.playwright-mcp/page-2026-01-21T14-08-31-529Z.png +0 -0
  34. package/.playwright-mcp/page-2026-01-21T14-09-23-431Z.png +0 -0
  35. package/.playwright-mcp/page-2026-01-21T14-10-28-773Z.png +0 -0
  36. package/.playwright-mcp/page-2026-01-21T14-10-47-432Z.png +0 -0
  37. package/.playwright-mcp/page-2026-01-21T14-11-12-350Z.png +0 -0
  38. package/.playwright-mcp/screenshot-after-click.png +0 -0
  39. package/.playwright-mcp/screenshot-after-dialog-click.png +0 -0
  40. package/.playwright-mcp/sheet-restored-after-dialog-close.png +0 -0
  41. package/.playwright-mcp/test-1-sheet-open-with-overlay.png +0 -0
  42. package/.playwright-mcp/test-2-morphing-dialog-with-overlay.png +0 -0
  43. package/.playwright-mcp/test-3-sheet-open-dark-overlay.png +0 -0
  44. package/.playwright-mcp/test-4-morphing-dialog-with-dark-overlay.png +0 -0
  45. package/.playwright-mcp/test-5-morphing-dialog-scrolled.png +0 -0
  46. package/.playwright-mcp/test-6-sheet-restored-after-dialog-close.png +0 -0
  47. package/.playwright-mcp/wheel-scroll-fixed.png +0 -0
  48. package/README.md +243 -0
  49. package/Screenshots/bead-detail.png +0 -0
  50. package/Screenshots/dashboard.png +0 -0
  51. package/Screenshots/kanban-board.png +0 -0
  52. package/components.json +27 -0
  53. package/logo/logo.svg +1 -0
  54. package/next.config.js +9 -0
  55. package/npm/README.md +37 -0
  56. package/npm/bin/cli.js +107 -0
  57. package/npm/package.json +20 -0
  58. package/npm/scripts/postinstall.js +132 -0
  59. package/package.json +62 -0
  60. package/postcss.config.js +6 -0
  61. package/public/logo.svg +1 -0
  62. package/restart.sh +5 -0
  63. package/server/Cargo.lock +1685 -0
  64. package/server/Cargo.toml +24 -0
  65. package/server/src/db.rs +570 -0
  66. package/server/src/main.rs +141 -0
  67. package/server/src/routes/beads.rs +413 -0
  68. package/server/src/routes/cli.rs +150 -0
  69. package/server/src/routes/fs.rs +360 -0
  70. package/server/src/routes/git.rs +169 -0
  71. package/server/src/routes/mod.rs +107 -0
  72. package/server/src/routes/projects.rs +177 -0
  73. package/server/src/routes/watch.rs +211 -0
  74. package/src/app/globals.css +101 -0
  75. package/src/app/layout.tsx +36 -0
  76. package/src/app/page.tsx +348 -0
  77. package/src/app/project/kanban-board.tsx +356 -0
  78. package/src/app/project/page.tsx +18 -0
  79. package/src/app/settings/page.tsx +224 -0
  80. package/src/components/Beams.css +5 -0
  81. package/src/components/Beams.jsx +307 -0
  82. package/src/components/Galaxy.css +5 -0
  83. package/src/components/Galaxy.jsx +333 -0
  84. package/src/components/activity-timeline.tsx +172 -0
  85. package/src/components/add-project-dialog.tsx +219 -0
  86. package/src/components/bead-card.tsx +196 -0
  87. package/src/components/bead-detail.tsx +306 -0
  88. package/src/components/color-picker.tsx +101 -0
  89. package/src/components/comment-input.tsx +155 -0
  90. package/src/components/comment-list.tsx +147 -0
  91. package/src/components/dependency-badge.tsx +106 -0
  92. package/src/components/design-doc-dialog.tsx +58 -0
  93. package/src/components/design-doc-preview.tsx +97 -0
  94. package/src/components/design-doc-viewer.tsx +199 -0
  95. package/src/components/editable-project-name.tsx +178 -0
  96. package/src/components/epic-card.tsx +263 -0
  97. package/src/components/folder-browser.tsx +273 -0
  98. package/src/components/footer.tsx +27 -0
  99. package/src/components/kanban/default.tsx +184 -0
  100. package/src/components/kanban-column.tsx +167 -0
  101. package/src/components/project-card.tsx +191 -0
  102. package/src/components/quick-filter-bar.tsx +279 -0
  103. package/src/components/scan-directory-dialog.tsx +368 -0
  104. package/src/components/status-donut.tsx +197 -0
  105. package/src/components/subtask-list.tsx +128 -0
  106. package/src/components/tag-picker.tsx +252 -0
  107. package/src/components/ui/.gitkeep +0 -0
  108. package/src/components/ui/alert-dialog.tsx +141 -0
  109. package/src/components/ui/avatar.tsx +67 -0
  110. package/src/components/ui/badge.tsx +230 -0
  111. package/src/components/ui/button.tsx +433 -0
  112. package/src/components/ui/card/index.tsx +24 -0
  113. package/src/components/ui/card/roiui-card.module.css +197 -0
  114. package/src/components/ui/card/roiui-card.tsx +154 -0
  115. package/src/components/ui/card/shadcn-card.tsx +76 -0
  116. package/src/components/ui/chart.tsx +369 -0
  117. package/src/components/ui/dialog.tsx +122 -0
  118. package/src/components/ui/dropdown-menu.tsx +201 -0
  119. package/src/components/ui/input.tsx +22 -0
  120. package/src/components/ui/kanban.tsx +522 -0
  121. package/src/components/ui/morphing-dialog.tsx +457 -0
  122. package/src/components/ui/popover.tsx +33 -0
  123. package/src/components/ui/progress.tsx +28 -0
  124. package/src/components/ui/scroll-area.tsx +48 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +31 -0
  127. package/src/components/ui/sheet.tsx +142 -0
  128. package/src/components/ui/skeleton.tsx +15 -0
  129. package/src/components/ui/toast.tsx +129 -0
  130. package/src/components/ui/toaster.tsx +35 -0
  131. package/src/components/ui/tooltip.tsx +30 -0
  132. package/src/hooks/.gitkeep +0 -0
  133. package/src/hooks/use-bead-filters.ts +261 -0
  134. package/src/hooks/use-beads.ts +162 -0
  135. package/src/hooks/use-branch-statuses.ts +161 -0
  136. package/src/hooks/use-epics.ts +173 -0
  137. package/src/hooks/use-file-watcher.ts +111 -0
  138. package/src/hooks/use-keyboard-navigation.ts +282 -0
  139. package/src/hooks/use-project.ts +61 -0
  140. package/src/hooks/use-projects.ts +93 -0
  141. package/src/hooks/use-toast.ts +194 -0
  142. package/src/hooks/useClickOutside.tsx +26 -0
  143. package/src/lib/.gitkeep +0 -0
  144. package/src/lib/api.ts +186 -0
  145. package/src/lib/beads-parser.ts +252 -0
  146. package/src/lib/cli.ts +193 -0
  147. package/src/lib/db.ts +145 -0
  148. package/src/lib/design-doc.ts +74 -0
  149. package/src/lib/epic-parser.ts +242 -0
  150. package/src/lib/git.ts +102 -0
  151. package/src/lib/utils.ts +12 -0
  152. package/src/types/index.ts +107 -0
  153. package/tailwind.config.ts +85 -0
  154. package/tsconfig.json +26 -0
@@ -0,0 +1,307 @@
1
+ import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';
2
+
3
+ import * as THREE from 'three';
4
+
5
+ import { Canvas, useFrame } from '@react-three/fiber';
6
+ import { PerspectiveCamera } from '@react-three/drei';
7
+ import { degToRad } from 'three/src/math/MathUtils.js';
8
+
9
+ import './Beams.css';
10
+
11
+ function extendMaterial(BaseMaterial, cfg) {
12
+ const physical = THREE.ShaderLib.physical;
13
+ const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;
14
+ const baseDefines = physical.defines ?? {};
15
+
16
+ const uniforms = THREE.UniformsUtils.clone(baseUniforms);
17
+
18
+ const defaults = new BaseMaterial(cfg.material || {});
19
+
20
+ if (defaults.color) uniforms.diffuse.value = defaults.color;
21
+ if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;
22
+ if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;
23
+ if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;
24
+ if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;
25
+
26
+ Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {
27
+ uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };
28
+ });
29
+
30
+ let vert = `${cfg.header}\n${cfg.vertexHeader ?? ''}\n${baseVert}`;
31
+ let frag = `${cfg.header}\n${cfg.fragmentHeader ?? ''}\n${baseFrag}`;
32
+
33
+ for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {
34
+ vert = vert.replace(inc, `${inc}\n${code}`);
35
+ }
36
+ for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {
37
+ frag = frag.replace(inc, `${inc}\n${code}`);
38
+ }
39
+
40
+ const mat = new THREE.ShaderMaterial({
41
+ defines: { ...baseDefines },
42
+ uniforms,
43
+ vertexShader: vert,
44
+ fragmentShader: frag,
45
+ lights: true,
46
+ fog: !!cfg.material?.fog
47
+ });
48
+
49
+ return mat;
50
+ }
51
+
52
+ const CanvasWrapper = ({ children }) => (
53
+ <Canvas dpr={[1, 2]} frameloop="always" className="beams-container">
54
+ {children}
55
+ </Canvas>
56
+ );
57
+
58
+ const hexToNormalizedRGB = hex => {
59
+ const clean = hex.replace('#', '');
60
+ const r = parseInt(clean.substring(0, 2), 16);
61
+ const g = parseInt(clean.substring(2, 4), 16);
62
+ const b = parseInt(clean.substring(4, 6), 16);
63
+ return [r / 255, g / 255, b / 255];
64
+ };
65
+
66
+ const noise = `
67
+ float random (in vec2 st) {
68
+ return fract(sin(dot(st.xy,
69
+ vec2(12.9898,78.233)))*
70
+ 43758.5453123);
71
+ }
72
+ float noise (in vec2 st) {
73
+ vec2 i = floor(st);
74
+ vec2 f = fract(st);
75
+ float a = random(i);
76
+ float b = random(i + vec2(1.0, 0.0));
77
+ float c = random(i + vec2(0.0, 1.0));
78
+ float d = random(i + vec2(1.0, 1.0));
79
+ vec2 u = f * f * (3.0 - 2.0 * f);
80
+ return mix(a, b, u.x) +
81
+ (c - a)* u.y * (1.0 - u.x) +
82
+ (d - b) * u.x * u.y;
83
+ }
84
+ vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
85
+ vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
86
+ vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
87
+ float cnoise(vec3 P){
88
+ vec3 Pi0 = floor(P);
89
+ vec3 Pi1 = Pi0 + vec3(1.0);
90
+ Pi0 = mod(Pi0, 289.0);
91
+ Pi1 = mod(Pi1, 289.0);
92
+ vec3 Pf0 = fract(P);
93
+ vec3 Pf1 = Pf0 - vec3(1.0);
94
+ vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
95
+ vec4 iy = vec4(Pi0.yy, Pi1.yy);
96
+ vec4 iz0 = Pi0.zzzz;
97
+ vec4 iz1 = Pi1.zzzz;
98
+ vec4 ixy = permute(permute(ix) + iy);
99
+ vec4 ixy0 = permute(ixy + iz0);
100
+ vec4 ixy1 = permute(ixy + iz1);
101
+ vec4 gx0 = ixy0 / 7.0;
102
+ vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
103
+ gx0 = fract(gx0);
104
+ vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
105
+ vec4 sz0 = step(gz0, vec4(0.0));
106
+ gx0 -= sz0 * (step(0.0, gx0) - 0.5);
107
+ gy0 -= sz0 * (step(0.0, gy0) - 0.5);
108
+ vec4 gx1 = ixy1 / 7.0;
109
+ vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
110
+ gx1 = fract(gx1);
111
+ vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
112
+ vec4 sz1 = step(gz1, vec4(0.0));
113
+ gx1 -= sz1 * (step(0.0, gx1) - 0.5);
114
+ gy1 -= sz1 * (step(0.0, gy1) - 0.5);
115
+ vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
116
+ vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
117
+ vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
118
+ vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
119
+ vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
120
+ vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
121
+ vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
122
+ vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
123
+ vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));
124
+ g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;
125
+ vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));
126
+ g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;
127
+ float n000 = dot(g000, Pf0);
128
+ float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));
129
+ float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));
130
+ float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));
131
+ float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));
132
+ float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));
133
+ float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));
134
+ float n111 = dot(g111, Pf1);
135
+ vec3 fade_xyz = fade(Pf0);
136
+ vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);
137
+ vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);
138
+ float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);
139
+ return 2.2 * n_xyz;
140
+ }
141
+ `;
142
+
143
+ const Beams = ({
144
+ beamWidth = 2,
145
+ beamHeight = 15,
146
+ beamNumber = 12,
147
+ lightColor = '#ffffff',
148
+ speed = 2,
149
+ noiseIntensity = 1.75,
150
+ scale = 0.2,
151
+ rotation = 0
152
+ }) => {
153
+ const meshRef = useRef(null);
154
+ const beamMaterial = useMemo(
155
+ () =>
156
+ extendMaterial(THREE.MeshStandardMaterial, {
157
+ header: `
158
+ varying vec3 vEye;
159
+ varying float vNoise;
160
+ varying vec2 vUv;
161
+ varying vec3 vPosition;
162
+ uniform float time;
163
+ uniform float uSpeed;
164
+ uniform float uNoiseIntensity;
165
+ uniform float uScale;
166
+ ${noise}`,
167
+ vertexHeader: `
168
+ float getPos(vec3 pos) {
169
+ vec3 noisePos =
170
+ vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;
171
+ return cnoise(noisePos);
172
+ }
173
+ vec3 getCurrentPos(vec3 pos) {
174
+ vec3 newpos = pos;
175
+ newpos.z += getPos(pos);
176
+ return newpos;
177
+ }
178
+ vec3 getNormal(vec3 pos) {
179
+ vec3 curpos = getCurrentPos(pos);
180
+ vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));
181
+ vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));
182
+ vec3 tangentX = normalize(nextposX - curpos);
183
+ vec3 tangentZ = normalize(nextposZ - curpos);
184
+ return normalize(cross(tangentZ, tangentX));
185
+ }`,
186
+ fragmentHeader: '',
187
+ vertex: {
188
+ '#include <begin_vertex>': `transformed.z += getPos(transformed.xyz);`,
189
+ '#include <beginnormal_vertex>': `objectNormal = getNormal(position.xyz);`
190
+ },
191
+ fragment: {
192
+ '#include <dithering_fragment>': `
193
+ float randomNoise = noise(gl_FragCoord.xy);
194
+ gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`
195
+ },
196
+ material: { fog: true },
197
+ uniforms: {
198
+ diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),
199
+ time: { shared: true, mixed: true, linked: true, value: 0 },
200
+ roughness: 0.3,
201
+ metalness: 0.3,
202
+ uSpeed: { shared: true, mixed: true, linked: true, value: speed },
203
+ envMapIntensity: 10,
204
+ uNoiseIntensity: noiseIntensity,
205
+ uScale: scale
206
+ }
207
+ }),
208
+ [speed, noiseIntensity, scale]
209
+ );
210
+
211
+ return (
212
+ <CanvasWrapper>
213
+ <group rotation={[0, 0, degToRad(rotation)]}>
214
+ <PlaneNoise ref={meshRef} material={beamMaterial} count={beamNumber} width={beamWidth} height={beamHeight} />
215
+ <DirLight color={lightColor} position={[0, 3, 10]} />
216
+ </group>
217
+ <ambientLight intensity={1} />
218
+ <color attach="background" args={['#000000']} />
219
+ <PerspectiveCamera makeDefault position={[0, 0, 20]} fov={30} />
220
+ </CanvasWrapper>
221
+ );
222
+ };
223
+
224
+ function createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {
225
+ const geometry = new THREE.BufferGeometry();
226
+ const numVertices = n * (heightSegments + 1) * 2;
227
+ const numFaces = n * heightSegments * 2;
228
+ const positions = new Float32Array(numVertices * 3);
229
+ const indices = new Uint32Array(numFaces * 3);
230
+ const uvs = new Float32Array(numVertices * 2);
231
+
232
+ let vertexOffset = 0;
233
+ let indexOffset = 0;
234
+ let uvOffset = 0;
235
+ const totalWidth = n * width + (n - 1) * spacing;
236
+ const xOffsetBase = -totalWidth / 2;
237
+
238
+ for (let i = 0; i < n; i++) {
239
+ const xOffset = xOffsetBase + i * (width + spacing);
240
+ const uvXOffset = Math.random() * 300;
241
+ const uvYOffset = Math.random() * 300;
242
+
243
+ for (let j = 0; j <= heightSegments; j++) {
244
+ const y = height * (j / heightSegments - 0.5);
245
+ const v0 = [xOffset, y, 0];
246
+ const v1 = [xOffset + width, y, 0];
247
+ positions.set([...v0, ...v1], vertexOffset * 3);
248
+
249
+ const uvY = j / heightSegments;
250
+ uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);
251
+
252
+ if (j < heightSegments) {
253
+ const a = vertexOffset,
254
+ b = vertexOffset + 1,
255
+ c = vertexOffset + 2,
256
+ d = vertexOffset + 3;
257
+ indices.set([a, b, c, c, b, d], indexOffset);
258
+ indexOffset += 6;
259
+ }
260
+ vertexOffset += 2;
261
+ uvOffset += 4;
262
+ }
263
+ }
264
+
265
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
266
+ geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
267
+ geometry.setIndex(new THREE.BufferAttribute(indices, 1));
268
+ geometry.computeVertexNormals();
269
+ return geometry;
270
+ }
271
+
272
+ const MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {
273
+ const mesh = useRef(null);
274
+ useImperativeHandle(ref, () => mesh.current);
275
+ const geometry = useMemo(
276
+ () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),
277
+ [count, width, height]
278
+ );
279
+ useFrame((_, delta) => {
280
+ mesh.current.material.uniforms.time.value += 0.1 * delta;
281
+ });
282
+ return <mesh ref={mesh} geometry={geometry} material={material} />;
283
+ });
284
+ MergedPlanes.displayName = 'MergedPlanes';
285
+
286
+ const PlaneNoise = forwardRef((props, ref) => (
287
+ <MergedPlanes ref={ref} material={props.material} width={props.width} count={props.count} height={props.height} />
288
+ ));
289
+ PlaneNoise.displayName = 'PlaneNoise';
290
+
291
+ const DirLight = ({ position, color }) => {
292
+ const dir = useRef(null);
293
+ useEffect(() => {
294
+ if (!dir.current) return;
295
+ const cam = dir.current.shadow.camera;
296
+ if (!cam) return;
297
+ cam.top = 24;
298
+ cam.bottom = -24;
299
+ cam.left = -24;
300
+ cam.right = 24;
301
+ cam.far = 64;
302
+ dir.current.shadow.bias = -0.004;
303
+ }, []);
304
+ return <directionalLight ref={dir} color={color} intensity={1} position={position} />;
305
+ };
306
+
307
+ export default Beams;
@@ -0,0 +1,5 @@
1
+ .galaxy-container {
2
+ width: 100%;
3
+ height: 100%;
4
+ position: relative;
5
+ }
@@ -0,0 +1,333 @@
1
+ import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
2
+ import { useEffect, useRef } from 'react';
3
+ import './Galaxy.css';
4
+
5
+ const vertexShader = `
6
+ attribute vec2 uv;
7
+ attribute vec2 position;
8
+
9
+ varying vec2 vUv;
10
+
11
+ void main() {
12
+ vUv = uv;
13
+ gl_Position = vec4(position, 0, 1);
14
+ }
15
+ `;
16
+
17
+ const fragmentShader = `
18
+ precision highp float;
19
+
20
+ uniform float uTime;
21
+ uniform vec3 uResolution;
22
+ uniform vec2 uFocal;
23
+ uniform vec2 uRotation;
24
+ uniform float uStarSpeed;
25
+ uniform float uDensity;
26
+ uniform float uHueShift;
27
+ uniform float uSpeed;
28
+ uniform vec2 uMouse;
29
+ uniform float uGlowIntensity;
30
+ uniform float uSaturation;
31
+ uniform bool uMouseRepulsion;
32
+ uniform float uTwinkleIntensity;
33
+ uniform float uRotationSpeed;
34
+ uniform float uRepulsionStrength;
35
+ uniform float uMouseActiveFactor;
36
+ uniform float uAutoCenterRepulsion;
37
+ uniform bool uTransparent;
38
+
39
+ varying vec2 vUv;
40
+
41
+ #define NUM_LAYER 4.0
42
+ #define STAR_COLOR_CUTOFF 0.2
43
+ #define MAT45 mat2(0.7071, -0.7071, 0.7071, 0.7071)
44
+ #define PERIOD 3.0
45
+
46
+ float Hash21(vec2 p) {
47
+ p = fract(p * vec2(123.34, 456.21));
48
+ p += dot(p, p + 45.32);
49
+ return fract(p.x * p.y);
50
+ }
51
+
52
+ float tri(float x) {
53
+ return abs(fract(x) * 2.0 - 1.0);
54
+ }
55
+
56
+ float tris(float x) {
57
+ float t = fract(x);
58
+ return 1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0));
59
+ }
60
+
61
+ float trisn(float x) {
62
+ float t = fract(x);
63
+ return 2.0 * (1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0))) - 1.0;
64
+ }
65
+
66
+ vec3 hsv2rgb(vec3 c) {
67
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
68
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
69
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
70
+ }
71
+
72
+ float Star(vec2 uv, float flare) {
73
+ float d = length(uv);
74
+ float m = (0.05 * uGlowIntensity) / d;
75
+ float rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
76
+ m += rays * flare * uGlowIntensity;
77
+ uv *= MAT45;
78
+ rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
79
+ m += rays * 0.3 * flare * uGlowIntensity;
80
+ m *= smoothstep(1.0, 0.2, d);
81
+ return m;
82
+ }
83
+
84
+ vec3 StarLayer(vec2 uv) {
85
+ vec3 col = vec3(0.0);
86
+
87
+ vec2 gv = fract(uv) - 0.5;
88
+ vec2 id = floor(uv);
89
+
90
+ for (int y = -1; y <= 1; y++) {
91
+ for (int x = -1; x <= 1; x++) {
92
+ vec2 offset = vec2(float(x), float(y));
93
+ vec2 si = id + vec2(float(x), float(y));
94
+ float seed = Hash21(si);
95
+ float size = fract(seed * 345.32);
96
+ float glossLocal = tri(uStarSpeed / (PERIOD * seed + 1.0));
97
+ float flareSize = smoothstep(0.9, 1.0, size) * glossLocal;
98
+
99
+ float red = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 1.0)) + STAR_COLOR_CUTOFF;
100
+ float blu = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 3.0)) + STAR_COLOR_CUTOFF;
101
+ float grn = min(red, blu) * seed;
102
+ vec3 base = vec3(red, grn, blu);
103
+
104
+ float hue = atan(base.g - base.r, base.b - base.r) / (2.0 * 3.14159) + 0.5;
105
+ hue = fract(hue + uHueShift / 360.0);
106
+ float sat = length(base - vec3(dot(base, vec3(0.299, 0.587, 0.114)))) * uSaturation;
107
+ float val = max(max(base.r, base.g), base.b);
108
+ base = hsv2rgb(vec3(hue, sat, val));
109
+
110
+ vec2 pad = vec2(tris(seed * 34.0 + uTime * uSpeed / 10.0), tris(seed * 38.0 + uTime * uSpeed / 30.0)) - 0.5;
111
+
112
+ float star = Star(gv - offset - pad, flareSize);
113
+ vec3 color = base;
114
+
115
+ float twinkle = trisn(uTime * uSpeed + seed * 6.2831) * 0.5 + 1.0;
116
+ twinkle = mix(1.0, twinkle, uTwinkleIntensity);
117
+ star *= twinkle;
118
+
119
+ col += star * size * color;
120
+ }
121
+ }
122
+
123
+ return col;
124
+ }
125
+
126
+ void main() {
127
+ vec2 focalPx = uFocal * uResolution.xy;
128
+ vec2 uv = (vUv * uResolution.xy - focalPx) / uResolution.y;
129
+
130
+ vec2 mouseNorm = uMouse - vec2(0.5);
131
+
132
+ if (uAutoCenterRepulsion > 0.0) {
133
+ vec2 centerUV = vec2(0.0, 0.0);
134
+ float centerDist = length(uv - centerUV);
135
+ vec2 repulsion = normalize(uv - centerUV) * (uAutoCenterRepulsion / (centerDist + 0.1));
136
+ uv += repulsion * 0.05;
137
+ } else if (uMouseRepulsion) {
138
+ vec2 mousePosUV = (uMouse * uResolution.xy - focalPx) / uResolution.y;
139
+ float mouseDist = length(uv - mousePosUV);
140
+ vec2 repulsion = normalize(uv - mousePosUV) * (uRepulsionStrength / (mouseDist + 0.1));
141
+ uv += repulsion * 0.05 * uMouseActiveFactor;
142
+ } else {
143
+ vec2 mouseOffset = mouseNorm * 0.1 * uMouseActiveFactor;
144
+ uv += mouseOffset;
145
+ }
146
+
147
+ float autoRotAngle = uTime * uRotationSpeed;
148
+ mat2 autoRot = mat2(cos(autoRotAngle), -sin(autoRotAngle), sin(autoRotAngle), cos(autoRotAngle));
149
+ uv = autoRot * uv;
150
+
151
+ uv = mat2(uRotation.x, -uRotation.y, uRotation.y, uRotation.x) * uv;
152
+
153
+ vec3 col = vec3(0.0);
154
+
155
+ for (float i = 0.0; i < 1.0; i += 1.0 / NUM_LAYER) {
156
+ float depth = fract(i + uStarSpeed * uSpeed);
157
+ float scale = mix(20.0 * uDensity, 0.5 * uDensity, depth);
158
+ float fade = depth * smoothstep(1.0, 0.9, depth);
159
+ col += StarLayer(uv * scale + i * 453.32) * fade;
160
+ }
161
+
162
+ if (uTransparent) {
163
+ float alpha = length(col);
164
+ alpha = smoothstep(0.0, 0.3, alpha);
165
+ alpha = min(alpha, 1.0);
166
+ gl_FragColor = vec4(col, alpha);
167
+ } else {
168
+ gl_FragColor = vec4(col, 1.0);
169
+ }
170
+ }
171
+ `;
172
+
173
+ export default function Galaxy({
174
+ focal = [0.5, 0.5],
175
+ rotation = [1.0, 0.0],
176
+ starSpeed = 0.5,
177
+ density = 1,
178
+ hueShift = 140,
179
+ disableAnimation = false,
180
+ speed = 1.0,
181
+ mouseInteraction = true,
182
+ glowIntensity = 0.3,
183
+ saturation = 0.0,
184
+ mouseRepulsion = true,
185
+ repulsionStrength = 2,
186
+ twinkleIntensity = 0.3,
187
+ rotationSpeed = 0.1,
188
+ autoCenterRepulsion = 0,
189
+ transparent = true,
190
+ ...rest
191
+ }) {
192
+ const ctnDom = useRef(null);
193
+ const targetMousePos = useRef({ x: 0.5, y: 0.5 });
194
+ const smoothMousePos = useRef({ x: 0.5, y: 0.5 });
195
+ const targetMouseActive = useRef(0.0);
196
+ const smoothMouseActive = useRef(0.0);
197
+
198
+ useEffect(() => {
199
+ if (!ctnDom.current) return;
200
+ const ctn = ctnDom.current;
201
+ const renderer = new Renderer({
202
+ alpha: transparent,
203
+ premultipliedAlpha: false
204
+ });
205
+ const gl = renderer.gl;
206
+
207
+ if (transparent) {
208
+ gl.enable(gl.BLEND);
209
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
210
+ gl.clearColor(0, 0, 0, 0);
211
+ } else {
212
+ gl.clearColor(0, 0, 0, 1);
213
+ }
214
+
215
+ let program;
216
+
217
+ function resize() {
218
+ const scale = 1;
219
+ renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
220
+ if (program) {
221
+ program.uniforms.uResolution.value = new Color(
222
+ gl.canvas.width,
223
+ gl.canvas.height,
224
+ gl.canvas.width / gl.canvas.height
225
+ );
226
+ }
227
+ }
228
+ window.addEventListener('resize', resize, false);
229
+ resize();
230
+
231
+ const geometry = new Triangle(gl);
232
+ program = new Program(gl, {
233
+ vertex: vertexShader,
234
+ fragment: fragmentShader,
235
+ uniforms: {
236
+ uTime: { value: 0 },
237
+ uResolution: {
238
+ value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
239
+ },
240
+ uFocal: { value: new Float32Array(focal) },
241
+ uRotation: { value: new Float32Array(rotation) },
242
+ uStarSpeed: { value: starSpeed },
243
+ uDensity: { value: density },
244
+ uHueShift: { value: hueShift },
245
+ uSpeed: { value: speed },
246
+ uMouse: {
247
+ value: new Float32Array([smoothMousePos.current.x, smoothMousePos.current.y])
248
+ },
249
+ uGlowIntensity: { value: glowIntensity },
250
+ uSaturation: { value: saturation },
251
+ uMouseRepulsion: { value: mouseRepulsion },
252
+ uTwinkleIntensity: { value: twinkleIntensity },
253
+ uRotationSpeed: { value: rotationSpeed },
254
+ uRepulsionStrength: { value: repulsionStrength },
255
+ uMouseActiveFactor: { value: 0.0 },
256
+ uAutoCenterRepulsion: { value: autoCenterRepulsion },
257
+ uTransparent: { value: transparent }
258
+ }
259
+ });
260
+
261
+ const mesh = new Mesh(gl, { geometry, program });
262
+ let animateId;
263
+
264
+ function update(t) {
265
+ animateId = requestAnimationFrame(update);
266
+ if (!disableAnimation) {
267
+ program.uniforms.uTime.value = t * 0.001;
268
+ program.uniforms.uStarSpeed.value = (t * 0.001 * starSpeed) / 10.0;
269
+ }
270
+
271
+ const lerpFactor = 0.05;
272
+ smoothMousePos.current.x += (targetMousePos.current.x - smoothMousePos.current.x) * lerpFactor;
273
+ smoothMousePos.current.y += (targetMousePos.current.y - smoothMousePos.current.y) * lerpFactor;
274
+
275
+ smoothMouseActive.current += (targetMouseActive.current - smoothMouseActive.current) * lerpFactor;
276
+
277
+ program.uniforms.uMouse.value[0] = smoothMousePos.current.x;
278
+ program.uniforms.uMouse.value[1] = smoothMousePos.current.y;
279
+ program.uniforms.uMouseActiveFactor.value = smoothMouseActive.current;
280
+
281
+ renderer.render({ scene: mesh });
282
+ }
283
+ animateId = requestAnimationFrame(update);
284
+ ctn.appendChild(gl.canvas);
285
+
286
+ function handleMouseMove(e) {
287
+ const rect = ctn.getBoundingClientRect();
288
+ const x = (e.clientX - rect.left) / rect.width;
289
+ const y = 1.0 - (e.clientY - rect.top) / rect.height;
290
+ targetMousePos.current = { x, y };
291
+ targetMouseActive.current = 1.0;
292
+ }
293
+
294
+ function handleMouseLeave() {
295
+ targetMouseActive.current = 0.0;
296
+ }
297
+
298
+ if (mouseInteraction) {
299
+ ctn.addEventListener('mousemove', handleMouseMove);
300
+ ctn.addEventListener('mouseleave', handleMouseLeave);
301
+ }
302
+
303
+ return () => {
304
+ cancelAnimationFrame(animateId);
305
+ window.removeEventListener('resize', resize);
306
+ if (mouseInteraction) {
307
+ ctn.removeEventListener('mousemove', handleMouseMove);
308
+ ctn.removeEventListener('mouseleave', handleMouseLeave);
309
+ }
310
+ ctn.removeChild(gl.canvas);
311
+ gl.getExtension('WEBGL_lose_context')?.loseContext();
312
+ };
313
+ }, [
314
+ focal,
315
+ rotation,
316
+ starSpeed,
317
+ density,
318
+ hueShift,
319
+ disableAnimation,
320
+ speed,
321
+ mouseInteraction,
322
+ glowIntensity,
323
+ saturation,
324
+ mouseRepulsion,
325
+ twinkleIntensity,
326
+ rotationSpeed,
327
+ repulsionStrength,
328
+ autoCenterRepulsion,
329
+ transparent
330
+ ]);
331
+
332
+ return <div ref={ctnDom} className="galaxy-container" {...rest} />;
333
+ }