I've been building Next.js templates as a side project and selling them on Gumroad. This weekend I shipped the fourth one: Orbit, a startup launch and waitlist landing page.
Here's a breakdown of every technical decision I made.
Why Next.js 15 with CSS Modules (no Tailwind)
Most templates use Tailwind. That's fine for customization, but it adds a compilation step and a learning curve for buyers who just want clean CSS they can read and edit.
CSS Modules give you:
- Locally scoped class names (no conflicts)
- Standard CSS syntax (no utility memorization)
- Zero runtime cost
- Works with Next.js out of the box
The tradeoff is more verbose than Tailwind for repetitive utilities. Worth it for a product you're selling.
The bento grid — 1px gap trick
The features section uses CSS Grid with grid-template-columns: repeat(3, 1fr). The first card spans 2 columns via grid-column: span 2.
.bento {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--color-border-subtle); /* gap IS the border */
border-radius: var(--radius-lg);
overflow: hidden;
}
.bento .card:first-child {
grid-column: span 2;
}
Instead of adding borders to each card, I set the grid's background to the border color and use 1px gaps. The cards themselves have no borders. This gives perfectly consistent grid lines with zero extra markup.
Count-up animation with IntersectionObserver
The metrics section triggers a count-up when the section enters the viewport:
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !started.current) {
started.current = true
const startTime = performance.now()
const tick = (now: number) => {
const progress = Math.min((now - startTime) / duration, 1)
const eased = 1 - Math.pow(1 - progress, 3) // cubic ease-out
setCount(Math.round(eased * end))
if (progress < 1) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
}
}, { threshold: 0.4 })
The started ref prevents re-triggering if the user scrolls away and back. Cubic ease-out feels much more natural than linear. No library — 30 lines of TypeScript.
Infinite logo marquee (CSS-only)
.row {
display: flex;
gap: 64px;
width: max-content;
animation: marquee 24s linear infinite;
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
The key: duplicate the logos array in the component and animate exactly -50% (half the total width). Seamless loop. Edge fade via mask-image on the parent:
.track {
mask-image: linear-gradient(
to right,
transparent 0%, black 12%, black 88%, transparent 100%
);
}
Single content file
All editable content — name, copy, nav links, logos, features, metrics, testimonials, FAQ — lives in src/lib/constants.ts. The buyer touches one file and the whole page updates. No hunting through components.
Design tokens in globals.css
8 variables to rebrand the entire template:
:root {
--color-accent: #f59e0b; /* change this → full rebrand */
--color-bg: #09090b;
--font-display: 'Sora', sans-serif;
--font-body: 'IBM Plex Sans', sans-serif;
--radius-lg: 16px;
}
Connecting the waitlist form
The form ships with a simulated delay. Replace it with your stack:
/* ConvertKit */
await fetch(`https://api.convertkit.com/v3/forms/${FORM_ID}/subscribe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ api_key: KEY, email }),
})
/* Loops */
await fetch('https://app.loops.so/api/v1/contacts/create', {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
})
Live demo: https://orbit-landing-iota.vercel.app/
The template is available on Gumroad for $29: https://devmaya.gumroad.com/l/orbit
United States
NORTH AMERICA
Related News
How Braze’s CTO is rethinking engineering for the agentic area
10h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
21h ago

Implementing Multicloud Data Sharding with Hexagonal Storage Adapters
15h ago

DeepMind’s CEO Says AGI May Be ~4 Years Away. The Last Three Missing Pieces Are Not What Most People Think.
15h ago

CCSnapshot - A Claude Code Configs Transfer Tool
21h ago