diff --git a/package-lock.json b/package-lock.json
index da5df19..516da5d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "dev-run",
"version": "0.1.0",
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
@@ -699,6 +700,39 @@
"node": ">= 10"
}
},
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -993,7 +1027,7 @@
"version": "19.2.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1069,7 +1103,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/detect-libc": {
diff --git a/package.json b/package.json
index eaf4bf3..40e4903 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"start": "next start"
},
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 295f8fd..3b029d4 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,158 @@
-import Image from "next/image";
+import { Timer, Zap, Trophy, Users, ArrowRight, Play } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import Link from "next/link";
+
+const Landing = () => {
+ const features = [
+ {
+ icon: Timer,
+ title: "Race the Clock",
+ description: "Built-in timer with splits tracking. Every millisecond counts in your pursuit of the perfect run.",
+ },
+ {
+ icon: Trophy,
+ title: "Compete Globally",
+ description: "Weekly and all-time leaderboards. Claim your spot among the fastest runners worldwide.",
+ },
+ {
+ icon: Zap,
+ title: "Quick Runs",
+ description: "No signup walls. Jump into a run instantly and start grinding for that PB.",
+ },
+ {
+ icon: Users,
+ title: "Community First",
+ description: "Share your runs, compare splits, and celebrate clean runs with fellow speedrunners.",
+ },
+ ];
+
+ const stats = [
+ { value: "12K+", label: "Runners" },
+ { value: "847K", label: "Total Runs" },
+ { value: "156", label: "Problems" },
+ { value: "2:34", label: "World Record" },
+ ];
-export default function Home() {
return (
-
-
-
+ {/* Hero Section */}
+
+ {/* Background gradient */}
+
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
+
+
+
+ {/* Badge */}
+
+
+ The speedrunning platform for developers
+
+
+ {/* Headline */}
+
+ Code Fast.{" "}
+ Run Faster.
+
+
+ {/* Subheadline */}
+
+ DevRun is competitive programming reimagined. Race the clock through algorithmic challenges,
+ track your splits, and compete for the fastest runs on the global leaderboard.
+
+
+ {/* CTA Buttons */}
+
+
+
+
+ Start Your First Run
+
+
+
+
+
+
+ View Leaderboards
+
+
+
+
+
+ {/* Stats */}
+
+ {stats.map((stat, i) => (
+
+
{stat.value}
+
{stat.label}
+
+ ))}
+
-
-
-
- Deploy Now
-
-
- Documentation
-
+
+
+ {/* Features Section */}
+
+
+
+
+ Built for the grind
+
+
+ Everything you need to optimize your runs and climb the ranks.
+
+
+
+
+ {features.map((feature, i) => (
+
+
+
+
+
{feature.title}
+
{feature.description}
+
+ ))}
+
-
+
+
+ {/* CTA Section */}
+
+
+
+
+
+
+ Ready to chase your PB?
+
+
+ Join thousands of runners pushing the limits of competitive programming.
+
+
+
+ Browse Problems
+
+
+
+
+
+
);
-}
+};
+
+export default Landing;
diff --git a/src/components/app/Footer.tsx b/src/components/app/Footer.tsx
new file mode 100644
index 0000000..4362732
--- /dev/null
+++ b/src/components/app/Footer.tsx
@@ -0,0 +1,84 @@
+import { Timer, Github, Twitter, MessageCircle } from "lucide-react";
+
+const Footer = () => {
+ return (
+
+
+
+ {/* Brand */}
+
+
+
+
+ DevRun
+
+
+
+ The competitive programming platform for speedrunners. Race the clock, not the grind.
+
+
+
+ {/* Quick Links */}
+
+
Quick Links
+
+ {["Problems", "Leaderboards", "Categories", "Rules"].map((link) => (
+
+
+ {link}
+
+
+ ))}
+
+
+
+ {/* Community */}
+
+
Community
+
+ {["Discord", "Reddit", "Twitter", "GitHub"].map((link) => (
+
+
+ {link}
+
+
+ ))}
+
+
+
+ {/* Social */}
+
+
+
+
+
+ © 2026 DevRun. Made with ⚡ by runners, for runners.
+
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/src/components/app/LeaderboardRow.tsx b/src/components/app/LeaderboardRow.tsx
new file mode 100644
index 0000000..59189ed
--- /dev/null
+++ b/src/components/app/LeaderboardRow.tsx
@@ -0,0 +1,73 @@
+import { Trophy } from "lucide-react";
+
+interface LeaderboardRowProps {
+ rank: number;
+ username: string;
+ pb: string;
+ runsCount: number;
+ category: string;
+ index?: number;
+}
+
+const LeaderboardRow = ({ rank, username, pb, runsCount, category, index = 0 }: LeaderboardRowProps) => {
+ const getRankStyle = (rank: number) => {
+ if (rank === 1) return "rank-gold";
+ if (rank === 2) return "rank-silver";
+ if (rank === 3) return "rank-bronze";
+ return "bg-secondary text-muted-foreground";
+ };
+
+ const getRankGlow = (rank: number) => {
+ if (rank === 1) return "glow-gold";
+ if (rank === 2) return "glow-silver";
+ if (rank === 3) return "glow-bronze";
+ return "";
+ };
+
+ const isTopThree = rank <= 3;
+
+ return (
+
+ {/* Rank */}
+
+
+ {rank <= 3 ? : rank}
+
+
+
+ {/* Username */}
+
+
+
+ {username.charAt(0).toUpperCase()}
+
+
+ {username}
+
+
+
+
+ {/* PB */}
+
+ {pb}
+
+
+ {/* Runs */}
+
+ {runsCount}
+
+
+ {/* Category */}
+
+
+ {category}
+
+
+
+ );
+};
+
+export default LeaderboardRow;
diff --git a/src/components/app/NavLink.tsx b/src/components/app/NavLink.tsx
new file mode 100644
index 0000000..39daaa8
--- /dev/null
+++ b/src/components/app/NavLink.tsx
@@ -0,0 +1,29 @@
+import Link, { LinkProps } from "next/link";
+import { forwardRef } from "react";
+import { cn } from "@/lib/utils";
+import { usePathname } from "next/navigation";
+
+interface NavLinkCompatProps extends Omit
{
+ className?: string;
+ activeClassName?: string;
+ pendingClassName?: string;
+}
+
+const NavLink = forwardRef(
+ ({ className, activeClassName, pendingClassName, href, ...props }, ref) => {
+ const pathname = usePathname();
+ const isActive = pathname === href;
+ const isPending = false;
+
+ return (
+
+ );
+ }
+);
+
+NavLink.displayName = "NavLink";
diff --git a/src/components/app/Navbar.tsx b/src/components/app/Navbar.tsx
new file mode 100644
index 0000000..2fe0fe0
--- /dev/null
+++ b/src/components/app/Navbar.tsx
@@ -0,0 +1,71 @@
+import Link from "next/link";
+import { Timer, Trophy, User, Zap } from "lucide-react";
+import { usePathname } from "next/navigation";
+
+const Navbar = () => {
+ const pathname = usePathname();
+
+ const navLinks = [
+ { to: "/problems", label: "Problems", icon: Zap },
+ { to: "/run", label: "Run", icon: Timer },
+ { to: "/leaderboards", label: "Leaderboards", icon: Trophy },
+ { to: "/profile", label: "Profile", icon: User },
+ ];
+
+ const isActive = (path: string) => pathname === path;
+
+ return (
+
+
+
+ {/* Logo */}
+
+
+
+ DevRun
+
+
+
+ {/* Navigation Links */}
+
+ {navLinks.map(({ to, label, icon: Icon }) => (
+
+
+ {label}
+
+ ))}
+
+
+ {/* CTA Button */}
+
+
+ Start Run
+
+
+ {/* Mobile menu button */}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Navbar;
diff --git a/src/components/app/ProblemCard.tsx b/src/components/app/ProblemCard.tsx
new file mode 100644
index 0000000..60481d2
--- /dev/null
+++ b/src/components/app/ProblemCard.tsx
@@ -0,0 +1,81 @@
+import { Clock, Zap, Users } from "lucide-react";
+
+interface ProblemCardProps {
+ name: string;
+ difficulty: "easy" | "medium" | "hard";
+ tags: string[];
+ bestTime?: string;
+ attempts: number;
+ index?: number;
+}
+
+const ProblemCard = ({ name, difficulty, tags, bestTime, attempts, index = 0 }: ProblemCardProps) => {
+ const difficultyStyles = {
+ easy: "diff-easy",
+ medium: "diff-medium",
+ hard: "diff-hard",
+ };
+
+ const difficultyLabels = {
+ easy: "Easy",
+ medium: "Medium",
+ hard: "Hard",
+ };
+
+ return (
+
+ {/* Glow effect on hover */}
+
+
+
+ {/* Header */}
+
+
+ {name}
+
+
+ {difficultyLabels[difficulty]}
+
+
+
+ {/* Tags */}
+
+ {tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ {/* Stats */}
+
+ {bestTime && (
+
+
+ {bestTime}
+
+ )}
+
+
+ {attempts} runs
+
+
+
+ {/* Quick run button - appears on hover */}
+
+
+
+
+
+
+
+ );
+};
+
+export default ProblemCard;
diff --git a/src/components/app/SplitRow.tsx b/src/components/app/SplitRow.tsx
new file mode 100644
index 0000000..9762457
--- /dev/null
+++ b/src/components/app/SplitRow.tsx
@@ -0,0 +1,66 @@
+import { Check, Minus } from "lucide-react";
+
+interface SplitRowProps {
+ problemName: string;
+ splitTime: string;
+ delta?: string;
+ isCompleted: boolean;
+ isCurrent?: boolean;
+ pbTime?: string;
+}
+
+const SplitRow = ({ problemName, splitTime, delta, isCompleted, isCurrent, pbTime }: SplitRowProps) => {
+ const getDeltaColor = (delta?: string) => {
+ if (!delta) return "text-muted-foreground";
+ if (delta.startsWith("-")) return "text-emerald-400";
+ if (delta.startsWith("+")) return "text-red-400";
+ return "text-muted-foreground";
+ };
+
+ return (
+
+
+ {/* Status indicator */}
+
+ {isCompleted ? : }
+
+
+
+ {problemName}
+
+
+
+
+ {/* Delta */}
+ {isCompleted && delta && (
+
+ {delta}
+
+ )}
+
+ {/* Split time */}
+
+ {isCompleted ? splitTime : pbTime || '--:--'}
+
+
+
+ );
+};
+
+export default SplitRow;
diff --git a/src/components/app/StatCard.tsx b/src/components/app/StatCard.tsx
new file mode 100644
index 0000000..ada35c5
--- /dev/null
+++ b/src/components/app/StatCard.tsx
@@ -0,0 +1,35 @@
+import { LucideIcon } from "lucide-react";
+
+interface StatCardProps {
+ label: string;
+ value: string;
+ icon: LucideIcon;
+ trend?: string;
+ index?: number;
+}
+
+const StatCard = ({ label, value, icon: Icon, trend, index = 0 }: StatCardProps) => {
+ return (
+
+
+
+
+
+ {trend && (
+
+ {trend}
+
+ )}
+
+
{value}
+
{label}
+
+ );
+};
+
+export default StatCard;
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..915ea2a
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,64 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }