I've been building an open-source project for a while now, and it reached a point where the README was doing too much. New people had to scroll through a wall of markdown before they even understood what it does.
So I decided to build a proper landing page. No React. No Next.js. No Tailwind. Just HTML, CSS, vanilla JS, and Three.js for some visual fun.
Here's what the process looked like and what I'd do differently.
Why no framework?
Honestly? I didn't need one. The site is 9 static pages. There's no state management, no API calls, no auth. A framework would've added build complexity for zero benefit.
The entire site is:
-
index.html— the homepage -
style.css— one stylesheet -
content.js— all page content as a JS object -
app.js— Three.js scene + scroll logic + SPA navigation -
build.js— a Node script that pre-renders everything to static HTML for SEO
That's it. No node_modules with 400 packages. No webpack config. No hydration bugs.
The Three.js particle brain
I wanted the site to feel like the tool itself — it's a screen memory tool, so I built a particle system that looks like a neural network. Each node is a glowing point, connected by faint lines, forming a brain-like cluster.
The scroll position controls the camera. As you scroll through the page, the brain rotates and the panels fade in/out. Your cursor influences nearby particles — they push away slightly, like you're touching the neurons.
Getting the scroll sync right was the trickiest part. The body is 600vh tall (just to give scroll distance), the panels are position: fixed, and their opacity is driven by scroll progress mapped to [0, 1]. Each panel has a data-a and data-b attribute defining when it fades in and out.
const t = scrollY / (document.body.scrollHeight - innerHeight);
panels.forEach(p => {
const a = +p.dataset.a, b = +p.dataset.b;
const visible = t >= a && t <= b;
p.classList.toggle('on', visible);
});
Simple, but it took a lot of tweaking to get the timing to feel natural.
SPA navigation without a router
When you click a docs link, instead of a full page load, the app plays a "warp" transition — the particles accelerate forward like a light-speed jump, then the doc content fades in.
But here's the thing — every doc page also exists as a real, pre-rendered HTML file. So if Google crawls /features/, it gets full static HTML with all the meta tags. If a user navigates there from the homepage, they get the animated transition. Best of both worlds.
The build.js script reads the same content.js data and generates static HTML for each page:
/features/index.html
/install/index.html
/privacy/index.html
...
Each one includes the full article content, sidebar nav, footer with links, and proper <meta> tags. No JavaScript required to see the content.
The SEO rabbit hole
This is where I spent way more time than expected.
-
Pre-rendering — Google can't see content that only exists in JavaScript. Every page needed to be a real
.htmlfile with the content baked in. -
Canonical URLs — each page gets a
<link rel="canonical">pointing to itself. -
JSON-LD structured data —
SoftwareApplicationschema on the homepage,TechArticleon doc pages,VideoObjectfor the demo video. - OG images — I learned the hard way that a 443×563 logo is not an OG image. Social previews need 1200×630 landscape.
-
Sitemap — generated automatically by
build.js. Then Google couldn't fetch it because of Cloudflare bot protection on*.pages.devsubdomains. Ended up hosting a fallback on a GitHub Gist and pointingrobots.txtto both.
That last one took me embarrassingly long to debug.
Things I'd do differently
Start with the pre-rendered build script. I built the SPA first and then realized Google couldn't see anything. Should've started with static HTML and added the JS sugar on top.
Design the mobile footer early. I kept tweaking the footer layout on desktop and it looked terrible on mobile every time. Ended up making it stack vertically with inline links on small screens.
Don't overthink the particle count. I started with 2000 particles and it lagged on mobile. 600 looks almost the same and runs at 60fps everywhere.
The result
The site is live at screenmind.pages.dev if you want to poke around. The full source is in a separate repo from the main project.
The project itself is ScreenMind — a local AI screen memory tool. But this post was really about the landing page journey. Building something visual with vanilla web tech in 2025 is still totally viable, and honestly kind of refreshing.
What's your approach for project landing pages? Framework or vanilla? I'm curious what others do.# I built a landing page with Three.js, vanilla JS, and zero frameworks — here's what I learned
Top comments (0)