Skip to content

Commit 5e03364

Browse files
committed
feat(website): add background effect to homepage
1 parent ad441cd commit 5e03364

File tree

10 files changed

+338
-9
lines changed

10 files changed

+338
-9
lines changed

.pkgs/configs/eslint.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const templateIndentTags = [
1313
"ts",
1414
"tsx",
1515
"html",
16+
"glsl",
1617
"dedent",
1718
"outdent",
1819
];

.pkgs/configs/eslint.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const templateIndentTags = [
1717
"ts",
1818
"tsx",
1919
"html",
20+
"glsl",
2021
"dedent",
2122
"outdent",
2223
];

apps/website/app/(home)/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Background } from "#/components/Background";
12
import { ESLintReact } from "#/components/ESLintReact";
23
import { Card, Cards } from "fumadocs-ui/components/card";
34
import { CircleDotDashed, Gauge, Sliders, Zap } from "lucide-react";
@@ -17,6 +18,7 @@ const features = [
1718
export default function HomePage() {
1819
return (
1920
<main className="w-full min-w-0 max-w-6xl px-8 pt-4 pb-12 md:px-12 mx-auto">
21+
<Background />
2022
<ESLintReact />
2123
<article className="prose max-w-none">
2224
<p className="text-center">

apps/website/app/layout.config.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,9 @@ export const baseOptions: BaseLayoutProps = {
2727
<span>ESLint React</span>
2828
</div>
2929
),
30+
transparentMode: "top",
31+
},
32+
themeSwitch: {
33+
enabled: false,
3034
},
3135
};

apps/website/app/layout.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ const ibm_plex_mono = IBM_Plex_Mono({
1515
weight: ["400", "500", "700"],
1616
});
1717

18+
const themeOptions = {
19+
defaultTheme: "dark",
20+
enabled: true,
21+
enableSystem: false,
22+
forcedTheme: "dark",
23+
};
24+
1825
export const metadata = {
1926
description: "A series of composable ESLint rules for React and friends.",
2027
title: {
@@ -45,7 +52,9 @@ export default function Layout({ children }: { children: ReactNode }) {
4552
type="image/png"
4653
/>
4754
<body className="flex flex-col min-h-screen">
48-
<RootProvider>{children}</RootProvider>
55+
<RootProvider theme={themeOptions}>
56+
{children}
57+
</RootProvider>
4958
</body>
5059
</html>
5160
</ViewTransitions>

apps/website/app/overrides.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
filter: brightness(0.5);
1414
}
1515

16+
#nd-home-layout {
17+
position: relative;
18+
}
19+
1620
#nd-page .bsky-post [class*="embed-module_external"] {
1721
display: none;
1822
}
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
"use client";
2+
3+
import type { PropsWithChildren } from "react";
4+
import { cn } from "#/lib/cn";
5+
import glsl from "dedent";
6+
import { Mesh, Program, Renderer, Triangle, Vec2, Vec3 } from "ogl";
7+
import { useEffect, useRef } from "react";
8+
9+
const vertex = glsl`
10+
#version 300 es
11+
12+
precision highp float;
13+
14+
in vec3 position;
15+
out vec2 vPos;
16+
17+
void main(){
18+
gl_Position=vec4(position,1.);
19+
vPos=position.xy;
20+
}
21+
`;
22+
23+
const fragment = glsl`
24+
#version 300 es
25+
26+
precision highp float;
27+
28+
uniform float uTime;
29+
uniform vec2 uResolution;
30+
uniform vec3 color1;
31+
uniform float colorSpacing;
32+
uniform vec3 color4;
33+
uniform float displacement;
34+
uniform float zoom;
35+
uniform float spacing;
36+
uniform vec2 colorOffset;
37+
uniform vec2 transformPosition;
38+
uniform float noiseSize;
39+
uniform float noiseIntensity;
40+
41+
in vec2 vPos;
42+
out vec4 outColor;
43+
44+
// The MIT License
45+
// Copyright © 2017 Inigo Quilez
46+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
47+
48+
// Computes the analytic derivatives of a 3D Gradient Noise. This can be used for example to compute normals to a
49+
// 3d rocks based on Gradient Noise without approximating the gradient by having to take central differences. More
50+
// info here: http://iquilezles.org/www/articles/gradientnoise/gradientnoise.htm
51+
52+
// Value Noise 2D, Derivatives: https://www.shadertoy.com/view/4dXBRH
53+
// Gradient Noise 2D, Derivatives: https://www.shadertoy.com/view/XdXBRH
54+
// Value Noise 3D, Derivatives: https://www.shadertoy.com/view/XsXfRH
55+
// Gradient Noise 3D, Derivatives: https://www.shadertoy.com/view/4dffRH
56+
// Value Noise 2D : https://www.shadertoy.com/view/lsf3WH
57+
// Value Noise 3D : https://www.shadertoy.com/view/4sfGzS
58+
// Gradient Noise 2D : https://www.shadertoy.com/view/XdXGW8
59+
// Gradient Noise 3D : https://www.shadertoy.com/view/Xsl3Dl
60+
// Simplex Noise 2D : https://www.shadertoy.com/view/Msf3WH
61+
62+
vec3 gradientDerivativesNoise3DHash(vec3 p){
63+
p=fract(p*vec3(.1031,.1030,.0973));
64+
p+=dot(p,p.yxz+33.3333);
65+
return fract((p.xxy+p.yxx)*p.zyx);
66+
}
67+
68+
// return value noise (in x) and its derivatives (in yzw)
69+
vec4 gradientDerivativesNoise3D(in vec3 x){
70+
// grid
71+
vec3 p=floor(x);
72+
vec3 w=fract(x);
73+
74+
// quintic interpolant
75+
vec3 u=w*w*w*(w*(w*6.-15.)+10.);
76+
vec3 du=30.*w*w*(w*(w-2.)+1.);
77+
78+
// gradients
79+
vec3 ga=gradientDerivativesNoise3DHash(p+vec3(0.,0.,0.));
80+
vec3 gb=gradientDerivativesNoise3DHash(p+vec3(1.,0.,0.));
81+
vec3 gc=gradientDerivativesNoise3DHash(p+vec3(0.,1.,0.));
82+
vec3 gd=gradientDerivativesNoise3DHash(p+vec3(1.,1.,0.));
83+
vec3 ge=gradientDerivativesNoise3DHash(p+vec3(0.,0.,1.));
84+
vec3 gf=gradientDerivativesNoise3DHash(p+vec3(1.,0.,1.));
85+
vec3 gg=gradientDerivativesNoise3DHash(p+vec3(0.,1.,1.));
86+
vec3 gh=gradientDerivativesNoise3DHash(p+vec3(1.,1.,1.));
87+
88+
// projections
89+
float va=dot(ga,w-vec3(0.,0.,0.));
90+
float vb=dot(gb,w-vec3(1.,0.,0.));
91+
float vc=dot(gc,w-vec3(0.,1.,0.));
92+
float vd=dot(gd,w-vec3(1.,1.,0.));
93+
float ve=dot(ge,w-vec3(0.,0.,1.));
94+
float vf=dot(gf,w-vec3(1.,0.,1.));
95+
float vg=dot(gg,w-vec3(0.,1.,1.));
96+
float vh=dot(gh,w-vec3(1.,1.,1.));
97+
98+
// interpolations
99+
return vec4(va+u.x*(vb-va)+u.y*(vc-va)+u.z*(ve-va)+u.x*u.y*(va-vb-vc+vd)+u.y*u.z*(va-vc-ve+vg)+u.z*u.x*(va-vb-ve+vf)+(-va+vb+vc-vd+ve-vf-vg+vh)*u.x*u.y*u.z,// value
100+
ga+u.x*(gb-ga)+u.y*(gc-ga)+u.z*(ge-ga)+u.x*u.y*(ga-gb-gc+gd)+u.y*u.z*(ga-gc-ge+gg)+u.z*u.x*(ga-gb-ge+gf)+(-ga+gb+gc-gd+ge-gf-gg+gh)*u.x*u.y*u.z+// derivatives
101+
du*(vec3(vb,vc,ve)-va+u.yzx*vec3(va-vb-vc+vd,va-vc-ve+vg,va-vb-ve+vf)+u.zxy*vec3(va-vb-ve+vf,va-vb-vc+vd,va-vc-ve+vg)+u.yzx*u.zxy*(-va+vb+vc-vd+ve-vf-vg+vh)));
102+
}
103+
104+
float hash(vec2 p){
105+
p=50.*fract(p*.3183099+vec2(.71,.113));
106+
return-1.+2.*fract(p.x*p.y*(p.x+p.y));
107+
}
108+
109+
float computeNoise(in vec2 p){
110+
vec2 i=floor(p);
111+
vec2 f=fract(p);
112+
113+
vec2 u=f*f*(3.-2.*f);
114+
115+
return mix(mix(hash(i+vec2(0.,0.)),
116+
hash(i+vec2(1.,0.)),u.x),
117+
mix(hash(i+vec2(0.,1.)),
118+
hash(i+vec2(1.,1.)),u.x),u.y);
119+
}
120+
121+
void main(){
122+
vec2 pos=vPos;
123+
pos.x*=min(1.,uResolution.x/uResolution.y);
124+
pos.y*=min(1.,uResolution.y/uResolution.x);
125+
pos/=zoom;
126+
pos+=transformPosition;
127+
128+
vec2 noiseLocalPosition=pos*.5+.5;
129+
vec3 displacementNoise=gradientDerivativesNoise3D(vec3(noiseLocalPosition,uTime*.1)).xyz;
130+
131+
pos+=displacementNoise.xz*displacement;
132+
133+
vec2 offsettedPosition=pos;
134+
offsettedPosition-=colorOffset;
135+
offsettedPosition=mod(offsettedPosition-spacing,vec2(spacing*2.))-spacing;
136+
137+
vec3 color=vec3(0.);
138+
color=mix(color1,color,smoothstep(0.,1.,distance(offsettedPosition,vec2(0.,colorSpacing*1.5))));
139+
color=mix(color4,color,smoothstep(0.,1.,distance(offsettedPosition,vec2(0.,-colorSpacing*1.5))));
140+
float noise=computeNoise(vPos*uResolution/noiseSize);
141+
color+=noise*noiseIntensity;
142+
color=clamp(color,0.,1.);
143+
144+
outColor=vec4(color,.0125);
145+
}
146+
`;
147+
148+
function createUniforms(width: number, height: number) {
149+
return {
150+
color1: {
151+
value: new Vec3(0.247, 0.341, 0.463),
152+
},
153+
color4: {
154+
value: new Vec3(0.4, 0.44, 0.54),
155+
},
156+
colorOffset: {
157+
value: new Vec2(-0.774, -0.206),
158+
},
159+
colorRotation: {
160+
value: -0.38,
161+
},
162+
colorSize: {
163+
value: 0.58,
164+
},
165+
colorSpacing: {
166+
value: 0.4,
167+
},
168+
colorSpread: {
169+
value: 4.52,
170+
},
171+
displacement: {
172+
value: 1.16,
173+
},
174+
noiseIntensity: {
175+
value: 0.08,
176+
},
177+
noiseSize: {
178+
value: 0.7,
179+
},
180+
spacing: {
181+
value: 4.27,
182+
},
183+
transformPosition: {
184+
value: new Vec2(-0.3, -0.439),
185+
},
186+
uResolution: {
187+
value: new Vec2(width, height),
188+
},
189+
uTime: {
190+
value: 0.8,
191+
},
192+
zoom: {
193+
value: 0.62,
194+
},
195+
};
196+
}
197+
198+
export type BackgroundProps = PropsWithChildren<{
199+
className?: string;
200+
}>;
201+
202+
export function Background({ children, className }: BackgroundProps) {
203+
const rRaf = useRef<number>(null);
204+
const rRoot = useRef<HTMLDivElement>(null);
205+
const rCanvas = useRef<HTMLCanvasElement>(null);
206+
207+
useEffect(() => {
208+
if (rRoot.current == null) return;
209+
if (rCanvas.current == null) return;
210+
211+
const root = rRoot.current;
212+
const canvas = rCanvas.current;
213+
const rect = root.getBoundingClientRect();
214+
const uniforms = createUniforms(rect.width, rect.height);
215+
const renderer = new Renderer({
216+
alpha: true,
217+
antialias: false,
218+
canvas,
219+
depth: false,
220+
dpr: window.devicePixelRatio,
221+
height: rect.height,
222+
powerPreference: "high-performance",
223+
premultipliedAlpha: true,
224+
webgl: 2,
225+
width: rect.width,
226+
});
227+
228+
const { gl } = renderer;
229+
const geometry = new Triangle(gl);
230+
const program = new Program(gl, {
231+
fragment,
232+
transparent: true,
233+
uniforms,
234+
vertex,
235+
});
236+
const mesh = new Mesh(gl, { geometry, program });
237+
238+
function update(time: number) {
239+
uniforms.uTime.value = time * 0.001;
240+
renderer.render({ scene: mesh });
241+
rRaf.current = requestAnimationFrame(update);
242+
}
243+
244+
const ro = new ResizeObserver((entries) => {
245+
for (const entry of entries) {
246+
if (entry.target !== root) continue;
247+
const rect = entry.contentRect;
248+
uniforms.uResolution.value.set(rect.width, rect.height);
249+
renderer.setSize(rect.width, rect.height);
250+
renderer.render({ scene: mesh });
251+
return;
252+
}
253+
});
254+
255+
ro.observe(root);
256+
rRaf.current = requestAnimationFrame(update);
257+
canvas.style.opacity = "1";
258+
259+
return () => {
260+
canvas.style.opacity = "0";
261+
if (rRaf.current != null) cancelAnimationFrame(rRaf.current);
262+
ro.disconnect();
263+
};
264+
}, []);
265+
266+
return (
267+
<div
268+
className={cn(styles.root, className)}
269+
ref={rRoot}
270+
>
271+
<canvas
272+
className={styles.canvas}
273+
ref={rCanvas}
274+
/>
275+
{children}
276+
</div>
277+
);
278+
}
279+
280+
const styles = {
281+
root: cn(
282+
"absolute",
283+
"left-0",
284+
"top-0",
285+
"w-full",
286+
"h-full",
287+
"pointer-events-none",
288+
),
289+
290+
canvas: cn(
291+
"w-full",
292+
"h-full",
293+
"bg-transparent",
294+
"opacity-0",
295+
"transition-opacity",
296+
"duration",
297+
"ease-[ease-in-out]",
298+
),
299+
};

apps/website/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
"fumadocs-twoslash": "3.1.1",
2121
"fumadocs-typescript": "4.0.3",
2222
"fumadocs-ui": "15.2.12",
23-
"lucide-react": "^0.503.0",
23+
"lucide-react": "^0.506.0",
2424
"next": "^15.3.1",
2525
"next-view-transitions": "^0.3.4",
26+
"ogl": "^1.0.11",
2627
"react": "^19.1.0",
2728
"react-dom": "^19.1.0",
2829
"shiki": "^3.3.0",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"@types/react-dom": "^19.1.3",
113113
"cross-spawn": "^7.0.6",
114114
"esbuild": "^0.25.3",
115-
"lucide-react": "^0.503.0",
115+
"lucide-react": "^0.506.0",
116116
"next": "^15.3.1",
117117
"react": "^19.1.0",
118118
"react-dom": "^19.1.0",

0 commit comments

Comments
 (0)