diff options
| author | Kha Nguyen <hi@khanguyen.com> | 2025-07-24 17:08:33 -0500 |
|---|---|---|
| committer | Kha Nguyen <hi@khanguyen.com> | 2025-07-24 17:10:39 -0500 |
| commit | 8dcbbfa25245624cfaa1f8b8b1a21c6c4e29d993 (patch) | |
| tree | 6d5e8268b98bea8fda8356a817a0662730613cb5 /apps/web/src/app | |
| parent | 8e737d87e0accbf1d433f4c8b1e8e0f125849799 (diff) | |
| parent | 65c3f9f7f78f787262a88c75b96a118ca5bee15c (diff) | |
| download | opencut-8dcbbfa25245624cfaa1f8b8b1a21c6c4e29d993.tar.gz | |
Merge branch 'main' into fix/project-title-styling
Diffstat (limited to 'apps/web/src/app')
| -rw-r--r-- | apps/web/src/app/api/auth/[...all]/route.ts | 8 | ||||
| -rw-r--r-- | apps/web/src/app/api/waitlist/route.ts | 188 | ||||
| -rw-r--r-- | apps/web/src/app/api/waitlist/token/route.ts | 23 | ||||
| -rw-r--r-- | apps/web/src/app/contributors/page.tsx | 554 | ||||
| -rw-r--r-- | apps/web/src/app/metadata.ts | 21 | ||||
| -rw-r--r-- | apps/web/src/app/page.tsx | 8 | ||||
| -rw-r--r-- | apps/web/src/app/privacy/page.tsx | 506 | ||||
| -rw-r--r-- | apps/web/src/app/roadmap/page.tsx | 494 | ||||
| -rw-r--r-- | apps/web/src/app/rss.xml/route.ts | 42 | ||||
| -rw-r--r-- | apps/web/src/app/terms/page.tsx | 634 | ||||
| -rw-r--r-- | apps/web/src/app/why-not-capcut/page.tsx | 382 |
11 files changed, 1448 insertions, 1412 deletions
diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts index 55510dcb..5deef204 100644 --- a/apps/web/src/app/api/auth/[...all]/route.ts +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@opencut/auth";
-import { toNextJsHandler } from "better-auth/next-js";
-
-export const { POST, GET } = toNextJsHandler(auth);
\ No newline at end of file +import { auth } from "@opencut/auth"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { POST, GET } = toNextJsHandler(auth); diff --git a/apps/web/src/app/api/waitlist/route.ts b/apps/web/src/app/api/waitlist/route.ts index e5c9fd94..9f00f221 100644 --- a/apps/web/src/app/api/waitlist/route.ts +++ b/apps/web/src/app/api/waitlist/route.ts @@ -1,83 +1,105 @@ -import { NextRequest, NextResponse } from "next/server";
-import { db, eq, waitlist } from "@opencut/db";
-import { checkBotId } from "botid/server";
-import { nanoid } from "nanoid";
-import { waitlistRateLimit } from "@/lib/rate-limit";
-import { z } from "zod";
-import { env } from "@/env";
-import { cookies } from "next/headers";
-import crypto from "crypto";
-
-const waitlistSchema = z.object({
- email: z.string().email("Invalid email format").min(1, "Email is required"),
-});
-
-const CSRF_TOKEN_NAME = "waitlist-csrf";
-const TOKEN_EXPIRY = 60 * 60 * 1000;
-
-async function validateCSRFToken(request: NextRequest): Promise<boolean> {
- const clientToken = request.headers.get("x-csrf-token");
- if (!clientToken) return false;
-
- const cookieStore = await cookies();
- const cookieValue = cookieStore.get(CSRF_TOKEN_NAME)?.value;
- if (!cookieValue) return false;
-
- const [token, timestamp, signature] = cookieValue.split(":");
- if (!token || !timestamp || !signature) return false;
-
- if (clientToken !== token) return false;
-
- const now = Date.now();
- const tokenTime = parseInt(timestamp);
- if (now - tokenTime > TOKEN_EXPIRY) return false;
-
- const expectedSignature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex");
-
- return signature === expectedSignature;
-}
-
-export async function POST(request: NextRequest) {
- const verification = await checkBotId();
-
- if (verification.isBot) {
- return NextResponse.json({ error: "Access denied" }, { status: 403 });
- }
-
- const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
- const { success } = await waitlistRateLimit.limit(identifier);
-
- if (!success) {
- return NextResponse.json({ error: "Too many requests. Please try again later." }, { status: 429 });
- }
- const isValidToken = await validateCSRFToken(request);
- if (!isValidToken) {
- return NextResponse.json({ error: "Invalid security token" }, { status: 403 });
- }
-
- try {
- const body = await request.json();
- const { email } = waitlistSchema.parse(body);
-
- const existingEmail = await db.select().from(waitlist).where(eq(waitlist.email, email.toLowerCase())).limit(1);
-
- if (existingEmail.length > 0) {
- return NextResponse.json({ error: "Email already registered" }, { status: 409 });
- }
-
- await db.insert(waitlist).values({
- id: nanoid(),
- email: email.toLowerCase(),
- });
-
- return NextResponse.json({ message: "Successfully joined waitlist!" }, { status: 201 });
- } catch (error) {
- if (error instanceof z.ZodError) {
- const firstError = error.errors[0];
- return NextResponse.json({ error: firstError.message }, { status: 400 });
- }
-
- console.error("Waitlist signup error:", error);
- return NextResponse.json({ error: "Internal server error" }, { status: 500 });
- }
-}
+import { NextRequest, NextResponse } from "next/server"; +import { db, eq, waitlist } from "@opencut/db"; +import { checkBotId } from "botid/server"; +import { nanoid } from "nanoid"; +import { waitlistRateLimit } from "@/lib/rate-limit"; +import { z } from "zod"; +import { env } from "@/env"; +import { cookies } from "next/headers"; +import crypto from "crypto"; + +const waitlistSchema = z.object({ + email: z.string().email("Invalid email format").min(1, "Email is required"), +}); + +const CSRF_TOKEN_NAME = "waitlist-csrf"; +const TOKEN_EXPIRY = 60 * 60 * 1000; + +async function validateCSRFToken(request: NextRequest): Promise<boolean> { + const clientToken = request.headers.get("x-csrf-token"); + if (!clientToken) return false; + + const cookieStore = await cookies(); + const cookieValue = cookieStore.get(CSRF_TOKEN_NAME)?.value; + if (!cookieValue) return false; + + const [token, timestamp, signature] = cookieValue.split(":"); + if (!token || !timestamp || !signature) return false; + + if (clientToken !== token) return false; + + const now = Date.now(); + const tokenTime = parseInt(timestamp); + if (now - tokenTime > TOKEN_EXPIRY) return false; + + const expectedSignature = crypto + .createHmac("sha256", env.BETTER_AUTH_SECRET) + .update(`${token}:${timestamp}`) + .digest("hex"); + + return signature === expectedSignature; +} + +export async function POST(request: NextRequest) { + const verification = await checkBotId(); + + if (verification.isBot) { + return NextResponse.json({ error: "Access denied" }, { status: 403 }); + } + + const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; + const { success } = await waitlistRateLimit.limit(identifier); + + if (!success) { + return NextResponse.json( + { error: "Too many requests. Please try again later." }, + { status: 429 } + ); + } + const isValidToken = await validateCSRFToken(request); + if (!isValidToken) { + return NextResponse.json( + { error: "Invalid security token" }, + { status: 403 } + ); + } + + try { + const body = await request.json(); + const { email } = waitlistSchema.parse(body); + + const existingEmail = await db + .select() + .from(waitlist) + .where(eq(waitlist.email, email.toLowerCase())) + .limit(1); + + if (existingEmail.length > 0) { + return NextResponse.json( + { error: "Email already registered" }, + { status: 409 } + ); + } + + await db.insert(waitlist).values({ + id: nanoid(), + email: email.toLowerCase(), + }); + + return NextResponse.json( + { message: "Successfully joined waitlist!" }, + { status: 201 } + ); + } catch (error) { + if (error instanceof z.ZodError) { + const firstError = error.errors[0]; + return NextResponse.json({ error: firstError.message }, { status: 400 }); + } + + console.error("Waitlist signup error:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/apps/web/src/app/api/waitlist/token/route.ts b/apps/web/src/app/api/waitlist/token/route.ts index 4c7695fa..fc2ff32c 100644 --- a/apps/web/src/app/api/waitlist/token/route.ts +++ b/apps/web/src/app/api/waitlist/token/route.ts @@ -5,7 +5,10 @@ import { env } from "@/env"; const CSRF_TOKEN_NAME = "waitlist-csrf"; const TOKEN_EXPIRY = 60 * 60 * 1000; -const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"]; +const allowedHosts = + env.NODE_ENV === "development" + ? ["localhost:3000", "127.0.0.1:3000"] + : ["opencut.app", "www.opencut.app"]; export async function GET(request: NextRequest) { const referer = request.headers.get("referer"); @@ -14,11 +17,20 @@ export async function GET(request: NextRequest) { if (referer) { const refererUrl = new URL(referer); - if (!allowedHosts.some((allowed) => refererUrl.host === allowed || refererUrl.host.endsWith(allowed))) { + if ( + !allowedHosts.some( + (allowed) => + refererUrl.host === allowed || refererUrl.host.endsWith(allowed) + ) + ) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } } else if (host) { - if (!allowedHosts.some((allowed) => host === allowed || host.endsWith(allowed))) { + if ( + !allowedHosts.some( + (allowed) => host === allowed || host.endsWith(allowed) + ) + ) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } } else { @@ -31,7 +43,10 @@ export async function GET(request: NextRequest) { const token = crypto.randomBytes(32).toString("hex"); const timestamp = Date.now(); - const signature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex"); + const signature = crypto + .createHmac("sha256", env.BETTER_AUTH_SECRET) + .update(`${token}:${timestamp}`) + .digest("hex"); const cookieStore = await cookies(); cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, { diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx index 5613f1f9..6a524866 100644 --- a/apps/web/src/app/contributors/page.tsx +++ b/apps/web/src/app/contributors/page.tsx @@ -1,277 +1,277 @@ -import { Metadata } from "next";
-import { Header } from "@/components/header";
-import { Card, CardContent } from "@/components/ui/card";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { Button } from "@/components/ui/button";
-import { ExternalLink } from "lucide-react";
-import Link from "next/link";
-import { GithubIcon } from "@/components/icons";
-import { Badge } from "@/components/ui/badge";
-
-export const metadata: Metadata = {
- title: "Contributors - OpenCut",
- description:
- "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
- openGraph: {
- title: "Contributors - OpenCut",
- description:
- "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
- type: "website",
- },
-};
-
-interface Contributor {
- id: number;
- login: string;
- avatar_url: string;
- html_url: string;
- contributions: number;
- type: string;
-}
-
-async function getContributors(): Promise<Contributor[]> {
- try {
- const response = await fetch(
- "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100",
- {
- headers: {
- Accept: "application/vnd.github.v3+json",
- "User-Agent": "OpenCut-Web-App",
- },
- next: { revalidate: 600 }, // 10 minutes
- }
- );
-
- if (!response.ok) {
- console.error("Failed to fetch contributors");
- return [];
- }
-
- const contributors = (await response.json()) as Contributor[];
-
- const filteredContributors = contributors.filter(
- (contributor: Contributor) => contributor.type === "User"
- );
-
- return filteredContributors;
- } catch (error) {
- console.error("Error fetching contributors:", error);
- return [];
- }
-}
-
-export default async function ContributorsPage() {
- const contributors = await getContributors();
- const topContributors = contributors.slice(0, 2);
- const otherContributors = contributors.slice(2);
-
- return (
- <div className="min-h-screen bg-background">
- <Header />
-
- <main className="relative">
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
- <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
- <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
- </div>
-
- <div className="relative container mx-auto px-4 py-16">
- <div className="max-w-6xl mx-auto">
- <div className="text-center mb-20">
- <Link
- href={"https://github.com/OpenCut-app/OpenCut"}
- target="_blank"
- >
- <Badge variant="secondary" className="gap-2 mb-6">
- <GithubIcon className="h-3 w-3" />
- Open Source
- </Badge>
- </Link>
- <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
- Contributors
- </h1>
- <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
- Meet the amazing developers who are building the future of video
- editing
- </p>
-
- <div className="flex items-center justify-center gap-8 text-sm">
- <div className="flex items-center gap-2">
- <div className="w-2 h-2 bg-foreground rounded-full" />
- <span className="font-medium">{contributors.length}</span>
- <span className="text-muted-foreground">contributors</span>
- </div>
- <div className="flex items-center gap-2">
- <div className="w-2 h-2 bg-foreground rounded-full" />
- <span className="font-medium">
- {contributors.reduce((sum, c) => sum + c.contributions, 0)}
- </span>
- <span className="text-muted-foreground">contributions</span>
- </div>
- </div>
- </div>
-
- {topContributors.length > 0 && (
- <div className="mb-20">
- <div className="text-center mb-12">
- <h2 className="text-2xl font-semibold mb-2">
- Top Contributors
- </h2>
- <p className="text-muted-foreground">
- Leading the way in contributions
- </p>
- </div>
-
- <div className="flex flex-col md:flex-row gap-6 justify-center max-w-4xl mx-auto">
- {topContributors.map((contributor, index) => (
- <Link
- key={contributor.id}
- href={contributor.html_url}
- target="_blank"
- rel="noopener noreferrer"
- className="group block flex-1"
- >
- <div className="relative mx-auto max-w-md">
- <div className="absolute inset-0 bg-gradient-to-r from-muted/50 to-muted/30 rounded-2xl blur group-hover:blur-md transition-all duration-300" />
- <Card className="relative bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl">
- <CardContent className="p-8 text-center">
- <div className="relative mb-6">
- <Avatar className="h-24 w-24 mx-auto ring-4 ring-background shadow-2xl">
- <AvatarImage
- src={contributor.avatar_url}
- alt={`${contributor.login}'s avatar`}
- />
- <AvatarFallback className="text-lg font-semibold">
- {contributor.login.charAt(0).toUpperCase()}
- </AvatarFallback>
- </Avatar>
- </div>
- <h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors">
- {contributor.login}
- </h3>
- <div className="flex items-center justify-center gap-2 text-muted-foreground">
- <span className="font-medium text-foreground">
- {contributor.contributions}
- </span>
- <span>contributions</span>
- </div>
- </CardContent>
- </Card>
- </div>
- </Link>
- ))}
- </div>
- </div>
- )}
-
- {otherContributors.length > 0 && (
- <div>
- <div className="text-center mb-12">
- <h2 className="text-2xl font-semibold mb-2">
- All Contributors
- </h2>
- <p className="text-muted-foreground">
- Everyone who makes OpenCut better
- </p>
- </div>
-
- <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
- {otherContributors.map((contributor, index) => (
- <Link
- key={contributor.id}
- href={contributor.html_url}
- target="_blank"
- rel="noopener noreferrer"
- className="group block"
- style={{
- animationDelay: `${index * 50}ms`,
- }}
- >
- <div className="text-center p-2 rounded-xl transition-all duration-300 hover:opacity-50">
- <Avatar className="h-16 w-16 mx-auto mb-3">
- <AvatarImage
- src={contributor.avatar_url}
- alt={`${contributor.login}'s avatar`}
- />
- <AvatarFallback className="font-medium">
- {contributor.login.charAt(0).toUpperCase()}
- </AvatarFallback>
- </Avatar>
- <h3 className="font-medium text-sm truncate group-hover:text-foreground transition-colors mb-1">
- {contributor.login}
- </h3>
- <p className="text-xs text-muted-foreground">
- {contributor.contributions}
- </p>
- </div>
- </Link>
- ))}
- </div>
- </div>
- )}
-
- {contributors.length === 0 && (
- <div className="text-center py-20">
- <div className="w-20 h-20 mx-auto mb-6 rounded-full bg-muted/50 flex items-center justify-center">
- <GithubIcon className="h-8 w-8 text-muted-foreground" />
- </div>
- <h3 className="text-xl font-medium mb-3">
- No contributors found
- </h3>
- <p className="text-muted-foreground mb-8 max-w-md mx-auto">
- Unable to load contributors at the moment. Check back later or
- view on GitHub.
- </p>
- <Link
- href="https://github.com/OpenCut-app/OpenCut/graphs/contributors"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Button variant="outline" className="gap-2">
- <GithubIcon className="h-4 w-4" />
- View on GitHub
- <ExternalLink className="h-4 w-4" />
- </Button>
- </Link>
- </div>
- )}
-
- <div className="mt-32 text-center">
- <div className="max-w-2xl mx-auto">
- <h2 className="text-3xl font-bold mb-4">Join the community</h2>
- <p className="text-lg text-muted-foreground mb-10 leading-relaxed">
- OpenCut is built by developers like you. Every contribution,
- no matter how small, helps make video editing more accessible
- for everyone.
- </p>
-
- <div className="flex flex-col sm:flex-row gap-4 justify-center">
- <Link
- href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Button size="lg" className="gap-2 group">
- <GithubIcon className="h-4 w-4 group-hover:scale-110 transition-transform" />
- Start Contributing
- </Button>
- </Link>
- <Link
- href="https://github.com/OpenCut-app/OpenCut/issues"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Button variant="outline" size="lg" className="gap-2 group">
- Browse Issues
- <ExternalLink className="h-4 w-4 group-hover:scale-110 transition-transform" />
- </Button>
- </Link>
- </div>
- </div>
- </div>
- </div>
- </div>
- </main>
- </div>
- );
-}
+import { Metadata } from "next"; +import { Header } from "@/components/header"; +import { Card, CardContent } from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { ExternalLink } from "lucide-react"; +import Link from "next/link"; +import { GithubIcon } from "@/components/icons"; +import { Badge } from "@/components/ui/badge"; + +export const metadata: Metadata = { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + openGraph: { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + type: "website", + }, +}; + +interface Contributor { + id: number; + login: string; + avatar_url: string; + html_url: string; + contributions: number; + type: string; +} + +async function getContributors(): Promise<Contributor[]> { + try { + const response = await fetch( + "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100", + { + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": "OpenCut-Web-App", + }, + next: { revalidate: 600 }, // 10 minutes + } + ); + + if (!response.ok) { + console.error("Failed to fetch contributors"); + return []; + } + + const contributors = (await response.json()) as Contributor[]; + + const filteredContributors = contributors.filter( + (contributor: Contributor) => contributor.type === "User" + ); + + return filteredContributors; + } catch (error) { + console.error("Error fetching contributors:", error); + return []; + } +} + +export default async function ContributorsPage() { + const contributors = await getContributors(); + const topContributors = contributors.slice(0, 2); + const otherContributors = contributors.slice(2); + + return ( + <div className="min-h-screen bg-background"> + <Header /> + + <main className="relative"> + <div className="absolute inset-0 overflow-hidden pointer-events-none"> + <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" /> + <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" /> + </div> + + <div className="relative container mx-auto px-4 py-16"> + <div className="max-w-6xl mx-auto"> + <div className="text-center mb-20"> + <Link + href={"https://github.com/OpenCut-app/OpenCut"} + target="_blank" + > + <Badge variant="secondary" className="gap-2 mb-6"> + <GithubIcon className="h-3 w-3" /> + Open Source + </Badge> + </Link> + <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6"> + Contributors + </h1> + <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed"> + Meet the amazing developers who are building the future of video + editing + </p> + + <div className="flex items-center justify-center gap-8 text-sm"> + <div className="flex items-center gap-2"> + <div className="w-2 h-2 bg-foreground rounded-full" /> + <span className="font-medium">{contributors.length}</span> + <span className="text-muted-foreground">contributors</span> + </div> + <div className="flex items-center gap-2"> + <div className="w-2 h-2 bg-foreground rounded-full" /> + <span className="font-medium"> + {contributors.reduce((sum, c) => sum + c.contributions, 0)} + </span> + <span className="text-muted-foreground">contributions</span> + </div> + </div> + </div> + + {topContributors.length > 0 && ( + <div className="mb-20"> + <div className="text-center mb-12"> + <h2 className="text-2xl font-semibold mb-2"> + Top Contributors + </h2> + <p className="text-muted-foreground"> + Leading the way in contributions + </p> + </div> + + <div className="flex flex-col md:flex-row gap-6 justify-center max-w-4xl mx-auto"> + {topContributors.map((contributor, index) => ( + <Link + key={contributor.id} + href={contributor.html_url} + target="_blank" + rel="noopener noreferrer" + className="group block flex-1" + > + <div className="relative mx-auto max-w-md"> + <div className="absolute inset-0 bg-gradient-to-r from-muted/50 to-muted/30 rounded-2xl blur group-hover:blur-md transition-all duration-300" /> + <Card className="relative bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl"> + <CardContent className="p-8 text-center"> + <div className="relative mb-6"> + <Avatar className="h-24 w-24 mx-auto ring-4 ring-background shadow-2xl"> + <AvatarImage + src={contributor.avatar_url} + alt={`${contributor.login}'s avatar`} + /> + <AvatarFallback className="text-lg font-semibold"> + {contributor.login.charAt(0).toUpperCase()} + </AvatarFallback> + </Avatar> + </div> + <h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors"> + {contributor.login} + </h3> + <div className="flex items-center justify-center gap-2 text-muted-foreground"> + <span className="font-medium text-foreground"> + {contributor.contributions} + </span> + <span>contributions</span> + </div> + </CardContent> + </Card> + </div> + </Link> + ))} + </div> + </div> + )} + + {otherContributors.length > 0 && ( + <div> + <div className="text-center mb-12"> + <h2 className="text-2xl font-semibold mb-2"> + All Contributors + </h2> + <p className="text-muted-foreground"> + Everyone who makes OpenCut better + </p> + </div> + + <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6"> + {otherContributors.map((contributor, index) => ( + <Link + key={contributor.id} + href={contributor.html_url} + target="_blank" + rel="noopener noreferrer" + className="group block" + style={{ + animationDelay: `${index * 50}ms`, + }} + > + <div className="text-center p-2 rounded-xl transition-all duration-300 hover:opacity-50"> + <Avatar className="h-16 w-16 mx-auto mb-3"> + <AvatarImage + src={contributor.avatar_url} + alt={`${contributor.login}'s avatar`} + /> + <AvatarFallback className="font-medium"> + {contributor.login.charAt(0).toUpperCase()} + </AvatarFallback> + </Avatar> + <h3 className="font-medium text-sm truncate group-hover:text-foreground transition-colors mb-1"> + {contributor.login} + </h3> + <p className="text-xs text-muted-foreground"> + {contributor.contributions} + </p> + </div> + </Link> + ))} + </div> + </div> + )} + + {contributors.length === 0 && ( + <div className="text-center py-20"> + <div className="w-20 h-20 mx-auto mb-6 rounded-full bg-muted/50 flex items-center justify-center"> + <GithubIcon className="h-8 w-8 text-muted-foreground" /> + </div> + <h3 className="text-xl font-medium mb-3"> + No contributors found + </h3> + <p className="text-muted-foreground mb-8 max-w-md mx-auto"> + Unable to load contributors at the moment. Check back later or + view on GitHub. + </p> + <Link + href="https://github.com/OpenCut-app/OpenCut/graphs/contributors" + target="_blank" + rel="noopener noreferrer" + > + <Button variant="outline" className="gap-2"> + <GithubIcon className="h-4 w-4" /> + View on GitHub + <ExternalLink className="h-4 w-4" /> + </Button> + </Link> + </div> + )} + + <div className="mt-32 text-center"> + <div className="max-w-2xl mx-auto"> + <h2 className="text-3xl font-bold mb-4">Join the community</h2> + <p className="text-lg text-muted-foreground mb-10 leading-relaxed"> + OpenCut is built by developers like you. Every contribution, + no matter how small, helps make video editing more accessible + for everyone. + </p> + + <div className="flex flex-col sm:flex-row gap-4 justify-center"> + <Link + href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md" + target="_blank" + rel="noopener noreferrer" + > + <Button size="lg" className="gap-2 group"> + <GithubIcon className="h-4 w-4 group-hover:scale-110 transition-transform" /> + Start Contributing + </Button> + </Link> + <Link + href="https://github.com/OpenCut-app/OpenCut/issues" + target="_blank" + rel="noopener noreferrer" + > + <Button variant="outline" size="lg" className="gap-2 group"> + Browse Issues + <ExternalLink className="h-4 w-4 group-hover:scale-110 transition-transform" /> + </Button> + </Link> + </div> + </div> + </div> + </div> + </div> + </main> + </div> + ); +} diff --git a/apps/web/src/app/metadata.ts b/apps/web/src/app/metadata.ts index de8cf6f0..0608974d 100644 --- a/apps/web/src/app/metadata.ts +++ b/apps/web/src/app/metadata.ts @@ -1,24 +1,15 @@ -import { Metadata } from "next"; - -export const SITE_INFO = { - title: "OpenCut", - description: - "A simple but powerful video editor that gets the job done. In your browser.", - url: "https://opencut.app", - openGraphImage: "https://opencut.app/open-graph/default.jpg", - twitterImage: "/open-graph/default.jpg", - favicon: "/favicon.ico", -}; +import type { Metadata } from "next"; +import { SITE_INFO, SITE_URL } from "@/constants/site"; export const baseMetaData: Metadata = { - metadataBase: new URL("https://opencut.app"), + metadataBase: new URL(SITE_URL), title: SITE_INFO.title, description: SITE_INFO.description, openGraph: { title: SITE_INFO.title, description: SITE_INFO.description, - url: "https://opencut.app", - siteName: "OpenCut", + url: SITE_URL, + siteName: SITE_INFO.title, locale: "en_US", type: "website", images: [ @@ -26,7 +17,7 @@ export const baseMetaData: Metadata = { url: SITE_INFO.openGraphImage, width: 1200, height: 630, - alt: "OpenCut", + alt: "OpenCut Wordmark", }, ], }, diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 74961549..818884ed 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,6 +1,14 @@ import { Hero } from "@/components/landing/hero"; import { Header } from "@/components/header"; import { Footer } from "@/components/footer"; +import type { Metadata } from "next"; +import { SITE_URL } from "@/constants/site"; + +export const metadata: Metadata = { + alternates: { + canonical: SITE_URL, + }, +}; export default async function Home() { return ( diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index 42dd0f9a..b7d700b5 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -1,253 +1,253 @@ -import { Metadata } from "next";
-import { Card, CardContent } from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
-import { GithubIcon } from "@/components/icons";
-import Link from "next/link";
-import { Footer } from "@/components/footer";
-import { Header } from "@/components/header";
-
-export const metadata: Metadata = {
- title: "Privacy Policy - OpenCut",
- description:
- "Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
- openGraph: {
- title: "Privacy Policy - OpenCut",
- description:
- "Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
- type: "website",
- },
-};
-
-export default function PrivacyPage() {
- return (
- <div className="min-h-screen bg-background">
- <Header />
- <main className="relative">
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
- <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
- <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
- </div>
- <div className="relative container mx-auto px-4 py-16">
- <div className="max-w-4xl mx-auto">
- <div className="text-center mb-10">
- <Link
- href="https://github.com/OpenCut-app/OpenCut"
- target="_blank"
- >
- <Badge variant="secondary" className="gap-2 mb-6">
- <GithubIcon className="h-3 w-3" />
- Open Source
- </Badge>
- </Link>
- <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
- Privacy Policy
- </h1>
- <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
- Learn how we handle your data and privacy. Contact us if you
- have any questions.
- </p>
- </div>
- <Card className="bg-background/80 backdrop-blur-sm border-2 border-muted/30">
- <CardContent className="p-8 text-base leading-relaxed space-y-8">
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Your Videos Stay Private
- </h2>
- <p className="mb-4">
- <strong>
- OpenCut processes all videos locally on your device.
- </strong>{" "}
- We never upload, store, or have access to your video files.
- Your content remains completely private and under your
- control at all times.
- </p>
- <p>
- All video editing, rendering, and processing happens in your
- browser using WebAssembly and local storage. No video data
- is transmitted to our servers.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Account Information
- </h2>
- <p className="mb-4">
- When you create an account, we only collect:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>Email address (for account access)</li>
- <li>
- Profile information from Google OAuth (if you choose to
- sign in with Google)
- </li>
- </ul>
- <p className="mb-4">
- <strong>
- We do NOT store your projects on our servers.
- </strong>{" "}
- All project data, including names, thumbnails, and creation
- dates, is stored locally in your browser using IndexedDB.
- </p>
- <p>
- We use{" "}
- <a
- href="https://www.better-auth.com"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- Better Auth
- </a>{" "}
- for secure authentication and follow industry-standard
- security practices.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">Analytics</h2>
- <p className="mb-4">
- We use{" "}
- <a
- href="https://www.databuddy.cc"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- Databuddy
- </a>{" "}
- for completely anonymized and non-invasive analytics to
- understand how people use OpenCut.
- </p>
- <p>
- This helps us improve the editor, but we never collect
- personal information, track individual users, or store any
- data that could identify you.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Local Storage & Cookies
- </h2>
- <p className="mb-4">
- We use browser local storage and IndexedDB to:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>Save your projects locally on your device</li>
- <li>Remember your editor preferences and settings</li>
- <li>Keep you logged in across browser sessions</li>
- </ul>
- <p>
- All data stays on your device and can be cleared at any time
- through your browser settings.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Third-Party Services
- </h2>
- <p className="mb-4">
- OpenCut integrates with these services:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- <strong>Google OAuth:</strong> For optional Google sign-in
- (governed by Google's privacy policy)
- </li>
- <li>
- <strong>Vercel:</strong> For hosting and content delivery
- </li>
- <li>
- <strong>Databuddy:</strong> For anonymized analytics
- </li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">Your Rights</h2>
- <p className="mb-4">
- You have complete control over your data:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- Delete your account and all associated data at any time
- </li>
- <li>Export your project data</li>
- <li>Clear local storage to remove all saved projects</li>
- <li>Contact us with any privacy concerns</li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Open Source Transparency
- </h2>
- <p className="mb-4">
- OpenCut is completely open source. You can review our code,
- see exactly how we handle data, and even self-host the
- application if you prefer.
- </p>
- <p>
- View our source code on{" "}
- <a
- href="https://github.com/OpenCut-app/OpenCut"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- GitHub
- </a>
- .
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">Contact Us</h2>
- <p className="mb-4">
- Questions about this privacy policy or how we handle your
- data?
- </p>
- <p>
- Open an issue on our{" "}
- <a
- href="https://github.com/OpenCut-app/OpenCut/issues"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- GitHub repository
- </a>
- , email us at{" "}
- <a
- href="mailto:oss@opencut.app"
- className="text-primary hover:underline"
- >
- oss@opencut.app
- </a>
- , or reach out on{" "}
- <a
- href="https://x.com/opencutapp"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- X (Twitter)
- </a>
- .
- </p>
- </section>
-
- <p className="text-sm text-muted-foreground mt-8 pt-8 border-t border-muted/20">
- Last updated: July 14, 2025
- </p>
- </CardContent>
- </Card>
- </div>
- </div>
- </main>
- <Footer />
- </div>
- );
-}
+import { Metadata } from "next"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { GithubIcon } from "@/components/icons"; +import Link from "next/link"; +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; + +export const metadata: Metadata = { + title: "Privacy Policy - OpenCut", + description: + "Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.", + openGraph: { + title: "Privacy Policy - OpenCut", + description: + "Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.", + type: "website", + }, +}; + +export default function PrivacyPage() { + return ( + <div className="min-h-screen bg-background"> + <Header /> + <main className="relative"> + <div className="absolute inset-0 overflow-hidden pointer-events-none"> + <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" /> + <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" /> + </div> + <div className="relative container mx-auto px-4 py-16"> + <div className="max-w-4xl mx-auto"> + <div className="text-center mb-10"> + <Link + href="https://github.com/OpenCut-app/OpenCut" + target="_blank" + > + <Badge variant="secondary" className="gap-2 mb-6"> + <GithubIcon className="h-3 w-3" /> + Open Source + </Badge> + </Link> + <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6"> + Privacy Policy + </h1> + <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed"> + Learn how we handle your data and privacy. Contact us if you + have any questions. + </p> + </div> + <Card className="bg-background/80 backdrop-blur-sm border-2 border-muted/30"> + <CardContent className="p-8 text-base leading-relaxed space-y-8"> + <section> + <h2 className="text-2xl font-semibold mb-4"> + Your Videos Stay Private + </h2> + <p className="mb-4"> + <strong> + OpenCut processes all videos locally on your device. + </strong>{" "} + We never upload, store, or have access to your video files. + Your content remains completely private and under your + control at all times. + </p> + <p> + All video editing, rendering, and processing happens in your + browser using WebAssembly and local storage. No video data + is transmitted to our servers. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Account Information + </h2> + <p className="mb-4"> + When you create an account, we only collect: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li>Email address (for account access)</li> + <li> + Profile information from Google OAuth (if you choose to + sign in with Google) + </li> + </ul> + <p className="mb-4"> + <strong> + We do NOT store your projects on our servers. + </strong>{" "} + All project data, including names, thumbnails, and creation + dates, is stored locally in your browser using IndexedDB. + </p> + <p> + We use{" "} + <a + href="https://www.better-auth.com" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + Better Auth + </a>{" "} + for secure authentication and follow industry-standard + security practices. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4">Analytics</h2> + <p className="mb-4"> + We use{" "} + <a + href="https://www.databuddy.cc" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + Databuddy + </a>{" "} + for completely anonymized and non-invasive analytics to + understand how people use OpenCut. + </p> + <p> + This helps us improve the editor, but we never collect + personal information, track individual users, or store any + data that could identify you. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Local Storage & Cookies + </h2> + <p className="mb-4"> + We use browser local storage and IndexedDB to: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li>Save your projects locally on your device</li> + <li>Remember your editor preferences and settings</li> + <li>Keep you logged in across browser sessions</li> + </ul> + <p> + All data stays on your device and can be cleared at any time + through your browser settings. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Third-Party Services + </h2> + <p className="mb-4"> + OpenCut integrates with these services: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + <strong>Google OAuth:</strong> For optional Google sign-in + (governed by Google's privacy policy) + </li> + <li> + <strong>Vercel:</strong> For hosting and content delivery + </li> + <li> + <strong>Databuddy:</strong> For anonymized analytics + </li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4">Your Rights</h2> + <p className="mb-4"> + You have complete control over your data: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + Delete your account and all associated data at any time + </li> + <li>Export your project data</li> + <li>Clear local storage to remove all saved projects</li> + <li>Contact us with any privacy concerns</li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Open Source Transparency + </h2> + <p className="mb-4"> + OpenCut is completely open source. You can review our code, + see exactly how we handle data, and even self-host the + application if you prefer. + </p> + <p> + View our source code on{" "} + <a + href="https://github.com/OpenCut-app/OpenCut" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + GitHub + </a> + . + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4">Contact Us</h2> + <p className="mb-4"> + Questions about this privacy policy or how we handle your + data? + </p> + <p> + Open an issue on our{" "} + <a + href="https://github.com/OpenCut-app/OpenCut/issues" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + GitHub repository + </a> + , email us at{" "} + <a + href="mailto:oss@opencut.app" + className="text-primary hover:underline" + > + oss@opencut.app + </a> + , or reach out on{" "} + <a + href="https://x.com/opencutapp" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + X (Twitter) + </a> + . + </p> + </section> + + <p className="text-sm text-muted-foreground mt-8 pt-8 border-t border-muted/20"> + Last updated: July 14, 2025 + </p> + </CardContent> + </Card> + </div> + </div> + </main> + <Footer /> + </div> + ); +} diff --git a/apps/web/src/app/roadmap/page.tsx b/apps/web/src/app/roadmap/page.tsx index c62dce3a..5c003cf0 100644 --- a/apps/web/src/app/roadmap/page.tsx +++ b/apps/web/src/app/roadmap/page.tsx @@ -1,247 +1,247 @@ -import { Metadata } from "next";
-import { Badge } from "@/components/ui/badge";
-import { GithubIcon } from "@/components/icons";
-import Link from "next/link";
-import { Footer } from "@/components/footer";
-import { Header } from "@/components/header";
-import ReactMarkdown from "react-markdown";
-import { cn } from "@/lib/utils";
-
-const roadmapItems: {
- title: string;
- description: string;
- status: {
- text: string;
- type: "complete" | "pending" | "default" | "info";
- };
-}[] = [
- {
- title: "Start",
- description:
- "This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.",
- status: {
- text: "Completed",
- type: "complete",
- },
- },
- {
- title: "Core UI",
- description:
- "Built the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.",
- status: {
- text: "Completed",
- type: "complete",
- },
- },
- {
- title: "Basic Functionality",
- description:
- "The heart of any video editor. Timeline zoom in/out, making clips longer/shorter, dragging elements around, selection, playhead scrubbing. **This part has to be fucking perfect** because it's what users interact with 99% of the time.",
- status: {
- text: "In Progress",
- type: "pending",
- },
- },
- {
- title: "Export/Preview Logic",
- description:
- "The foundation that enables everything else. Real-time preview, video rendering, export functionality. Once this works, we can add effects, filters, transitions - basically everything that makes a video editor powerful.",
- status: {
- text: "In Progress",
- type: "pending",
- },
- },
- {
- title: "Text",
- description:
- "After media, text is the next most important thing. Font selection with custom font imports, text stroke, colors. All the text essential text properties.",
- status: {
- text: "Not Started",
- type: "default",
- },
- },
- {
- title: "Effects",
- description:
- "Adding visual effects to both text and media. Blur, brightness, contrast, saturation, filters, and all the creative tools that make videos pop. This is where the magic happens.",
- status: {
- text: "Not Started",
- type: "default",
- },
- },
- {
- title: "Transitions",
- description:
- "Smooth transitions between clips. Fade in/out, slide, zoom, dissolve, and custom transition effects.",
- status: {
- text: "Not Started",
- type: "default",
- },
- },
- {
- title: "Refine from Here",
- description:
- "Once we nail the above, we have a **solid foundation** to build anything. Advanced features, performance optimizations, mobile support, desktop app.",
- status: {
- text: "Future",
- type: "info",
- },
- },
-];
-
-export const metadata: Metadata = {
- title: "Roadmap - OpenCut",
- description:
- "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
- openGraph: {
- title: "OpenCut Roadmap - What's Coming Next",
- description:
- "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
- type: "website",
- images: [
- {
- url: "/open-graph/roadmap.jpg",
- width: 1200,
- height: 630,
- alt: "OpenCut Roadmap",
- },
- ],
- },
- twitter: {
- card: "summary_large_image",
- title: "OpenCut Roadmap - What's Coming Next",
- description:
- "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
- images: ["/open-graph/roadmap.jpg"],
- },
-};
-
-export default function RoadmapPage() {
- return (
- <div className="min-h-screen bg-background">
- <Header />
- <main className="relative">
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
- <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
- <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
- </div>
- <div className="relative container mx-auto px-4 py-16">
- <div className="max-w-4xl mx-auto">
- <div className="text-center mb-10">
- <Link
- href="https://github.com/OpenCut-app/OpenCut"
- target="_blank"
- >
- <Badge variant="secondary" className="gap-2 mb-6">
- <GithubIcon className="h-3 w-3" />
- Open Source
- </Badge>
- </Link>
- <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
- Roadmap
- </h1>
- <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
- What's coming next for OpenCut (last updated: July 14, 2025)
- </p>
- </div>
- <div className="space-y-6">
- {roadmapItems.map((item, index) => (
- <div key={index} className="relative">
- <div className="flex items-start gap-2">
- <span className="text-lg font-medium text-muted-foreground select-none leading-[1.5]">
- {index + 1}.
- </span>
- <div className="flex-1 pt-[2px]">
- <div className="flex items-center gap-2 mb-2">
- <h3 className="font-medium text-lg">{item.title}</h3>
- <Badge
- className={cn("shadow-none", {
- "!bg-green-500 text-white":
- item.status.type === "complete",
- "!bg-yellow-500 text-white":
- item.status.type === "pending",
- "!bg-blue-500 text-white":
- item.status.type === "info",
- "!bg-foreground/10 text-accent-foreground":
- item.status.type === "default",
- })}
- >
- {item.status.text}
- </Badge>
- </div>
- <div className="text-foreground/70 leading-relaxed">
- <ReactMarkdown
- components={{
- a: ({ className, children, ...props }) => (
- <a
- className={cn(
- "text-primary hover:underline",
- className
- )}
- target="_blank"
- rel="noopener noreferrer"
- {...props}
- >
- {children}
- </a>
- ),
- strong: ({ children }) => (
- <strong className="font-semibold text-foreground">
- {children}
- </strong>
- ),
- }}
- >
- {item.description}
- </ReactMarkdown>
- </div>
- </div>
- </div>
- </div>
- ))}
- </div>
-
- <div className="mt-12 pt-8 border-t border-muted/20">
- <div className="text-center space-y-4">
- <h3 className="text-xl font-semibold">Want to Help?</h3>
- <p className="text-muted-foreground max-w-2xl mx-auto">
- OpenCut is open source and built by the community. Every
- contribution, no matter how small, helps us build the best
- free video editor possible.
- </p>
- <div className="flex flex-col sm:flex-row gap-4 justify-center mt-6">
- <Link
- href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Badge
- variant="outline"
- className="text-sm px-4 py-2 hover:bg-muted/50 transition-colors"
- >
- <GithubIcon className="h-4 w-4 mr-2" />
- Start Contributing
- </Badge>
- </Link>
- <Link
- href="https://github.com/OpenCut-app/OpenCut/issues"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Badge
- variant="outline"
- className="text-sm px-4 py-2 hover:bg-muted/50 transition-colors"
- >
- Report Issues
- </Badge>
- </Link>
- </div>
- </div>
- </div>
- </div>
- </div>
- </main>
- <Footer />
- </div>
- );
-}
+import { Metadata } from "next"; +import { Badge } from "@/components/ui/badge"; +import { GithubIcon } from "@/components/icons"; +import Link from "next/link"; +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; +import ReactMarkdown from "react-markdown"; +import { cn } from "@/lib/utils"; + +const roadmapItems: { + title: string; + description: string; + status: { + text: string; + type: "complete" | "pending" | "default" | "info"; + }; +}[] = [ + { + title: "Start", + description: + "This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Core UI", + description: + "Built the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Basic Functionality", + description: + "The heart of any video editor. Timeline zoom in/out, making clips longer/shorter, dragging elements around, selection, playhead scrubbing. **This part has to be fucking perfect** because it's what users interact with 99% of the time.", + status: { + text: "In Progress", + type: "pending", + }, + }, + { + title: "Export/Preview Logic", + description: + "The foundation that enables everything else. Real-time preview, video rendering, export functionality. Once this works, we can add effects, filters, transitions - basically everything that makes a video editor powerful.", + status: { + text: "In Progress", + type: "pending", + }, + }, + { + title: "Text", + description: + "After media, text is the next most important thing. Font selection with custom font imports, text stroke, colors. All the text essential text properties.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Effects", + description: + "Adding visual effects to both text and media. Blur, brightness, contrast, saturation, filters, and all the creative tools that make videos pop. This is where the magic happens.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Transitions", + description: + "Smooth transitions between clips. Fade in/out, slide, zoom, dissolve, and custom transition effects.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Refine from Here", + description: + "Once we nail the above, we have a **solid foundation** to build anything. Advanced features, performance optimizations, mobile support, desktop app.", + status: { + text: "Future", + type: "info", + }, + }, +]; + +export const metadata: Metadata = { + title: "Roadmap - OpenCut", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + openGraph: { + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + type: "website", + images: [ + { + url: "/open-graph/roadmap.jpg", + width: 1200, + height: 630, + alt: "OpenCut Roadmap", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + images: ["/open-graph/roadmap.jpg"], + }, +}; + +export default function RoadmapPage() { + return ( + <div className="min-h-screen bg-background"> + <Header /> + <main className="relative"> + <div className="absolute inset-0 overflow-hidden pointer-events-none"> + <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" /> + <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" /> + </div> + <div className="relative container mx-auto px-4 py-16"> + <div className="max-w-4xl mx-auto"> + <div className="text-center mb-10"> + <Link + href="https://github.com/OpenCut-app/OpenCut" + target="_blank" + > + <Badge variant="secondary" className="gap-2 mb-6"> + <GithubIcon className="h-3 w-3" /> + Open Source + </Badge> + </Link> + <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6"> + Roadmap + </h1> + <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed"> + What's coming next for OpenCut (last updated: July 14, 2025) + </p> + </div> + <div className="space-y-6"> + {roadmapItems.map((item, index) => ( + <div key={index} className="relative"> + <div className="flex items-start gap-2"> + <span className="text-lg font-medium text-muted-foreground select-none leading-[1.5]"> + {index + 1}. + </span> + <div className="flex-1 pt-[2px]"> + <div className="flex items-center gap-2 mb-2"> + <h3 className="font-medium text-lg">{item.title}</h3> + <Badge + className={cn("shadow-none", { + "!bg-green-500 text-white": + item.status.type === "complete", + "!bg-yellow-500 text-white": + item.status.type === "pending", + "!bg-blue-500 text-white": + item.status.type === "info", + "!bg-foreground/10 text-accent-foreground": + item.status.type === "default", + })} + > + {item.status.text} + </Badge> + </div> + <div className="text-foreground/70 leading-relaxed"> + <ReactMarkdown + components={{ + a: ({ className, children, ...props }) => ( + <a + className={cn( + "text-primary hover:underline", + className + )} + target="_blank" + rel="noopener noreferrer" + {...props} + > + {children} + </a> + ), + strong: ({ children }) => ( + <strong className="font-semibold text-foreground"> + {children} + </strong> + ), + }} + > + {item.description} + </ReactMarkdown> + </div> + </div> + </div> + </div> + ))} + </div> + + <div className="mt-12 pt-8 border-t border-muted/20"> + <div className="text-center space-y-4"> + <h3 className="text-xl font-semibold">Want to Help?</h3> + <p className="text-muted-foreground max-w-2xl mx-auto"> + OpenCut is open source and built by the community. Every + contribution, no matter how small, helps us build the best + free video editor possible. + </p> + <div className="flex flex-col sm:flex-row gap-4 justify-center mt-6"> + <Link + href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md" + target="_blank" + rel="noopener noreferrer" + > + <Badge + variant="outline" + className="text-sm px-4 py-2 hover:bg-muted/50 transition-colors" + > + <GithubIcon className="h-4 w-4 mr-2" /> + Start Contributing + </Badge> + </Link> + <Link + href="https://github.com/OpenCut-app/OpenCut/issues" + target="_blank" + rel="noopener noreferrer" + > + <Badge + variant="outline" + className="text-sm px-4 py-2 hover:bg-muted/50 transition-colors" + > + Report Issues + </Badge> + </Link> + </div> + </div> + </div> + </div> + </div> + </main> + <Footer /> + </div> + ); +} diff --git a/apps/web/src/app/rss.xml/route.ts b/apps/web/src/app/rss.xml/route.ts index b16bd856..ab645655 100644 --- a/apps/web/src/app/rss.xml/route.ts +++ b/apps/web/src/app/rss.xml/route.ts @@ -1,33 +1,33 @@ -import { Feed } from 'feed'; -import { getPosts } from '@/lib/blog-query'; -import { SITE_INFO } from '../metadata'; +import { Feed } from "feed"; +import { getPosts } from "@/lib/blog-query"; +import { SITE_INFO, SITE_URL } from "@/constants/site"; export async function GET() { try { const { posts } = await getPosts(); - + const feed = new Feed({ - title: 'OpenCut Blog', + title: `${SITE_INFO.title} Blog`, description: SITE_INFO.description, - id: `${SITE_INFO.url}`, - link: `${SITE_INFO.url}/blog/`, - language: 'en', + id: `${SITE_URL}`, + link: `${SITE_URL}/blog/`, + language: "en", image: `${SITE_INFO.openGraphImage}`, favicon: `${SITE_INFO.favicon}`, copyright: `All rights reserved ${new Date().getFullYear()}, ${ SITE_INFO.title }`, }); - + for (const post of posts) { feed.addItem({ - title: post.title, - id: `${SITE_INFO.url}/blog/${post.slug}`, - link: `${SITE_INFO.url}/blog/${post.slug}`, - description: post.description, - author: post.authors.map((author) => ({ - name: author.name, - })), + title: post.title, + id: `${SITE_URL}/blog/${post.slug}`, + link: `${SITE_URL}/blog/${post.slug}`, + description: post.description, + author: post.authors.map((author) => ({ + name: author.name, + })), date: new Date(post.publishedAt), image: post.coverImage || SITE_INFO.openGraphImage, }); @@ -35,12 +35,12 @@ export async function GET() { return new Response(feed.rss2(), { headers: { - 'Content-Type': 'text/xml', - 'Cache-Control': 'public, max-age=86400, stale-while-revalidate', + "Content-Type": "text/xml", + "Cache-Control": "public, max-age=86400, stale-while-revalidate", }, }); } catch (error) { - console.error('Error generating RSS feed', error); - return new Response('Internal Server Error', { status: 500 }); + console.error("Error generating RSS feed", error); + return new Response("Internal Server Error", { status: 500 }); } -}
\ No newline at end of file +} diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index c56e8fc1..7d1fda7f 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -1,317 +1,317 @@ -import { Metadata } from "next";
-import { Card, CardContent } from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
-import { GithubIcon } from "@/components/icons";
-import Link from "next/link";
-import { Footer } from "@/components/footer";
-import { Header } from "@/components/header";
-
-export const metadata: Metadata = {
- title: "Terms of Service - OpenCut",
- description:
- "OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
- openGraph: {
- title: "Terms of Service - OpenCut",
- description:
- "OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
- type: "website",
- },
-};
-
-export default function TermsPage() {
- return (
- <div className="min-h-screen bg-background">
- <Header />
- <main className="relative">
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
- <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
- <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
- </div>
- <div className="relative container mx-auto px-4 py-16">
- <div className="max-w-4xl mx-auto">
- <div className="text-center mb-10">
- <Link
- href="https://github.com/OpenCut-app/OpenCut"
- target="_blank"
- >
- <Badge variant="secondary" className="gap-2 mb-6">
- <GithubIcon className="h-3 w-3" />
- Open Source
- </Badge>
- </Link>
- <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
- Terms of Service
- </h1>
- <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
- Fair and transparent terms for our free, open-source video
- editor.
- </p>
- </div>
- <Card className="bg-background/80 backdrop-blur-sm border-2 border-muted/30">
- <CardContent className="p-8 text-base leading-relaxed space-y-8">
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Welcome to OpenCut
- </h2>
- <p className="mb-4">
- OpenCut is a free, open-source video editor that runs in
- your browser. By using our service, you agree to these
- terms. We've designed these terms to be fair and protect
- both you and our project.
- </p>
- <p>
- <strong>Key principle:</strong> Your content stays on your
- device. We never claim ownership of your videos or projects.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Your Content, Your Rights
- </h2>
- <p className="mb-4">
- <strong>You own everything you create.</strong> OpenCut
- processes your videos locally on your device, so we never
- have access to your content. We make no claims to ownership,
- licensing, or rights over your videos, projects, or any
- content you create using OpenCut.
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- Your videos remain completely private and under your
- control
- </li>
- <li>
- You retain all intellectual property rights to your
- content
- </li>
- <li>
- You can export and use your content however you choose
- </li>
- <li>
- No watermarks, no licensing restrictions from OpenCut
- </li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- How You Can Use OpenCut
- </h2>
- <p className="mb-4">
- OpenCut is free for personal and commercial use. You can:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- Create videos for personal, educational, or commercial
- purposes
- </li>
- <li>Use OpenCut for client work and paid projects</li>
- <li>Share and distribute videos created with OpenCut</li>
- <li>
- Modify and distribute the OpenCut software (under MIT
- license)
- </li>
- </ul>
- <p>
- <strong>What we ask:</strong> Don't use OpenCut for illegal
- activities, harassment, or creating harmful content. Be
- respectful of others and follow applicable laws.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Account and Service
- </h2>
- <p className="mb-4">
- To use certain features, you may create an account:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>Provide accurate information when signing up</li>
- <li>
- Keep your account secure and don't share credentials
- </li>
- <li>You're responsible for activity under your account</li>
- <li>You can delete your account at any time</li>
- </ul>
- <p>
- OpenCut is provided "as is" without warranties. While we
- strive for reliability, we can't guarantee uninterrupted
- service.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Open Source Benefits
- </h2>
- <p className="mb-4">
- Because OpenCut is open source, you have additional rights:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- Review our code to see exactly how we handle your data
- </li>
- <li>Self-host OpenCut on your own servers</li>
- <li>Modify the software to suit your needs</li>
- <li>Contribute improvements back to the community</li>
- </ul>
- <p>
- View our source code and license on{" "}
- <a
- href="https://github.com/OpenCut-app/OpenCut"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- GitHub
- </a>
- .
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Third-Party Content
- </h2>
- <p className="mb-4">
- When using OpenCut, make sure you have the right to use any
- content you import:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- Only upload content you own or have permission to use
- </li>
- <li>
- Respect copyright, trademarks, and other intellectual
- property
- </li>
- <li>
- Don't use copyrighted music, images, or videos without
- permission
- </li>
- <li>
- You're responsible for any claims related to your content
- </li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Limitations and Liability
- </h2>
- <p className="mb-4">
- OpenCut is provided free of charge. To the extent permitted
- by law:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>We're not liable for any loss of data or content</li>
- <li>
- Projects are stored in your browser and may be lost if you
- clear browser data
- </li>
- <li>We're not responsible for how you use the service</li>
- <li>
- Our liability is limited to the maximum extent allowed by
- law
- </li>
- </ul>
- <p>
- Since your content stays on your device, we have no way to
- recover lost projects. Consider exporting important videos
- when finished editing.
- </p>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Service Changes
- </h2>
- <p className="mb-4">We may update OpenCut and these terms:</p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>
- We'll notify you of significant changes to these terms
- </li>
- <li>Continued use means you accept any updates</li>
- <li>
- You can always self-host an older version if you prefer
- </li>
- <li>
- Major changes will be discussed with the community on
- GitHub
- </li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">Termination</h2>
- <p className="mb-4">
- You can stop using OpenCut at any time:
- </p>
- <ul className="list-disc pl-6 mb-4 space-y-2">
- <li>Delete your account through your profile settings</li>
- <li>Clear your browser data to remove local projects</li>
- <li>
- Your content remains yours even if you stop using OpenCut
- </li>
- <li>
- We may suspend accounts for violations of these terms
- </li>
- </ul>
- </section>
-
- <section>
- <h2 className="text-2xl font-semibold mb-4">
- Contact and Disputes
- </h2>
- <p className="mb-4">
- Questions about these terms or need to report an issue?
- </p>
- <p className="mb-4">
- Contact us through our{" "}
- <a
- href="https://github.com/OpenCut-app/OpenCut/issues"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- GitHub repository
- </a>
- , email us at{" "}
- <a
- href="mailto:oss@opencut.app"
- className="text-primary hover:underline"
- >
- oss@opencut.app
- </a>
- , or reach out on{" "}
- <a
- href="https://x.com/opencutapp"
- target="_blank"
- rel="noopener"
- className="text-primary hover:underline"
- >
- X (Twitter)
- </a>
- .
- </p>
- <p>
- These terms are governed by applicable law in your
- jurisdiction. We prefer to resolve disputes through friendly
- discussion in our open-source community.
- </p>
- </section>
-
- <p className="text-sm text-muted-foreground mt-8 pt-8 border-t border-muted/20">
- Last updated: July 14, 2025
- </p>
- </CardContent>
- </Card>
- </div>
- </div>
- </main>
- <Footer />
- </div>
- );
-}
+import { Metadata } from "next"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { GithubIcon } from "@/components/icons"; +import Link from "next/link"; +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; + +export const metadata: Metadata = { + title: "Terms of Service - OpenCut", + description: + "OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.", + openGraph: { + title: "Terms of Service - OpenCut", + description: + "OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.", + type: "website", + }, +}; + +export default function TermsPage() { + return ( + <div className="min-h-screen bg-background"> + <Header /> + <main className="relative"> + <div className="absolute inset-0 overflow-hidden pointer-events-none"> + <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" /> + <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" /> + </div> + <div className="relative container mx-auto px-4 py-16"> + <div className="max-w-4xl mx-auto"> + <div className="text-center mb-10"> + <Link + href="https://github.com/OpenCut-app/OpenCut" + target="_blank" + > + <Badge variant="secondary" className="gap-2 mb-6"> + <GithubIcon className="h-3 w-3" /> + Open Source + </Badge> + </Link> + <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6"> + Terms of Service + </h1> + <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed"> + Fair and transparent terms for our free, open-source video + editor. + </p> + </div> + <Card className="bg-background/80 backdrop-blur-sm border-2 border-muted/30"> + <CardContent className="p-8 text-base leading-relaxed space-y-8"> + <section> + <h2 className="text-2xl font-semibold mb-4"> + Welcome to OpenCut + </h2> + <p className="mb-4"> + OpenCut is a free, open-source video editor that runs in + your browser. By using our service, you agree to these + terms. We've designed these terms to be fair and protect + both you and our project. + </p> + <p> + <strong>Key principle:</strong> Your content stays on your + device. We never claim ownership of your videos or projects. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Your Content, Your Rights + </h2> + <p className="mb-4"> + <strong>You own everything you create.</strong> OpenCut + processes your videos locally on your device, so we never + have access to your content. We make no claims to ownership, + licensing, or rights over your videos, projects, or any + content you create using OpenCut. + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + Your videos remain completely private and under your + control + </li> + <li> + You retain all intellectual property rights to your + content + </li> + <li> + You can export and use your content however you choose + </li> + <li> + No watermarks, no licensing restrictions from OpenCut + </li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + How You Can Use OpenCut + </h2> + <p className="mb-4"> + OpenCut is free for personal and commercial use. You can: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + Create videos for personal, educational, or commercial + purposes + </li> + <li>Use OpenCut for client work and paid projects</li> + <li>Share and distribute videos created with OpenCut</li> + <li> + Modify and distribute the OpenCut software (under MIT + license) + </li> + </ul> + <p> + <strong>What we ask:</strong> Don't use OpenCut for illegal + activities, harassment, or creating harmful content. Be + respectful of others and follow applicable laws. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Account and Service + </h2> + <p className="mb-4"> + To use certain features, you may create an account: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li>Provide accurate information when signing up</li> + <li> + Keep your account secure and don't share credentials + </li> + <li>You're responsible for activity under your account</li> + <li>You can delete your account at any time</li> + </ul> + <p> + OpenCut is provided "as is" without warranties. While we + strive for reliability, we can't guarantee uninterrupted + service. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Open Source Benefits + </h2> + <p className="mb-4"> + Because OpenCut is open source, you have additional rights: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + Review our code to see exactly how we handle your data + </li> + <li>Self-host OpenCut on your own servers</li> + <li>Modify the software to suit your needs</li> + <li>Contribute improvements back to the community</li> + </ul> + <p> + View our source code and license on{" "} + <a + href="https://github.com/OpenCut-app/OpenCut" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + GitHub + </a> + . + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Third-Party Content + </h2> + <p className="mb-4"> + When using OpenCut, make sure you have the right to use any + content you import: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + Only upload content you own or have permission to use + </li> + <li> + Respect copyright, trademarks, and other intellectual + property + </li> + <li> + Don't use copyrighted music, images, or videos without + permission + </li> + <li> + You're responsible for any claims related to your content + </li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Limitations and Liability + </h2> + <p className="mb-4"> + OpenCut is provided free of charge. To the extent permitted + by law: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li>We're not liable for any loss of data or content</li> + <li> + Projects are stored in your browser and may be lost if you + clear browser data + </li> + <li>We're not responsible for how you use the service</li> + <li> + Our liability is limited to the maximum extent allowed by + law + </li> + </ul> + <p> + Since your content stays on your device, we have no way to + recover lost projects. Consider exporting important videos + when finished editing. + </p> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Service Changes + </h2> + <p className="mb-4">We may update OpenCut and these terms:</p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li> + We'll notify you of significant changes to these terms + </li> + <li>Continued use means you accept any updates</li> + <li> + You can always self-host an older version if you prefer + </li> + <li> + Major changes will be discussed with the community on + GitHub + </li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4">Termination</h2> + <p className="mb-4"> + You can stop using OpenCut at any time: + </p> + <ul className="list-disc pl-6 mb-4 space-y-2"> + <li>Delete your account through your profile settings</li> + <li>Clear your browser data to remove local projects</li> + <li> + Your content remains yours even if you stop using OpenCut + </li> + <li> + We may suspend accounts for violations of these terms + </li> + </ul> + </section> + + <section> + <h2 className="text-2xl font-semibold mb-4"> + Contact and Disputes + </h2> + <p className="mb-4"> + Questions about these terms or need to report an issue? + </p> + <p className="mb-4"> + Contact us through our{" "} + <a + href="https://github.com/OpenCut-app/OpenCut/issues" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + GitHub repository + </a> + , email us at{" "} + <a + href="mailto:oss@opencut.app" + className="text-primary hover:underline" + > + oss@opencut.app + </a> + , or reach out on{" "} + <a + href="https://x.com/opencutapp" + target="_blank" + rel="noopener" + className="text-primary hover:underline" + > + X (Twitter) + </a> + . + </p> + <p> + These terms are governed by applicable law in your + jurisdiction. We prefer to resolve disputes through friendly + discussion in our open-source community. + </p> + </section> + + <p className="text-sm text-muted-foreground mt-8 pt-8 border-t border-muted/20"> + Last updated: July 14, 2025 + </p> + </CardContent> + </Card> + </div> + </div> + </main> + <Footer /> + </div> + ); +} diff --git a/apps/web/src/app/why-not-capcut/page.tsx b/apps/web/src/app/why-not-capcut/page.tsx index 56b3d27a..7fe6df66 100644 --- a/apps/web/src/app/why-not-capcut/page.tsx +++ b/apps/web/src/app/why-not-capcut/page.tsx @@ -1,191 +1,191 @@ -import { Header } from "@/components/header";
-
-export default function WhyNotCapcut() {
- return (
- <div className="min-h-screen bg-background px-5">
- <Header />
-
- <main className="relative mt-12">
- <div className="absolute inset-0 overflow-hidden pointer-events-none">
- <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
- <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
- </div>
-
- <div className="relative container mx-auto px-4 py-16">
- <div className="max-w-6xl mx-auto">
- <div className="text-center mb-20">
- <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
- Fuck CapCut
- </h1>
- <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
- Roasting time, so get ready motherfucker.
- </p>
- </div>
-
- <div className="max-w-4xl mx-auto space-y-12">
- <div>
- <h2 className="text-3xl font-bold mb-6">
- Seriously, what the fuck else do you want?
- </h2>
- <p className="text-lg mb-6">
- You probably use CapCut and think your video editing is
- special. You think your fucking TikTok with 47 transitions and
- 12 different fonts is going to get you some viral fame. You
- think loading up every goddamn effect in their library makes
- your content better. Wrong, motherfucker. Let me describe what
- CapCut actually gives you:
- </p>
- <ul className="text-lg space-y-2 mb-6 list-disc list-inside">
- <li>A paywall every time you breathe</li>
- <li>Terms of service that steal your shit</li>
- <li>
- More "Get Pro" dialogs than a Windows 95 error message
- </li>
- <li>
- Features that disappear behind paywalls while you're fucking
- using them
- </li>
- <li>Bugs disguised as "premium features"</li>
- </ul>
- <p className="text-lg mb-6">
- <strong>Well guess what, motherfucker:</strong>
- </p>
- <p className="text-lg mb-6">
- You. Are. Getting. Scammed. Look at this shit. It's a fucking
- video editor. Why the fuck do you need to pay $20/month just
- to remove a goddamn watermark? You spent hours editing your
- video and they slap their logo on it like they fucking made
- it.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- The "Get Pro" dialog is everywhere
- </h2>
- <p className="text-lg mb-6">
- This motherfucking dialog pops up more than ads on a pirated
- movie site. Want to add a transition? Get Pro. Want to export
- without their watermark? Get Pro. Want to use more than 2
- fonts? Get fucking Pro, peasant.
- </p>
- <p className="text-lg mb-6">
- Did you seriously think you could edit a video without seeing
- this dialog 47 times? You click one button and BAM - there it
- is again, asking for your credit card like a desperate ex
- asking for money.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- Everything costs money now
- </h2>
- <p className="text-lg mb-6">
- You dumbass. You thought CapCut was free, but no. Free means
- they let you open the app. Everything else costs money. Basic
- shake effect? That'll be $20/month. A decent transition that isn't
- "fade"? Pay up, motherfucker.
- </p>
- <p className="text-lg mb-6">
- Here's my favorite piece of bullshit: You import an MP3 file -
- you know, AUDIO - and try to export. "Sorry, can't export
- because you're using our premium extract audio feature!"
- </p>
- <p className="text-lg mb-6">
- <strong>
- My MP3 was already fucking audio, you absolute morons.
- </strong>
- </p>
- <p className="text-lg mb-6">
- But wait, there's more! If you drag that same MP3 to their
- media panel first, then to the timeline, it magically works.
- This isn't a bug, it's a fucking scam disguised as software
- engineering.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- Their Terms of Service are insane
- </h2>
- <p className="text-lg mb-6">
- Look at this shit. You upload your content and they basically
- say "thanks for the free content, we own it now, but if Disney
- sues anyone, that's your problem."
- </p>
- <p className="text-lg mb-6">
- <strong>CapCut's Terms of Service:</strong> We get full rights
- to use, modify, distribute, and monetize everything you upload
- - permanently and without paying you shit. But you're still
- responsible if anything goes wrong.
- </p>
- <p className="text-lg mb-6">
- Translation: "We'll make money off your viral video, you
- handle the lawsuits." Brilliant legal strategy, you fucks.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- The editor is actually good
- </h2>
- <p className="text-lg mb-6">
- Here's the thing that makes me want to punch my monitor: the
- actual video editor is fucking good. It's intuitive, powerful,
- and anyone can figure it out. When it's not begging for money
- every 30 seconds, it actually works well.
- </p>
- <p className="text-lg mb-6">
- Which makes everything else so much worse. They built
- something people want to use, then turned it into a digital
- slot machine. Every click might trigger a payment request.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- This is a video editor. Look at it. You've never seen one
- before.
- </h2>
- <p className="text-lg mb-6">
- Like the person who's never used software that doesn't
- constantly beg for money, you have no fucking idea what a
- video editor should be. All you've ever seen are predatory
- apps disguised as creative tools.
- </p>
- <p className="text-lg mb-6">
- A real video editor lets you edit videos. It doesn't steal
- your content. It doesn't pop up payment dialogs every 5
- seconds. It doesn't charge you separately for basic features
- that should be free.
- </p>
- </div>
-
- <div>
- <h2 className="text-3xl font-bold mb-6">
- Yes, this is fucking satire, you fuck
- </h2>
- <p className="text-lg mb-6">
- I'm not actually saying all video editors should be basic as
- shit. What I'm saying is that all the problems we have with
- video editing apps are{" "}
- <strong>ones they create themselves</strong>. Video editors
- aren't broken by default - they edit videos, export them, and
- let you use basic features without constantly begging for
- money. CapCut breaks them. They turn them into payment
- processors with video editing as a side feature.
- </p>
- <p className="text-lg">
- <em>"Good software gets out of your way."</em>
- <br />- Some smart motherfucker who definitely wasn't working
- at CapCut
- </p>
- </div>
- </div>
- </div>
- </div>
- </main>
- </div>
- );
-}
+import { Header } from "@/components/header"; + +export default function WhyNotCapcut() { + return ( + <div className="min-h-screen bg-background px-5"> + <Header /> + + <main className="relative mt-12"> + <div className="absolute inset-0 overflow-hidden pointer-events-none"> + <div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" /> + <div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" /> + </div> + + <div className="relative container mx-auto px-4 py-16"> + <div className="max-w-6xl mx-auto"> + <div className="text-center mb-20"> + <h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6"> + Fuck CapCut + </h1> + <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed"> + Roasting time, so get ready motherfucker. + </p> + </div> + + <div className="max-w-4xl mx-auto space-y-12"> + <div> + <h2 className="text-3xl font-bold mb-6"> + Seriously, what the fuck else do you want? + </h2> + <p className="text-lg mb-6"> + You probably use CapCut and think your video editing is + special. You think your fucking TikTok with 47 transitions and + 12 different fonts is going to get you some viral fame. You + think loading up every goddamn effect in their library makes + your content better. Wrong, motherfucker. Let me describe what + CapCut actually gives you: + </p> + <ul className="text-lg space-y-2 mb-6 list-disc list-inside"> + <li>A paywall every time you breathe</li> + <li>Terms of service that steal your shit</li> + <li> + More "Get Pro" dialogs than a Windows 95 error message + </li> + <li> + Features that disappear behind paywalls while you're fucking + using them + </li> + <li>Bugs disguised as "premium features"</li> + </ul> + <p className="text-lg mb-6"> + <strong>Well guess what, motherfucker:</strong> + </p> + <p className="text-lg mb-6"> + You. Are. Getting. Scammed. Look at this shit. It's a fucking + video editor. Why the fuck do you need to pay $20/month just + to remove a goddamn watermark? You spent hours editing your + video and they slap their logo on it like they fucking made + it. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + The "Get Pro" dialog is everywhere + </h2> + <p className="text-lg mb-6"> + This motherfucking dialog pops up more than ads on a pirated + movie site. Want to add a transition? Get Pro. Want to export + without their watermark? Get Pro. Want to use more than 2 + fonts? Get fucking Pro, peasant. + </p> + <p className="text-lg mb-6"> + Did you seriously think you could edit a video without seeing + this dialog 47 times? You click one button and BAM - there it + is again, asking for your credit card like a desperate ex + asking for money. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + Everything costs money now + </h2> + <p className="text-lg mb-6"> + You dumbass. You thought CapCut was free, but no. Free means + they let you open the app. Everything else costs money. Basic + shake effect? That'll be $20/month. A decent transition that + isn't "fade"? Pay up, motherfucker. + </p> + <p className="text-lg mb-6"> + Here's my favorite piece of bullshit: You import an MP3 file - + you know, AUDIO - and try to export. "Sorry, can't export + because you're using our premium extract audio feature!" + </p> + <p className="text-lg mb-6"> + <strong> + My MP3 was already fucking audio, you absolute morons. + </strong> + </p> + <p className="text-lg mb-6"> + But wait, there's more! If you drag that same MP3 to their + media panel first, then to the timeline, it magically works. + This isn't a bug, it's a fucking scam disguised as software + engineering. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + Their Terms of Service are insane + </h2> + <p className="text-lg mb-6"> + Look at this shit. You upload your content and they basically + say "thanks for the free content, we own it now, but if Disney + sues anyone, that's your problem." + </p> + <p className="text-lg mb-6"> + <strong>CapCut's Terms of Service:</strong> We get full rights + to use, modify, distribute, and monetize everything you upload + - permanently and without paying you shit. But you're still + responsible if anything goes wrong. + </p> + <p className="text-lg mb-6"> + Translation: "We'll make money off your viral video, you + handle the lawsuits." Brilliant legal strategy, you fucks. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + The editor is actually good + </h2> + <p className="text-lg mb-6"> + Here's the thing that makes me want to punch my monitor: the + actual video editor is fucking good. It's intuitive, powerful, + and anyone can figure it out. When it's not begging for money + every 30 seconds, it actually works well. + </p> + <p className="text-lg mb-6"> + Which makes everything else so much worse. They built + something people want to use, then turned it into a digital + slot machine. Every click might trigger a payment request. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + This is a video editor. Look at it. You've never seen one + before. + </h2> + <p className="text-lg mb-6"> + Like the person who's never used software that doesn't + constantly beg for money, you have no fucking idea what a + video editor should be. All you've ever seen are predatory + apps disguised as creative tools. + </p> + <p className="text-lg mb-6"> + A real video editor lets you edit videos. It doesn't steal + your content. It doesn't pop up payment dialogs every 5 + seconds. It doesn't charge you separately for basic features + that should be free. + </p> + </div> + + <div> + <h2 className="text-3xl font-bold mb-6"> + Yes, this is fucking satire, you fuck + </h2> + <p className="text-lg mb-6"> + I'm not actually saying all video editors should be basic as + shit. What I'm saying is that all the problems we have with + video editing apps are{" "} + <strong>ones they create themselves</strong>. Video editors + aren't broken by default - they edit videos, export them, and + let you use basic features without constantly begging for + money. CapCut breaks them. They turn them into payment + processors with video editing as a side feature. + </p> + <p className="text-lg"> + <em>"Good software gets out of your way."</em> + <br />- Some smart motherfucker who definitely wasn't working + at CapCut + </p> + </div> + </div> + </div> + </div> + </main> + </div> + ); +} |
