Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/existing/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Link from "next/link"

import { Button } from "@/components/ui/button"

export default function ExistingProjectPage() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background px-6 py-24 text-center text-foreground">
<div className="space-y-6">
<div className="space-y-2">
<h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Existing projects are coming soon</h1>
<p className="mx-auto max-w-2xl text-base text-muted-foreground md:text-lg">
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.
</p>
</div>
<Button asChild size="lg">
<Link href="/new">Explore the new project wizard</Link>
</Button>
</div>
</div>
)
}
123 changes: 123 additions & 0 deletions app/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client"

import { useMemo, useState } from "react"

import { Button } from "@/components/ui/button"
import { InstructionsWizard } from "@/components/instructions-wizard"
import { getHomeMainClasses } from "@/lib/utils"
import { getFormatLabel } from "@/lib/wizard-utils"
import { ANALYTICS_EVENTS } from "@/lib/analytics-events"
import { track } from "@/lib/mixpanel"
import type { FileOutputConfig } from "@/types/wizard"
import { Github } from "lucide-react"
import Link from "next/link"

import Logo from "@/components/Logo"
import filesData from "@/data/files.json"

export default function NewInstructionsPage() {
const [showWizard, setShowWizard] = useState(false)
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)

const fileOptions = useMemo(() => {
return (filesData as FileOutputConfig[]).filter((file) => file.enabled !== false)
}, [])

const handleFileCtaClick = (file: FileOutputConfig) => {
setSelectedFileId(file.id)
setShowWizard(true)
track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, {
fileId: file.id,
fileLabel: file.label,
})
}

const handleWizardClose = () => {
setShowWizard(false)
setSelectedFileId(null)
}

return (
<div className="min-h-screen bg-background text-foreground">
{/* Top utility bar */}
<div className="absolute top-4 right-4 z-10">
<Link href="https://github.com/spivx/devcontext" target="_blank">
<Button variant="outline" size="sm">
<Github className="mr-2 h-4 w-4" />
GitHub
</Button>
</Link>
</div>

{/* Hero Section */}
<main className={getHomeMainClasses(showWizard)}>
{showWizard && selectedFileId ? (
<InstructionsWizard selectedFileId={selectedFileId} onClose={handleWizardClose} />
) : (
<>
<div className="space-y-6">
{/* Logo/Title */}
<Logo />

{/* Headline */}
<h1 className="max-w-4xl text-3xl font-bold">
Assemble Tailored AI Coding Playbooks With a Guided Wizard
</h1>

{/* Subheadline */}
<p className="max-w-xl text-lg leading-relaxed text-muted-foreground">
Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps.
</p>

<p className="max-w-xl text-sm text-muted-foreground/80">
Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page.
</p>

{/* File type CTAs */}
<div className="pt-6">
<div className="grid gap-4 md:grid-cols-2">
{fileOptions.map((file) => {
const formatLabel = getFormatLabel(file.format)
return (
<button
key={file.id}
type="button"
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"
onClick={() => handleFileCtaClick(file)}
aria-label={`Create ${file.label}`}
>
<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">
<span className="inline-flex h-2 w-2 rounded-full bg-primary/70" aria-hidden />
<span>Start with</span>
</div>
<div>
<p className="text-xl font-semibold text-foreground transition-colors group-hover:text-primary">
{file.label}
</p>
{file.filename ? (
<p className="mt-1 text-sm text-muted-foreground">
{file.filename}
</p>
) : null}
</div>
{formatLabel ? (
<span className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
{formatLabel} format
</span>
) : null}
<span className="pointer-events-none absolute right-6 top-6 text-primary/60 transition-transform group-hover:translate-x-1">
</span>
</button>
)
})}
</div>
</div>
</div>
</>
)}
</main>

</div>
)
}
145 changes: 31 additions & 114 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,40 @@
"use client"

import { useMemo, useState } from "react"
import Link from "next/link"

import { AnimatedBackground } from "@/components/AnimatedBackground"
import { Hero } from "@/components/Hero"
import { Button } from "@/components/ui/button"
import { InstructionsWizard } from "@/components/instructions-wizard"
import { getHomeMainClasses } from "@/lib/utils"
import { getFormatLabel } from "@/lib/wizard-utils"
import { ANALYTICS_EVENTS } from "@/lib/analytics-events"
import { track } from "@/lib/mixpanel"
import type { FileOutputConfig } from "@/types/wizard"
import { Github } from "lucide-react"
import Link from "next/link"

import Logo from "./../components/Logo"
import filesData from "@/data/files.json"

export default function Home() {
const [showWizard, setShowWizard] = useState(false)
const [selectedFileId, setSelectedFileId] = useState<string | null>(null)

const fileOptions = useMemo(() => {
return (filesData as FileOutputConfig[]).filter((file) => file.enabled !== false)
}, [])

const handleFileCtaClick = (file: FileOutputConfig) => {
setSelectedFileId(file.id)
setShowWizard(true)
track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, {
fileId: file.id,
fileLabel: file.label,
})
}

const handleWizardClose = () => {
setShowWizard(false)
setSelectedFileId(null)
}

export default function LandingPage() {
return (
<div className="min-h-screen bg-background text-foreground">
{/* Top utility bar */}
<div className="absolute top-4 right-4 z-10">
<Link href="https://github.com/spivx/devcontext" target="_blank">
<Button variant="outline" size="sm">
<Github className="mr-2 h-4 w-4" />
GitHub
</Button>
</Link>
<div className="relative min-h-screen overflow-hidden bg-background text-foreground">
<AnimatedBackground />
<div className="relative z-10 flex min-h-screen flex-col">
<header className="flex items-center justify-between px-6 py-6 lg:px-12 lg:py-8">
<Link href="/" className="text-lg font-semibold tracking-tight text-foreground md:text-xl">
DevContext
</Link>
<div className="flex items-center gap-3">
<Button asChild variant="ghost" size="sm" className="hidden sm:inline-flex">
<Link href="/new">Launch wizard</Link>
</Button>
<Button asChild variant="outline" size="sm">
<Link href="https://github.com/spivx/devcontext" rel="noreferrer" target="_blank">
<Github className="size-4" />
GitHub
</Link>
</Button>
</div>
</header>

<main className="flex flex-1 flex-col">
<Hero />
</main>

<footer className="px-6 pb-10 text-center text-xs text-muted-foreground lg:px-12">
Open-source and community built — keep shipping with more context than commits.
</footer>
</div>

{/* Hero Section */}
<main className={getHomeMainClasses(showWizard)}>
{showWizard && selectedFileId ? (
<InstructionsWizard selectedFileId={selectedFileId} onClose={handleWizardClose} />
) : (
<>
<div className="space-y-6">
{/* Logo/Title */}
<Logo />

{/* Headline */}
<h1 className="max-w-4xl text-3xl font-bold">
Assemble Tailored AI Coding Playbooks With a Guided Wizard
</h1>

{/* Subheadline */}
<p className="max-w-xl text-lg leading-relaxed text-muted-foreground">
Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps.
</p>

<p className="max-w-xl text-sm text-muted-foreground/80">
Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page.
</p>

{/* File type CTAs */}
<div className="pt-6">
<div className="grid gap-4 md:grid-cols-2">
{fileOptions.map((file) => {
const formatLabel = getFormatLabel(file.format)
return (
<button
key={file.id}
type="button"
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"
onClick={() => handleFileCtaClick(file)}
aria-label={`Create ${file.label}`}
>
<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">
<span className="inline-flex h-2 w-2 rounded-full bg-primary/70" aria-hidden />
<span>Start with</span>
</div>
<div>
<p className="text-xl font-semibold text-foreground transition-colors group-hover:text-primary">
{file.label}
</p>
{file.filename ? (
<p className="mt-1 text-sm text-muted-foreground">
{file.filename}
</p>
) : null}
</div>
{formatLabel ? (
<span className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
{formatLabel} format
</span>
) : null}
<span className="pointer-events-none absolute right-6 top-6 text-primary/60 transition-transform group-hover:translate-x-1">
</span>
</button>
)
})}
</div>
</div>
</div>
</>
)}
</main>

</div>
)
}
22 changes: 13 additions & 9 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import type { MetadataRoute } from "next";
const baseUrl = "https://devcontext.com";

export default function sitemap(): MetadataRoute.Sitemap {
const lastModified = new Date();
const lastModified = new Date()

return [
{
url: baseUrl,
lastModified,
changeFrequency: "weekly",
priority: 1,
},
];
const entries: Array<{ path: string; priority: number; changeFrequency: MetadataRoute.Sitemap[number]["changeFrequency"] }> = [
{ path: "", priority: 1, changeFrequency: "weekly" },
{ path: "/new", priority: 0.9, changeFrequency: "weekly" },
{ path: "/existing", priority: 0.6, changeFrequency: "monthly" },
]

return entries.map(({ path, priority, changeFrequency }) => ({
url: `${baseUrl}${path}`,
priority,
changeFrequency,
lastModified,
}))
}
59 changes: 59 additions & 0 deletions components/AnimatedBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client"

import { motion } from "framer-motion"

const particles = [
{ left: "8%", top: "18%", size: "h-1.5 w-1.5", delay: 0 },
{ left: "20%", top: "70%", size: "h-2 w-2", delay: 0.6 },
{ left: "32%", top: "40%", size: "h-1 w-1", delay: 0.3 },
{ left: "48%", top: "14%", size: "h-2 w-2", delay: 0.9 },
{ left: "60%", top: "78%", size: "h-1.5 w-1.5", delay: 0.2 },
{ left: "76%", top: "32%", size: "h-1 w-1", delay: 0.75 },
{ left: "88%", top: "58%", size: "h-2 w-2", delay: 0.4 },
{ left: "15%", top: "88%", size: "h-1.5 w-1.5", delay: 1.1 },
{ left: "68%", top: "12%", size: "h-1.5 w-1.5", delay: 1.3 },
{ left: "84%", top: "24%", size: "h-1 w-1", delay: 1.6 },
]

export function AnimatedBackground() {
return (
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<motion.div
className="absolute inset-0 bg-gradient-to-br from-primary/20 via-transparent to-accent/20"
animate={{ opacity: [0.5, 0.85, 0.65], rotate: [0, 2, -1, 0] }}
transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }}
/>

<motion.div
className="absolute top-[-20%] left-[10%] h-[46rem] w-[46rem] rounded-full bg-primary/30 blur-3xl"
initial={{ x: "0%", y: "0%", scale: 1 }}
animate={{ scale: [1, 1.1, 1], x: ["0%", "-6%", "0%"], y: ["0%", "-4%", "0%"] }}
transition={{ duration: 16, repeat: Infinity, ease: "easeInOut" }}
/>

<motion.div
className="absolute bottom-[-25%] left-[15%] h-[32rem] w-[32rem] rounded-full bg-secondary/25 blur-3xl"
animate={{ scale: [1.05, 0.95, 1.05], rotate: [5, -3, 5] }}
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}
/>

<motion.div
className="absolute right-[-10%] top-1/4 h-[40rem] w-[40rem] rounded-full bg-accent/20 blur-3xl"
animate={{ scale: [0.9, 1.05, 0.95], rotate: [-4, 4, -4] }}
transition={{ duration: 24, repeat: Infinity, ease: "easeInOut" }}
/>

{particles.map((particle, index) => (
<motion.span
key={`${particle.left}-${particle.top}-${index}`}
className={`absolute rounded-full bg-primary/60 blur-[2px] ${particle.size} dark:bg-primary/40`}
style={{ left: particle.left, top: particle.top }}
animate={{ opacity: [0.2, 0.7, 0.2], y: ["0%", "-15%", "0%"] }}
transition={{ duration: 9 + index * 0.6, repeat: Infinity, delay: particle.delay, ease: "easeInOut" }}
/>
))}

<div className="absolute inset-0 bg-gradient-to-b from-background/10 via-background/60 to-background" />
</div>
)
}
Loading
Loading