mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: add ultracite (#452)
* Init ultracite * Update scripts and biome.jsonc * Update biome.jsonc * Update biome.jsonc * Update biome.jsonc * Run format
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { auth } from "@opencut/auth";
|
||||
import { toNextJsHandler } from "better-auth/next-js";
|
||||
|
||||
export const { POST, GET } = toNextJsHandler(auth);
|
||||
import { auth } from "@opencut/auth";
|
||||
import { toNextJsHandler } from "better-auth/next-js";
|
||||
|
||||
export const { POST, GET } = toNextJsHandler(auth);
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`, {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
+253
-253
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
+247
-247
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { Feed } from 'feed';
|
||||
import { getPosts } from '@/lib/blog-query';
|
||||
import { SITE_INFO, SITE_URL } from '@/constants/site';
|
||||
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: `${SITE_INFO.title} Blog`,
|
||||
description: SITE_INFO.description,
|
||||
id: `${SITE_URL}`,
|
||||
link: `${SITE_URL}/blog/`,
|
||||
language: 'en',
|
||||
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_URL}/blog/${post.slug}`,
|
||||
link: `${SITE_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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+317
-317
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user