diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index e9957e1..183b6a5 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,25 +1,27 @@
-import { Hero } from "@/components/landing/hero";
-import { Header } from "@/components/header";
-import { getWaitlistCount } from "@/lib/waitlist";
-import Image from "next/image";
-
-// Force dynamic rendering so waitlist count updates in real-time
-export const dynamic = "force-dynamic";
-
-export default async function Home() {
- const signupCount = await getWaitlistCount();
-
- return (
-
-
-
-
-
- );
-}
+import { Hero } from "@/components/landing/hero";
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { getWaitlistCount } from "@/lib/waitlist";
+import Image from "next/image";
+
+// Force dynamic rendering so waitlist count updates in real-time
+export const dynamic = "force-dynamic";
+
+export default async function Home() {
+ const signupCount = await getWaitlistCount();
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx
new file mode 100644
index 0000000..7cc1440
--- /dev/null
+++ b/apps/web/src/components/footer.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import { motion } from "motion/react";
+import Link from "next/link";
+import { useEffect, useState } from "react";
+import { RiGithubLine, RiTwitterXLine } from "react-icons/ri";
+import { getStars } from "@/lib/fetchGhStars";
+import Image from "next/image";
+
+export function Footer() {
+ const [star, setStar] = useState();
+
+ useEffect(() => {
+ const fetchStars = async () => {
+ try {
+ const data = await getStars();
+ setStar(data);
+ } catch (err) {
+ console.error("Failed to fetch GitHub stars", err);
+ }
+ };
+
+ fetchStars();
+ }, []);
+
+ return (
+
+
+
+ {/* Brand Section */}
+
+
+
+ OpenCut
+
+
+ The open source video editor that gets the job done. Simple,
+ powerful, and works on any platform.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Resources
+
+
+
+ Privacy policy
+
+
+
+
+ Terms of use
+
+
+
+
+
+ {/* Company Links */}
+
+
Company
+
+
+
+ Contributors
+
+
+
+
+ About
+
+
+
+
+
+
+
+ {/* Bottom Section */}
+
+
+ © 2025 OpenCut, All Rights Reserved
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx
index 7f4a7f2..1ac9390 100644
--- a/apps/web/src/components/landing/hero.tsx
+++ b/apps/web/src/components/landing/hero.tsx
@@ -1,197 +1,153 @@
-"use client";
-
-import { motion } from "motion/react";
-import { Button } from "../ui/button";
-import { Input } from "../ui/input";
-import { ArrowRight } from "lucide-react";
-import Link from "next/link";
-import { useEffect, useState } from "react";
-import { useToast } from "@/hooks/use-toast";
-import { getStars } from "@/lib/fetchGhStars";
-import Image from "next/image";
-import { RiGithubLine, RiTwitterXLine } from "react-icons/ri";
-
-interface HeroProps {
- signupCount: number;
-}
-
-export function Hero({ signupCount }: HeroProps) {
- const [star, setStar] = useState();
- const [email, setEmail] = useState("");
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { toast } = useToast();
-
- useEffect(() => {
- const fetchStars = async () => {
- try {
- const data = await getStars();
- setStar(data);
- } catch (err) {
- console.error("Failed to fetch GitHub stars", err);
- }
- };
-
- fetchStars();
- }, []);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- if (!email.trim()) {
- toast({
- title: "Email required",
- description: "Please enter your email address.",
- variant: "destructive",
- });
- return;
- }
-
- setIsSubmitting(true);
-
- try {
- const response = await fetch("/api/waitlist", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ email: email.trim() }),
- });
-
- const data = await response.json();
-
- if (response.ok) {
- toast({
- title: "Welcome to the waitlist! 🎉",
- description: "You'll be notified when we launch.",
- });
- setEmail("");
- } else {
- toast({
- title: "Oops!",
- description: data.error || "Something went wrong. Please try again.",
- variant: "destructive",
- });
- }
- } catch (error) {
- toast({
- title: "Network error",
- description: "Please check your connection and try again.",
- variant: "destructive",
- });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
-
-
- The Open Source
-
-
-
-
- Video Editor
-
-
-
-
-
-
- A simple but powerful video editor that gets the job done. Works on
- any platform.
-
-
-
-
-
-
- {signupCount > 0 && (
-
-
- {signupCount.toLocaleString()} people already joined
-
- )}
-
-
-
- Currently in beta • Open source on{" "}
-
- Github
-
- {star}+
-
- • Follow us on
-
- Twitter
-
-
-
-
- );
-}
+"use client";
+
+import { motion } from "motion/react";
+import { Button } from "../ui/button";
+import { Input } from "../ui/input";
+import { ArrowRight } from "lucide-react";
+import Link from "next/link";
+import { useState } from "react";
+import { useToast } from "@/hooks/use-toast";
+
+import Image from "next/image";
+
+interface HeroProps {
+ signupCount: number;
+}
+
+export function Hero({ signupCount }: HeroProps) {
+ const [email, setEmail] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const { toast } = useToast();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!email.trim()) {
+ toast({
+ title: "Email required",
+ description: "Please enter your email address.",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ const response = await fetch("/api/waitlist", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ email: email.trim() }),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ toast({
+ title: "Welcome to the waitlist! 🎉",
+ description: "You'll be notified when we launch.",
+ });
+ setEmail("");
+ } else {
+ toast({
+ title: "Oops!",
+ description: data.error || "Something went wrong. Please try again.",
+ variant: "destructive",
+ });
+ }
+ } catch (error) {
+ toast({
+ title: "Network error",
+ description: "Please check your connection and try again.",
+ variant: "destructive",
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+ The Open Source
+
+
+
+
+ Video Editor
+
+
+
+
+
+
+ A simple but powerful video editor that gets the job done. Works on
+ any platform.
+
+
+
+
+
+
+ {signupCount > 0 && (
+
+
+ {signupCount.toLocaleString()} people already joined
+
+ )}
+
+
+ );
+}
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index c35ee1a..00309f8 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -69,7 +69,7 @@ export default {
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)",
+ sm: "calc(var(--radius) - 6px)",
},
keyframes: {
"accordion-down": {