Skip to content

Commit 3303806

Browse files
committed
Update dependencies and add HeroASCII component for enhanced visuals
- Updated `next` and `react-dom` dependencies in `package.json`. - Added `ogl` library for 3D rendering. - Introduced `HeroASCII` component for animated ASCII art display. - Modified `globals.css` to include a new grey color variable. - Simplified `page.tsx` to utilize the new `HeroASCII` component.
1 parent bf64c5d commit 3303806

File tree

5 files changed

+212
-103
lines changed

5 files changed

+212
-103
lines changed

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12+
"next": "15.2.4",
13+
"ogl": "^1.0.11",
1214
"react": "^19.0.0",
13-
"react-dom": "^19.0.0",
14-
"next": "15.2.4"
15+
"react-dom": "^19.0.0"
1516
},
1617
"devDependencies": {
17-
"typescript": "^5",
18+
"@eslint/eslintrc": "^3",
19+
"@tailwindcss/postcss": "^4",
1820
"@types/node": "^20",
1921
"@types/react": "^19",
2022
"@types/react-dom": "^19",
21-
"@tailwindcss/postcss": "^4",
22-
"tailwindcss": "^4",
2323
"eslint": "^9",
2424
"eslint-config-next": "15.2.4",
25-
"@eslint/eslintrc": "^3"
25+
"tailwindcss": "^4",
26+
"typescript": "^5"
2627
}
2728
}

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/globals.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
--color-foreground: var(--foreground);
1111
--font-sans: var(--font-geist-sans);
1212
--font-mono: var(--font-geist-mono);
13+
14+
--color-grey: #D9D9D9;
1315
}
1416

1517
@media (prefers-color-scheme: dark) {

src/app/page.tsx

Lines changed: 3 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,9 @@
1-
import Image from "next/image";
1+
import { HeroASCII } from "@/components/hero";
22

33
export default function Home() {
44
return (
5-
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
6-
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7-
<Image
8-
className="dark:invert"
9-
src="/next.svg"
10-
alt="Next.js logo"
11-
width={180}
12-
height={38}
13-
priority
14-
/>
15-
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16-
<li className="mb-2 tracking-[-.01em]">
17-
Get started by editing{" "}
18-
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
19-
src/app/page.tsx
20-
</code>
21-
.
22-
</li>
23-
<li className="tracking-[-.01em]">
24-
Save and see your changes instantly.
25-
</li>
26-
</ol>
27-
28-
<div className="flex gap-4 items-center flex-col sm:flex-row">
29-
<a
30-
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
31-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
32-
target="_blank"
33-
rel="noopener noreferrer"
34-
>
35-
<Image
36-
className="dark:invert"
37-
src="/vercel.svg"
38-
alt="Vercel logomark"
39-
width={20}
40-
height={20}
41-
/>
42-
Deploy now
43-
</a>
44-
<a
45-
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
46-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
47-
target="_blank"
48-
rel="noopener noreferrer"
49-
>
50-
Read our docs
51-
</a>
52-
</div>
53-
</main>
54-
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
55-
<a
56-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
57-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
58-
target="_blank"
59-
rel="noopener noreferrer"
60-
>
61-
<Image
62-
aria-hidden
63-
src="/file.svg"
64-
alt="File icon"
65-
width={16}
66-
height={16}
67-
/>
68-
Learn
69-
</a>
70-
<a
71-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
72-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
73-
target="_blank"
74-
rel="noopener noreferrer"
75-
>
76-
<Image
77-
aria-hidden
78-
src="/window.svg"
79-
alt="Window icon"
80-
width={16}
81-
height={16}
82-
/>
83-
Examples
84-
</a>
85-
<a
86-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
87-
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
88-
target="_blank"
89-
rel="noopener noreferrer"
90-
>
91-
<Image
92-
aria-hidden
93-
src="/globe.svg"
94-
alt="Globe icon"
95-
width={16}
96-
height={16}
97-
/>
98-
Go to nextjs.org →
99-
</a>
100-
</footer>
5+
<div className="h-screen w-screen relative">
6+
<HeroASCII />
1017
</div>
1028
);
1039
}

src/components/hero.tsx

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"use client";
2+
import { useEffect, useRef } from "react";
3+
import {
4+
Camera,
5+
Mesh,
6+
Plane,
7+
Program,
8+
Renderer,
9+
RenderTarget,
10+
Transform,
11+
Box,
12+
Vec3,
13+
} from "ogl";
14+
15+
export const HeroASCII = () => {
16+
const containerRef = useRef<HTMLDivElement>(null);
17+
useEffect(() => {
18+
const container = containerRef.current;
19+
const vertex = `#version 300 es
20+
in vec3 position;
21+
in vec3 normal;
22+
in vec2 uv;
23+
uniform mat4 modelViewMatrix;
24+
uniform mat4 projectionMatrix;
25+
uniform mat3 normalMatrix;
26+
out vec2 vUv;
27+
out vec3 vNormal;
28+
void main() {
29+
vUv = uv;
30+
vNormal = normalize(normalMatrix * normal);
31+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
32+
}`;
33+
const fragment = `#version 300 es
34+
precision mediump float;
35+
uniform float uTime;
36+
in vec2 vUv;
37+
in vec3 vNormal;
38+
out vec4 fragColor;
39+
void main() {
40+
// Simple lighting from the top-left
41+
vec3 lightDir = normalize(vec3(-1.0, 1.0, 1.0));
42+
float light = max(0.1, dot(vNormal, lightDir)) * 0.9 + 0.2;
43+
// Basic white color with light intensity
44+
fragColor = vec4(light, light, light, 1.0);
45+
}`;
46+
const asciiVertex = `#version 300 es
47+
in vec2 uv;
48+
in vec2 position;
49+
out vec2 vUv;
50+
void main() {
51+
vUv = uv;
52+
gl_Position = vec4(position, 0., 1.);
53+
}`;
54+
const asciiFragment = `#version 300 es
55+
precision highp float;
56+
uniform vec2 uResolution;
57+
uniform sampler2D uTexture;
58+
out vec4 fragColor;
59+
float character(int n, vec2 p) {
60+
p = floor(p * vec2(-4.0, 4.0) + 2.5);
61+
if(clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) {
62+
int a = int(round(p.x) + 5.0 * round(p.y));
63+
if(((n >> a) & 1) == 1) return 0.8;
64+
}
65+
return 0.0;
66+
}
67+
void main() {
68+
vec2 pix = gl_FragCoord.xy;
69+
vec3 col = texture(uTexture, floor(pix / 16.0) * 16.0 / uResolution.xy).rgb;
70+
float gray = 0.3 * col.r + 0.59 * col.g + 0.11 * col.b;
71+
int n = 4096;
72+
if(gray > 0.2) n = 65600;
73+
if(gray > 0.3) n = 163153;
74+
if(gray > 0.4) n = 15255086;
75+
if(gray > 0.5) n = 13121101;
76+
if(gray > 0.6) n = 15252014;
77+
if(gray > 0.7) n = 13195790;
78+
if(gray > 0.8) n = 11512810;
79+
vec2 p = mod(pix / 8.0, 2.0) - vec2(1.0);
80+
col = vec3(character(n, p));
81+
if (gray < 0.2) {
82+
col *= 0.4;
83+
}
84+
fragColor = vec4(col, 1.0);
85+
}`;
86+
const renderer = new Renderer({ antialias: true });
87+
const gl = renderer.gl;
88+
containerRef.current?.appendChild(gl.canvas);
89+
const camera = new Camera(gl, { near: 0.1, far: 100 });
90+
camera.position.set(2, 2, 2);
91+
camera.lookAt(new Vec3(0, 0, 0));
92+
renderer.setSize(window.innerWidth, window.innerHeight);
93+
const resize = () => {
94+
camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
95+
};
96+
window.addEventListener("resize", resize);
97+
resize();
98+
const boxProgram = new Program(gl, {
99+
vertex: vertex,
100+
fragment: fragment,
101+
uniforms: {
102+
uTime: { value: 0 },
103+
},
104+
cullFace: false,
105+
});
106+
const boxMesh = new Mesh(gl, {
107+
geometry: new Box(gl, { width: 1, height: 2, depth: 1 }),
108+
program: boxProgram,
109+
});
110+
const renderTarget = new RenderTarget(gl);
111+
const asciiProgram = new Program(gl, {
112+
vertex: asciiVertex,
113+
fragment: asciiFragment,
114+
uniforms: {
115+
uResolution: { value: [gl.canvas.width, gl.canvas.height] },
116+
uTexture: { value: renderTarget.texture },
117+
},
118+
});
119+
const asciiMesh = new Mesh(gl, {
120+
geometry: new Plane(gl, { width: 2, height: 2 }),
121+
program: asciiProgram,
122+
});
123+
const boxScene = new Transform();
124+
boxMesh.setParent(boxScene);
125+
const asciiScene = new Transform();
126+
asciiMesh.setParent(asciiScene);
127+
128+
const MAX_TILT = 0.2;
129+
const LERP_FACTOR = 0.05;
130+
131+
// Add hover animation
132+
let targetRotationX = 0;
133+
let targetRotationY = 0;
134+
let targetRotationZ = 0;
135+
let currentRotationX = 0;
136+
let currentRotationY = 0;
137+
let currentRotationZ = 0;
138+
139+
function onMouseMove(e: MouseEvent) {
140+
const x = (e.clientX / window.innerWidth) * 2 - 1;
141+
const y = (e.clientY / window.innerHeight) * 2 - 1;
142+
143+
targetRotationX = y * MAX_TILT;
144+
targetRotationY = x * MAX_TILT;
145+
targetRotationZ = -y * MAX_TILT;
146+
}
147+
148+
window.addEventListener("mousemove", onMouseMove);
149+
150+
function update(time: number) {
151+
const elapsedTime = time * 0.001;
152+
boxProgram.uniforms.uTime.value = elapsedTime;
153+
154+
// Smoothly interpolate current rotation towards target rotation
155+
currentRotationX += (targetRotationX - currentRotationX) * LERP_FACTOR;
156+
currentRotationY += (targetRotationY - currentRotationY) * LERP_FACTOR;
157+
currentRotationZ += (targetRotationZ - currentRotationZ) * LERP_FACTOR;
158+
159+
// Apply rotation to the box
160+
boxMesh.rotation.x = currentRotationX;
161+
boxMesh.rotation.y = currentRotationY;
162+
boxMesh.rotation.z = currentRotationZ;
163+
164+
renderer.render({ scene: boxScene, camera, target: renderTarget });
165+
asciiProgram.uniforms.uResolution.value = [
166+
gl.canvas.width,
167+
gl.canvas.height,
168+
];
169+
renderer.render({ scene: asciiScene, camera });
170+
}
171+
172+
let animationId: number;
173+
function animate(time: number) {
174+
update(time);
175+
animationId = requestAnimationFrame(animate);
176+
}
177+
animationId = requestAnimationFrame(animate);
178+
return () => {
179+
window.removeEventListener("resize", resize);
180+
window.removeEventListener("mousemove", onMouseMove);
181+
cancelAnimationFrame(animationId);
182+
renderer.gl.getExtension("WEBGL_lose_context")?.loseContext();
183+
if (container?.contains(gl.canvas)) {
184+
container.removeChild(gl.canvas);
185+
}
186+
};
187+
}, []);
188+
189+
return (
190+
<div ref={containerRef} className="w-full h-full absolute inset-0"></div>
191+
);
192+
};

0 commit comments

Comments
 (0)