diff --git a/app/existing/page.tsx b/app/existing/page.tsx new file mode 100644 index 0000000..9f33e09 --- /dev/null +++ b/app/existing/page.tsx @@ -0,0 +1,21 @@ +import Link from "next/link" + +import { Button } from "@/components/ui/button" + +export default function ExistingProjectPage() { + return ( +
+
+
+

Existing projects are coming soon

+

+ We'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'll reach out the moment it's live. +

+
+ +
+
+ ) +} diff --git a/app/new/page.tsx b/app/new/page.tsx new file mode 100644 index 0000000..a6a814f --- /dev/null +++ b/app/new/page.tsx @@ -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(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 ( +
+ {/* Top utility bar */} +
+ + + +
+ + {/* Hero Section */} +
+ {showWizard && selectedFileId ? ( + + ) : ( + <> +
+ {/* Logo/Title */} + + + {/* Headline */} +

+ Assemble Tailored AI Coding Playbooks With a Guided Wizard +

+ + {/* Subheadline */} +

+ Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps. +

+ +

+ Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page. +

+ + {/* File type CTAs */} +
+
+ {fileOptions.map((file) => { + const formatLabel = getFormatLabel(file.format) + return ( + + ) + })} +
+
+
+ + )} +
+ +
+ ) +} diff --git a/app/page.tsx b/app/page.tsx index 82d35c7..7c99617 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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(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 ( -
- {/* Top utility bar */} -
- - - +
+ +
+
+ + DevContext + +
+ + +
+
+ +
+ +
+ +
+ Open-source and community built — keep shipping with more context than commits. +
- - {/* Hero Section */} -
- {showWizard && selectedFileId ? ( - - ) : ( - <> -
- {/* Logo/Title */} - - - {/* Headline */} -

- Assemble Tailored AI Coding Playbooks With a Guided Wizard -

- - {/* Subheadline */} -

- Move from curated best practices to sharable files like Copilot instructions, Cursor rules, and agents.md playbooks in just a few guided steps. -

- -

- Use the wizard to generate Copilot instruction files, agents files, comprehensive instruction sets, and Cursor rules without starting from a blank page. -

- - {/* File type CTAs */} -
-
- {fileOptions.map((file) => { - const formatLabel = getFormatLabel(file.format) - return ( - - ) - })} -
-
-
- - )} -
-
) } diff --git a/app/sitemap.ts b/app/sitemap.ts index 60ee8fa..9023ea7 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -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, + })) } diff --git a/components/AnimatedBackground.tsx b/components/AnimatedBackground.tsx new file mode 100644 index 0000000..d2f12dd --- /dev/null +++ b/components/AnimatedBackground.tsx @@ -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 ( +
+ + + + + + + + + {particles.map((particle, index) => ( + + ))} + +
+
+ ) +} diff --git a/components/Hero.tsx b/components/Hero.tsx new file mode 100644 index 0000000..d37a7ee --- /dev/null +++ b/components/Hero.tsx @@ -0,0 +1,69 @@ +"use client" + +import { motion } from "framer-motion" +import { ArrowRight } from "lucide-react" +import Link from "next/link" + +import { Button } from "@/components/ui/button" + +const containerVariants = { + hidden: { opacity: 0 }, + show: { + opacity: 1, + transition: { staggerChildren: 0.15, delayChildren: 0.2 }, + }, +} + +const itemVariants = { + hidden: { opacity: 0, y: 28 }, + show: { opacity: 1, y: 0, transition: { ease: "easeOut", duration: 0.7 } }, +} + +export function Hero() { + return ( + + + + + Crafted for engineering teams shipping with AI + + + DevContext — AI Coding Guidelines & Context Generator + + + Generate AI config files like Copilot instructions, Cursor rules, and prompts — consistent, fast, and IDE-ready. Bring product context, architecture, and workflow nuance into every coding session. + + + + + + + + ) +} diff --git a/package-lock.json b/package-lock.json index a3a52fd..5871d48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^11.18.2", "lucide-react": "^0.544.0", "mixpanel-browser": "^2.70.0", "next": "15.5.3", @@ -5404,6 +5405,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6901,6 +6929,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 9d11d79..244c49d 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^11.18.2", "lucide-react": "^0.544.0", "mixpanel-browser": "^2.70.0", "next": "15.5.3",