Skip to content

Commit 85d9557

Browse files
committed
feat: add animated background and hero components; create existing and new project pages with improved UI and functionality
1 parent eaf3c39 commit 85d9557

File tree

8 files changed

+360
-123
lines changed

8 files changed

+360
-123
lines changed

app/existing/page.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Link from "next/link"
2+
3+
import { Button } from "@/components/ui/button"
4+
5+
export default function ExistingProjectPage() {
6+
return (
7+
<div className="flex min-h-screen flex-col items-center justify-center bg-background px-6 py-24 text-center text-foreground">
8+
<div className="space-y-6">
9+
<div className="space-y-2">
10+
<h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Existing projects are coming soon</h1>
11+
<p className="mx-auto max-w-2xl text-base text-muted-foreground md:text-lg">
12+
We&apos;re crafting guided flows to ingest your current instructions, audit gaps, and align new guidance with your repository. Leave your email in the wizard and we&apos;ll reach out the moment it&apos;s live.
13+
</p>
14+
</div>
15+
<Button asChild size="lg">
16+
<Link href="/new">Explore the new project wizard</Link>
17+
</Button>
18+
</div>
19+
</div>
20+
)
21+
}

app/new/page.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"use client"
2+
3+
import { useMemo, useState } from "react"
4+
5+
import { Button } from "@/components/ui/button"
6+
import { InstructionsWizard } from "@/components/instructions-wizard"
7+
import { getHomeMainClasses } from "@/lib/utils"
8+
import { getFormatLabel } from "@/lib/wizard-utils"
9+
import { ANALYTICS_EVENTS } from "@/lib/analytics-events"
10+
import { track } from "@/lib/mixpanel"
11+
import type { FileOutputConfig } from "@/types/wizard"
12+
import { Github } from "lucide-react"
13+
import Link from "next/link"
14+
15+
import Logo from "@/components/Logo"
16+
import filesData from "@/data/files.json"
17+
18+
export default function NewInstructionsPage() {
19+
const [showWizard, setShowWizard] = useState(false)
20+
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)
21+
22+
const fileOptions = useMemo(() => {
23+
return (filesData as FileOutputConfig[]).filter((file) => file.enabled !== false)
24+
}, [])
25+
26+
const handleFileCtaClick = (file: FileOutputConfig) => {
27+
setSelectedFileId(file.id)
28+
setShowWizard(true)
29+
track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, {
30+
fileId: file.id,
31+
fileLabel: file.label,
32+
})
33+
}
34+
35+
const handleWizardClose = () => {
36+
setShowWizard(false)
37+
setSelectedFileId(null)
38+
}
39+
40+
return (
41+
<div className="min-h-screen bg-background text-foreground">
42+
{/* Top utility bar */}
43+
<div className="absolute top-4 right-4 z-10">
44+
<Link href="https://github.com/spivx/devcontext" target="_blank">
45+
<Button variant="outline" size="sm">
46+
<Github className="mr-2 h-4 w-4" />
47+
GitHub
48+
</Button>
49+
</Link>
50+
</div>
51+
52+
{/* Hero Section */}
53+
<main className={getHomeMainClasses(showWizard)}>
54+
{showWizard && selectedFileId ? (
55+
<InstructionsWizard selectedFileId={selectedFileId} onClose={handleWizardClose} />
56+
) : (
57+
<>
58+
<div className="space-y-6">
59+
{/* Logo/Title */}
60+
<Logo />
61+
62+
{/* Headline */}
63+
<h1 className="max-w-4xl text-3xl font-bold">
64+
Assemble Tailored AI Coding Playbooks With a Guided Wizard
65+
</h1>
66+
67+
{/* Subheadline */}
68+
<p className="max-w-xl text-lg leading-relaxed text-muted-foreground">
69+
Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps.
70+
</p>
71+
72+
<p className="max-w-xl text-sm text-muted-foreground/80">
73+
Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page.
74+
</p>
75+
76+
{/* File type CTAs */}
77+
<div className="pt-6">
78+
<div className="grid gap-4 md:grid-cols-2">
79+
{fileOptions.map((file) => {
80+
const formatLabel = getFormatLabel(file.format)
81+
return (
82+
<button
83+
key={file.id}
84+
type="button"
85+
className="group relative flex w-full flex-col items-start gap-3 rounded-3xl border border-border/60 bg-card/80 p-6 text-left shadow-sm ring-offset-background transition-all hover:-translate-y-1 hover:border-primary/60 hover:shadow-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/80"
86+
onClick={() => handleFileCtaClick(file)}
87+
aria-label={`Create ${file.label}`}
88+
>
89+
<div className="flex items-center gap-2 rounded-full bg-secondary/60 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-secondary-foreground/80">
90+
<span className="inline-flex h-2 w-2 rounded-full bg-primary/70" aria-hidden />
91+
<span>Start with</span>
92+
</div>
93+
<div>
94+
<p className="text-xl font-semibold text-foreground transition-colors group-hover:text-primary">
95+
{file.label}
96+
</p>
97+
{file.filename ? (
98+
<p className="mt-1 text-sm text-muted-foreground">
99+
{file.filename}
100+
</p>
101+
) : null}
102+
</div>
103+
{formatLabel ? (
104+
<span className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
105+
{formatLabel} format
106+
</span>
107+
) : null}
108+
<span className="pointer-events-none absolute right-6 top-6 text-primary/60 transition-transform group-hover:translate-x-1">
109+
110+
</span>
111+
</button>
112+
)
113+
})}
114+
</div>
115+
</div>
116+
</div>
117+
</>
118+
)}
119+
</main>
120+
121+
</div>
122+
)
123+
}

app/page.tsx

Lines changed: 31 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,40 @@
1-
"use client"
2-
3-
import { useMemo, useState } from "react"
1+
import Link from "next/link"
42

3+
import { AnimatedBackground } from "@/components/AnimatedBackground"
4+
import { Hero } from "@/components/Hero"
55
import { Button } from "@/components/ui/button"
6-
import { InstructionsWizard } from "@/components/instructions-wizard"
7-
import { getHomeMainClasses } from "@/lib/utils"
8-
import { getFormatLabel } from "@/lib/wizard-utils"
9-
import { ANALYTICS_EVENTS } from "@/lib/analytics-events"
10-
import { track } from "@/lib/mixpanel"
11-
import type { FileOutputConfig } from "@/types/wizard"
126
import { Github } from "lucide-react"
13-
import Link from "next/link"
14-
15-
import Logo from "./../components/Logo"
16-
import filesData from "@/data/files.json"
17-
18-
export default function Home() {
19-
const [showWizard, setShowWizard] = useState(false)
20-
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)
21-
22-
const fileOptions = useMemo(() => {
23-
return (filesData as FileOutputConfig[]).filter((file) => file.enabled !== false)
24-
}, [])
25-
26-
const handleFileCtaClick = (file: FileOutputConfig) => {
27-
setSelectedFileId(file.id)
28-
setShowWizard(true)
29-
track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, {
30-
fileId: file.id,
31-
fileLabel: file.label,
32-
})
33-
}
34-
35-
const handleWizardClose = () => {
36-
setShowWizard(false)
37-
setSelectedFileId(null)
38-
}
397

8+
export default function LandingPage() {
409
return (
41-
<div className="min-h-screen bg-background text-foreground">
42-
{/* Top utility bar */}
43-
<div className="absolute top-4 right-4 z-10">
44-
<Link href="https://github.com/spivx/devcontext" target="_blank">
45-
<Button variant="outline" size="sm">
46-
<Github className="mr-2 h-4 w-4" />
47-
GitHub
48-
</Button>
49-
</Link>
10+
<div className="relative min-h-screen overflow-hidden bg-background text-foreground">
11+
<AnimatedBackground />
12+
<div className="relative z-10 flex min-h-screen flex-col">
13+
<header className="flex items-center justify-between px-6 py-6 lg:px-12 lg:py-8">
14+
<Link href="/" className="text-lg font-semibold tracking-tight text-foreground md:text-xl">
15+
DevContext
16+
</Link>
17+
<div className="flex items-center gap-3">
18+
<Button asChild variant="ghost" size="sm" className="hidden sm:inline-flex">
19+
<Link href="/new">Launch wizard</Link>
20+
</Button>
21+
<Button asChild variant="outline" size="sm">
22+
<Link href="https://github.com/spivx/devcontext" rel="noreferrer" target="_blank">
23+
<Github className="size-4" />
24+
GitHub
25+
</Link>
26+
</Button>
27+
</div>
28+
</header>
29+
30+
<main className="flex flex-1 flex-col">
31+
<Hero />
32+
</main>
33+
34+
<footer className="px-6 pb-10 text-center text-xs text-muted-foreground lg:px-12">
35+
Open-source and community built — keep shipping with more context than commits.
36+
</footer>
5037
</div>
51-
52-
{/* Hero Section */}
53-
<main className={getHomeMainClasses(showWizard)}>
54-
{showWizard && selectedFileId ? (
55-
<InstructionsWizard selectedFileId={selectedFileId} onClose={handleWizardClose} />
56-
) : (
57-
<>
58-
<div className="space-y-6">
59-
{/* Logo/Title */}
60-
<Logo />
61-
62-
{/* Headline */}
63-
<h1 className="max-w-4xl text-3xl font-bold">
64-
Assemble Tailored AI Coding Playbooks With a Guided Wizard
65-
</h1>
66-
67-
{/* Subheadline */}
68-
<p className="max-w-xl text-lg leading-relaxed text-muted-foreground">
69-
Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps.
70-
</p>
71-
72-
<p className="max-w-xl text-sm text-muted-foreground/80">
73-
Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page.
74-
</p>
75-
76-
{/* File type CTAs */}
77-
<div className="pt-6">
78-
<div className="grid gap-4 md:grid-cols-2">
79-
{fileOptions.map((file) => {
80-
const formatLabel = getFormatLabel(file.format)
81-
return (
82-
<button
83-
key={file.id}
84-
type="button"
85-
className="group relative flex w-full flex-col items-start gap-3 rounded-3xl border border-border/60 bg-card/80 p-6 text-left shadow-sm ring-offset-background transition-all hover:-translate-y-1 hover:border-primary/60 hover:shadow-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/80"
86-
onClick={() => handleFileCtaClick(file)}
87-
aria-label={`Create ${file.label}`}
88-
>
89-
<div className="flex items-center gap-2 rounded-full bg-secondary/60 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-secondary-foreground/80">
90-
<span className="inline-flex h-2 w-2 rounded-full bg-primary/70" aria-hidden />
91-
<span>Start with</span>
92-
</div>
93-
<div>
94-
<p className="text-xl font-semibold text-foreground transition-colors group-hover:text-primary">
95-
{file.label}
96-
</p>
97-
{file.filename ? (
98-
<p className="mt-1 text-sm text-muted-foreground">
99-
{file.filename}
100-
</p>
101-
) : null}
102-
</div>
103-
{formatLabel ? (
104-
<span className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
105-
{formatLabel} format
106-
</span>
107-
) : null}
108-
<span className="pointer-events-none absolute right-6 top-6 text-primary/60 transition-transform group-hover:translate-x-1">
109-
110-
</span>
111-
</button>
112-
)
113-
})}
114-
</div>
115-
</div>
116-
</div>
117-
</>
118-
)}
119-
</main>
120-
12138
</div>
12239
)
12340
}

app/sitemap.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import type { MetadataRoute } from "next";
33
const baseUrl = "https://devcontext.com";
44

55
export default function sitemap(): MetadataRoute.Sitemap {
6-
const lastModified = new Date();
6+
const lastModified = new Date()
77

8-
return [
9-
{
10-
url: baseUrl,
11-
lastModified,
12-
changeFrequency: "weekly",
13-
priority: 1,
14-
},
15-
];
8+
const entries: Array<{ path: string; priority: number; changeFrequency: MetadataRoute.Sitemap[number]["changeFrequency"] }> = [
9+
{ path: "", priority: 1, changeFrequency: "weekly" },
10+
{ path: "/new", priority: 0.9, changeFrequency: "weekly" },
11+
{ path: "/existing", priority: 0.6, changeFrequency: "monthly" },
12+
]
13+
14+
return entries.map(({ path, priority, changeFrequency }) => ({
15+
url: `${baseUrl}${path}`,
16+
priority,
17+
changeFrequency,
18+
lastModified,
19+
}))
1620
}

components/AnimatedBackground.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client"
2+
3+
import { motion } from "framer-motion"
4+
5+
const particles = [
6+
{ left: "8%", top: "18%", size: "h-1.5 w-1.5", delay: 0 },
7+
{ left: "20%", top: "70%", size: "h-2 w-2", delay: 0.6 },
8+
{ left: "32%", top: "40%", size: "h-1 w-1", delay: 0.3 },
9+
{ left: "48%", top: "14%", size: "h-2 w-2", delay: 0.9 },
10+
{ left: "60%", top: "78%", size: "h-1.5 w-1.5", delay: 0.2 },
11+
{ left: "76%", top: "32%", size: "h-1 w-1", delay: 0.75 },
12+
{ left: "88%", top: "58%", size: "h-2 w-2", delay: 0.4 },
13+
{ left: "15%", top: "88%", size: "h-1.5 w-1.5", delay: 1.1 },
14+
{ left: "68%", top: "12%", size: "h-1.5 w-1.5", delay: 1.3 },
15+
{ left: "84%", top: "24%", size: "h-1 w-1", delay: 1.6 },
16+
]
17+
18+
export function AnimatedBackground() {
19+
return (
20+
<div className="pointer-events-none absolute inset-0 overflow-hidden">
21+
<motion.div
22+
className="absolute inset-0 bg-gradient-to-br from-primary/20 via-transparent to-accent/20"
23+
animate={{ opacity: [0.5, 0.85, 0.65], rotate: [0, 2, -1, 0] }}
24+
transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }}
25+
/>
26+
27+
<motion.div
28+
className="absolute top-[-20%] left-[10%] h-[46rem] w-[46rem] rounded-full bg-primary/30 blur-3xl"
29+
initial={{ x: "0%", y: "0%", scale: 1 }}
30+
animate={{ scale: [1, 1.1, 1], x: ["0%", "-6%", "0%"], y: ["0%", "-4%", "0%"] }}
31+
transition={{ duration: 16, repeat: Infinity, ease: "easeInOut" }}
32+
/>
33+
34+
<motion.div
35+
className="absolute bottom-[-25%] left-[15%] h-[32rem] w-[32rem] rounded-full bg-secondary/25 blur-3xl"
36+
animate={{ scale: [1.05, 0.95, 1.05], rotate: [5, -3, 5] }}
37+
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}
38+
/>
39+
40+
<motion.div
41+
className="absolute right-[-10%] top-1/4 h-[40rem] w-[40rem] rounded-full bg-accent/20 blur-3xl"
42+
animate={{ scale: [0.9, 1.05, 0.95], rotate: [-4, 4, -4] }}
43+
transition={{ duration: 24, repeat: Infinity, ease: "easeInOut" }}
44+
/>
45+
46+
{particles.map((particle, index) => (
47+
<motion.span
48+
key={`${particle.left}-${particle.top}-${index}`}
49+
className={`absolute rounded-full bg-primary/60 blur-[2px] ${particle.size} dark:bg-primary/40`}
50+
style={{ left: particle.left, top: particle.top }}
51+
animate={{ opacity: [0.2, 0.7, 0.2], y: ["0%", "-15%", "0%"] }}
52+
transition={{ duration: 9 + index * 0.6, repeat: Infinity, delay: particle.delay, ease: "easeInOut" }}
53+
/>
54+
))}
55+
56+
<div className="absolute inset-0 bg-gradient-to-b from-background/10 via-background/60 to-background" />
57+
</div>
58+
)
59+
}

0 commit comments

Comments
 (0)