Skip to content

Commit f05a558

Browse files
committed
landing page
1 parent b1eb7f2 commit f05a558

4 files changed

Lines changed: 422 additions & 43 deletions

File tree

Lines changed: 322 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,335 @@
11
---
2-
// Hero content, interactions, and animation state.
2+
const snapshotSections = [
3+
{
4+
id: "project",
5+
label: "Project",
6+
title: "Project Overview",
7+
stats: ["Next.js", "TypeScript", "pnpm", "247 files", "18 routes", "6 features"],
8+
tree: ["app/", "├─ dashboard", "├─ api", "├─ auth", "└─ settings"],
9+
chips: ["Authentication", "Database", "AI Integration", "File Upload"],
10+
},
11+
{
12+
id: "routes",
13+
label: "Routes",
14+
title: "Routes & APIs",
15+
stats: ["Next.js", "TypeScript", "pnpm", "247 files", "18 routes", "6 features"],
16+
tree: ["routes/", "├─ /", "├─ /dashboard", "├─ /auth/login", "└─ /api/workspaces"],
17+
chips: ["App Router", "API Routes", "Middleware", "Layouts"],
18+
},
19+
{
20+
id: "critical",
21+
label: "Critical",
22+
title: "Critical Files",
23+
stats: ["Next.js", "TypeScript", "pnpm", "247 files", "18 routes", "6 features"],
24+
tree: ["critical/", "├─ lib/auth.ts", "├─ lib/db.ts", "├─ prisma/schema.prisma", "└─ middleware.ts"],
25+
chips: ["Auth Core", "Database", "Routing", "Security"],
26+
},
27+
{
28+
id: "features",
29+
label: "Features",
30+
title: "Feature Evidence",
31+
stats: ["Next.js", "TypeScript", "pnpm", "247 files", "18 routes", "6 features"],
32+
tree: ["features/", "├─ authentication", "├─ database", "├─ ai integration", "└─ file upload"],
33+
chips: ["Authentication", "Database", "AI Integration", "Upload"],
34+
},
35+
{
36+
id: "index",
37+
label: "File Index",
38+
title: "File Index",
39+
stats: ["Next.js", "TypeScript", "pnpm", "247 files", "18 routes", "6 features"],
40+
tree: ["index/", "├─ app/**/*.tsx", "├─ lib/**/*.ts", "├─ components/**/*.tsx", "└─ prisma/schema.prisma"],
41+
chips: ["Search Terms", "Purpose", "Scope", "Ownership"],
42+
},
43+
];
44+
45+
const typingTexts = [
46+
"Every codebase needs a map.",
47+
"Stop rebuilding context.",
48+
"Understand any codebase.",
49+
"Generate reusable context.",
50+
"Built for developers and AI agents.",
51+
];
352
---
453

5-
<section id="hero" class="landing-section hero-section">
6-
<!--
7-
HERO SECTION
54+
<section
55+
id="hero"
56+
class="relative flex min-h-screen items-center overflow-hidden bg-[#02080f] px-6 pt-20 pb-16 text-white lg:pt-24"
57+
>
58+
<div class="hero-grid" aria-hidden="true"></div>
59+
60+
<div class="relative z-10 mx-auto grid h-full w-full max-w-7xl items-center gap-16 lg:grid-cols-[0.9fr_1.1fr]">
61+
<div class="max-w-2xl">
62+
<div class="mb-5 inline-flex rounded-full border border-cyan-400/20 bg-cyan-400/[0.04] px-4 py-2 font-mono text-xs font-bold uppercase text-[#2EE6D6]">
63+
devmap scan
64+
</div>
65+
66+
<h1 class="min-h-[130px] max-w-xl text-5xl font-bold leading-tight tracking-tight md:min-h-[170px] md:text-7xl">
67+
<span id="typing-text" class="typing-text"></span>
68+
<span class="typing-cursor">|</span>
69+
</h1>
70+
71+
<p class="mt-6 max-w-xl text-base leading-8 text-slate-400 md:text-lg">
72+
DevMap scans routes, dependencies, entry points, critical files, and feature evidence into one reusable snapshot before you or your AI agent touch the code.
73+
</p>
74+
75+
<div class="mt-8 flex flex-wrap gap-3">
76+
<button class="rounded-xl bg-[#2EE6D6] px-5 py-3 font-mono text-sm font-bold text-[#02100f] transition hover:-translate-y-0.5">
77+
$ npm install -g devmap
78+
</button>
879

9-
Headline:
10-
"Understand any codebase.
11-
In minutes, not days."
80+
<a
81+
href="https://github.com/itsflaid/devmap"
82+
class="rounded-xl border border-white/10 bg-white/[0.03] px-5 py-3 font-mono text-sm font-bold text-white transition hover:-translate-y-0.5 hover:border-cyan-400/30"
83+
>
84+
View on GitHub
85+
</a>
86+
</div>
1287

13-
Subheadline:
14-
"DevMap maps your project through static analysis - so you, your team,
15-
and your AI agents always know where to start."
88+
<div class="mt-6 flex flex-wrap gap-2">
89+
{["Open Source", "MIT License", "Local First", "Agent Ready"].map((item) => (
90+
<span class="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 font-mono text-xs text-slate-400">
91+
{item}
92+
</span>
93+
))}
94+
</div>
95+
</div>
1696

17-
Primary CTA:
18-
"Get Started Free"
97+
<div
98+
data-snapshot-panel
99+
class="relative w-full max-w-[760px] justify-self-end overflow-hidden rounded-2xl border border-cyan-400/15 bg-[#07131f]/90 shadow-2xl shadow-black/40"
100+
>
101+
<div class="relative z-10 flex items-center justify-between border-b border-white/10 bg-black/20 px-5 py-3 font-mono text-xs">
102+
<div class="flex items-center gap-2">
103+
<span class="h-2.5 w-2.5 rounded-full border border-white/20"></span>
104+
<span class="h-2.5 w-2.5 rounded-full border border-white/20"></span>
105+
<span class="h-2.5 w-2.5 rounded-full border border-white/20"></span>
106+
</div>
107+
<span class="text-white">.devmap/snapshot.json</span>
108+
<span class="text-[#2EE6D6]">analyzed ✓</span>
109+
</div>
19110

20-
Secondary CTA:
21-
"View on GitHub"
111+
<div class="relative z-10 flex gap-2 overflow-x-auto border-b border-white/10 px-5 py-3">
112+
{snapshotSections.map((section, index) => (
113+
<button
114+
type="button"
115+
data-snapshot-tab={index}
116+
class:list={[
117+
"snapshot-tab whitespace-nowrap rounded-full border px-3 py-1 font-mono text-xs transition",
118+
index === 0
119+
? "is-active border-cyan-400/30 bg-cyan-400/[0.08] text-[#2EE6D6]"
120+
: "border-white/10 bg-white/[0.02] text-slate-500 hover:text-slate-300",
121+
]}
122+
>
123+
{section.label}
124+
</button>
125+
))}
126+
</div>
22127

23-
Social proof (tampilkan setelah datanya tersedia):
24-
"Used by 500+ developers - Open Source - MIT License"
128+
<div class="relative z-10 space-y-4 p-5">
129+
<section>
130+
<div class="flex items-center justify-between gap-4">
131+
<p data-title class="font-mono text-xs font-bold uppercase tracking-wide text-[#2EE6D6]">
132+
{snapshotSections[0].title}
133+
</p>
134+
<span class="rounded-full border border-cyan-400/20 px-3 py-1 font-mono text-xs text-[#2EE6D6]">
135+
reusable context
136+
</span>
137+
</div>
25138

26-
Pesan inti:
27-
Pahami codebase apa pun dalam hitungan menit.
28-
-->
139+
<div data-stats class="mt-4 grid grid-cols-2 gap-3 md:grid-cols-3">
140+
{snapshotSections[0].stats.map((item) => (
141+
<div class="rounded-xl border border-white/10 bg-white/[0.03] px-3 py-3 font-mono text-sm text-white">
142+
{item}
143+
</div>
144+
))}
145+
</div>
146+
</section>
147+
148+
<section class="grid gap-4 md:grid-cols-[1.18fr_0.82fr]">
149+
<div class="rounded-xl border border-white/10 bg-black/20 p-4">
150+
<p class="font-mono text-xs font-bold uppercase text-slate-500">File Map</p>
151+
<pre data-tree class="mt-3 overflow-hidden font-mono text-sm leading-7 text-slate-300">{snapshotSections[0].tree.join("\n")}</pre>
152+
</div>
153+
154+
<div class="rounded-xl border border-white/10 bg-black/20 p-4">
155+
<p class="font-mono text-xs font-bold uppercase text-slate-500">Detected</p>
156+
<div data-chips class="mt-3 flex flex-wrap gap-2">
157+
{snapshotSections[0].chips.map((item) => (
158+
<span class="rounded-full border border-cyan-400/20 bg-cyan-400/[0.04] px-3 py-1 font-mono text-xs text-[#2EE6D6]">
159+
{item}
160+
</span>
161+
))}
162+
</div>
163+
</div>
164+
</section>
165+
</div>
166+
</div>
167+
</div>
29168
</section>
30169

170+
<script is:inline define:vars={{ snapshotSections, typingTexts }}>
171+
const typingEl = document.getElementById("typing-text");
172+
173+
if (typingEl) {
174+
typingEl.textContent = "";
175+
typingEl.style.visibility = "visible";
176+
177+
let textIndex = 0;
178+
let charIndex = 0;
179+
let deleting = false;
180+
181+
const typeLoop = () => {
182+
const current = typingTexts[textIndex];
183+
184+
if (!deleting) {
185+
typingEl.textContent = current.slice(0, charIndex++);
186+
187+
if (charIndex > current.length) {
188+
deleting = true;
189+
setTimeout(typeLoop, 1700);
190+
return;
191+
}
192+
} else {
193+
typingEl.textContent = current.slice(0, charIndex--);
194+
195+
if (charIndex < 0) {
196+
deleting = false;
197+
textIndex = (textIndex + 1) % typingTexts.length;
198+
}
199+
}
200+
201+
setTimeout(typeLoop, deleting ? 28 : 62);
202+
};
203+
204+
typeLoop();
205+
}
206+
207+
const panel = document.querySelector("[data-snapshot-panel]");
208+
209+
if (panel) {
210+
const tabs = Array.from(panel.querySelectorAll("[data-snapshot-tab]"));
211+
const title = panel.querySelector("[data-title]");
212+
const stats = panel.querySelector("[data-stats]");
213+
const tree = panel.querySelector("[data-tree]");
214+
const chips = panel.querySelector("[data-chips]");
215+
216+
let activeIndex = 0;
217+
let timer;
218+
219+
const render = (index) => {
220+
const section = snapshotSections[index];
221+
activeIndex = index;
222+
223+
tabs.forEach((tab, tabIndex) => {
224+
const active = tabIndex === index;
225+
226+
tab.classList.toggle("border-cyan-400/30", active);
227+
tab.classList.toggle("bg-cyan-400/[0.08]", active);
228+
tab.classList.toggle("text-[#2EE6D6]", active);
229+
230+
tab.classList.toggle("border-white/10", !active);
231+
tab.classList.toggle("bg-white/[0.02]", !active);
232+
tab.classList.toggle("text-slate-500", !active);
233+
});
234+
235+
title.textContent = section.title;
236+
237+
stats.innerHTML = section.stats
238+
.map((item) => `<div class="rounded-xl border border-white/10 bg-white/[0.03] px-3 py-3 font-mono text-sm text-white">${item}</div>`)
239+
.join("");
240+
241+
tree.textContent = section.tree.join("\n");
242+
243+
chips.innerHTML = section.chips
244+
.map((item) => `<span class="rounded-full border border-cyan-400/20 bg-cyan-400/[0.04] px-3 py-1 font-mono text-xs text-[#2EE6D6]">${item}</span>`)
245+
.join("");
246+
};
247+
248+
const start = () => {
249+
window.clearInterval(timer);
250+
timer = window.setInterval(() => {
251+
render((activeIndex + 1) % snapshotSections.length);
252+
}, 3200);
253+
};
254+
255+
panel.addEventListener("mousemove", (event) => {
256+
const rect = panel.getBoundingClientRect();
257+
const x = (event.clientX - rect.left) / rect.width;
258+
const y = (event.clientY - rect.top) / rect.height;
259+
260+
panel.style.setProperty("--mx", `${x}`);
261+
panel.style.setProperty("--my", `${y}`);
262+
});
263+
264+
tabs.forEach((tab, index) => {
265+
tab.addEventListener("click", () => {
266+
render(index);
267+
start();
268+
});
269+
});
270+
271+
start();
272+
}
273+
</script>
274+
31275
<style>
32-
/* Hero section styles. */
276+
.hero-grid {
277+
position: absolute;
278+
inset: 0;
279+
z-index: 0;
280+
background: #02080f;
281+
overflow: hidden;
282+
}
283+
284+
.hero-grid::before {
285+
position: absolute;
286+
inset: 0;
287+
content: "";
288+
background:
289+
linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
290+
linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
291+
background-size: 50px 50px;
292+
mask-image: radial-gradient(circle at center, black 45%, transparent 75%);
293+
}
294+
295+
.typing-text {
296+
visibility: hidden;
297+
}
298+
299+
.typing-cursor {
300+
color: #2ee6d6;
301+
animation: blink 1s infinite;
302+
}
303+
304+
@keyframes blink {
305+
0%,
306+
49% {
307+
opacity: 1;
308+
}
309+
310+
50%,
311+
100% {
312+
opacity: 0;
313+
}
314+
}
315+
316+
[data-snapshot-panel]::before {
317+
position: absolute;
318+
inset: 0;
319+
content: "";
320+
background: radial-gradient(
321+
380px circle at calc(var(--mx, 0.5) * 100%) calc(var(--my, 0.5) * 100%),
322+
rgba(46, 230, 214, 0.12),
323+
transparent 70%
324+
);
325+
pointer-events: none;
326+
}
327+
328+
@media (max-width: 1023px) {
329+
#hero {
330+
min-height: auto;
331+
padding-top: 6rem;
332+
padding-bottom: 6rem;
333+
}
334+
}
33335
</style>

0 commit comments

Comments
 (0)