mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
Merge branch 'OpenCut-app:main' into refactor/move-add-button
This commit is contained in:
@@ -0,0 +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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ export function TimelineTrackContent({
|
|||||||
const { mediaItems } = useMediaStore();
|
const { mediaItems } = useMediaStore();
|
||||||
const {
|
const {
|
||||||
tracks,
|
tracks,
|
||||||
|
addTrack,
|
||||||
moveElementToTrack,
|
moveElementToTrack,
|
||||||
updateElementStartTime,
|
updateElementStartTime,
|
||||||
addElementToTrack,
|
addElementToTrack,
|
||||||
@@ -495,6 +496,16 @@ export function TimelineTrackContent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
message: "Drop event started in timeline track",
|
||||||
|
dataTransferTypes: Array.from(e.dataTransfer.types),
|
||||||
|
trackId: track.id,
|
||||||
|
trackType: track.type,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Reset all drag states
|
// Reset all drag states
|
||||||
dragCounterRef.current = 0;
|
dragCounterRef.current = 0;
|
||||||
setIsDropping(false);
|
setIsDropping(false);
|
||||||
@@ -727,68 +738,28 @@ export function TimelineTrackContent({
|
|||||||
|
|
||||||
// Handle position-aware track creation for media elements
|
// Handle position-aware track creation for media elements
|
||||||
if (!isCompatible || dropPosition !== "on") {
|
if (!isCompatible || dropPosition !== "on") {
|
||||||
const needsNewTrack = !isCompatible || dropPosition !== "on";
|
if (isVideoOrImage) {
|
||||||
|
// For video/image, check if we need a main track or additional media track
|
||||||
|
const mainTrack = getMainTrack(tracks);
|
||||||
|
|
||||||
if (needsNewTrack) {
|
if (!mainTrack) {
|
||||||
if (isVideoOrImage) {
|
// No main track exists, create it
|
||||||
// For video/image, check if we need a main track or additional media track
|
targetTrackId = addTrack("media");
|
||||||
const mainTrack = getMainTrack(tracks);
|
const updatedTracks = useTimelineStore.getState().tracks;
|
||||||
|
const newTargetTrack = updatedTracks.find(
|
||||||
if (!mainTrack) {
|
(t) => t.id === targetTrackId
|
||||||
// No main track exists, create it
|
);
|
||||||
const updatedTracks = ensureMainTrack(tracks);
|
if (!newTargetTrack) return;
|
||||||
const newMainTrack = getMainTrack(updatedTracks);
|
targetTrack = newTargetTrack;
|
||||||
if (newMainTrack && newMainTrack.elements.length === 0) {
|
} else if (
|
||||||
targetTrackId = newMainTrack.id;
|
mainTrack.elements.length === 0 &&
|
||||||
targetTrack = newMainTrack;
|
dropPosition === "on"
|
||||||
} else {
|
) {
|
||||||
// Main track was created but somehow has elements, create new media track
|
// Main track exists and is empty, use it
|
||||||
const mainTrackIndex = updatedTracks.findIndex(
|
targetTrackId = mainTrack.id;
|
||||||
(t) => t.id === newMainTrack?.id
|
targetTrack = mainTrack;
|
||||||
);
|
} else {
|
||||||
targetTrackId = insertTrackAt("media", mainTrackIndex);
|
// Create new media track
|
||||||
const updatedTracksAfterInsert =
|
|
||||||
useTimelineStore.getState().tracks;
|
|
||||||
const newTargetTrack = updatedTracksAfterInsert.find(
|
|
||||||
(t) => t.id === targetTrackId
|
|
||||||
);
|
|
||||||
if (!newTargetTrack) return;
|
|
||||||
targetTrack = newTargetTrack;
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
mainTrack.elements.length === 0 &&
|
|
||||||
dropPosition === "on"
|
|
||||||
) {
|
|
||||||
// Main track exists and is empty, use it
|
|
||||||
targetTrackId = mainTrack.id;
|
|
||||||
targetTrack = mainTrack;
|
|
||||||
} else {
|
|
||||||
// Create new media track above main track
|
|
||||||
const mainTrackIndex = tracks.findIndex(
|
|
||||||
(t) => t.id === mainTrack.id
|
|
||||||
);
|
|
||||||
let insertIndex: number;
|
|
||||||
|
|
||||||
if (dropPosition === "above") {
|
|
||||||
insertIndex = currentTrackIndex;
|
|
||||||
} else if (dropPosition === "below") {
|
|
||||||
insertIndex = currentTrackIndex + 1;
|
|
||||||
} else {
|
|
||||||
// Insert above main track
|
|
||||||
insertIndex = mainTrackIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetTrackId = insertTrackAt("media", insertIndex);
|
|
||||||
const updatedTracks = useTimelineStore.getState().tracks;
|
|
||||||
const newTargetTrack = updatedTracks.find(
|
|
||||||
(t) => t.id === targetTrackId
|
|
||||||
);
|
|
||||||
if (!newTargetTrack) return;
|
|
||||||
targetTrack = newTargetTrack;
|
|
||||||
}
|
|
||||||
} else if (isAudio) {
|
|
||||||
// Audio tracks go at the bottom
|
|
||||||
const mainTrack = getMainTrack(tracks);
|
|
||||||
let insertIndex: number;
|
let insertIndex: number;
|
||||||
|
|
||||||
if (dropPosition === "above") {
|
if (dropPosition === "above") {
|
||||||
@@ -796,18 +767,14 @@ export function TimelineTrackContent({
|
|||||||
} else if (dropPosition === "below") {
|
} else if (dropPosition === "below") {
|
||||||
insertIndex = currentTrackIndex + 1;
|
insertIndex = currentTrackIndex + 1;
|
||||||
} else {
|
} else {
|
||||||
// Insert after main track (bottom area)
|
// Insert above main track
|
||||||
if (mainTrack) {
|
const mainTrackIndex = tracks.findIndex(
|
||||||
const mainTrackIndex = tracks.findIndex(
|
(t) => t.id === mainTrack.id
|
||||||
(t) => t.id === mainTrack.id
|
);
|
||||||
);
|
insertIndex = mainTrackIndex;
|
||||||
insertIndex = mainTrackIndex + 1;
|
|
||||||
} else {
|
|
||||||
insertIndex = tracks.length; // Bottom of timeline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
targetTrackId = insertTrackAt("audio", insertIndex);
|
targetTrackId = insertTrackAt("media", insertIndex);
|
||||||
const updatedTracks = useTimelineStore.getState().tracks;
|
const updatedTracks = useTimelineStore.getState().tracks;
|
||||||
const newTargetTrack = updatedTracks.find(
|
const newTargetTrack = updatedTracks.find(
|
||||||
(t) => t.id === targetTrackId
|
(t) => t.id === targetTrackId
|
||||||
@@ -815,6 +782,34 @@ export function TimelineTrackContent({
|
|||||||
if (!newTargetTrack) return;
|
if (!newTargetTrack) return;
|
||||||
targetTrack = newTargetTrack;
|
targetTrack = newTargetTrack;
|
||||||
}
|
}
|
||||||
|
} else if (isAudio) {
|
||||||
|
// Audio tracks go at the bottom
|
||||||
|
const mainTrack = getMainTrack(tracks);
|
||||||
|
let insertIndex: number;
|
||||||
|
|
||||||
|
if (dropPosition === "above") {
|
||||||
|
insertIndex = currentTrackIndex;
|
||||||
|
} else if (dropPosition === "below") {
|
||||||
|
insertIndex = currentTrackIndex + 1;
|
||||||
|
} else {
|
||||||
|
// Insert after main track (bottom area)
|
||||||
|
if (mainTrack) {
|
||||||
|
const mainTrackIndex = tracks.findIndex(
|
||||||
|
(t) => t.id === mainTrack.id
|
||||||
|
);
|
||||||
|
insertIndex = mainTrackIndex + 1;
|
||||||
|
} else {
|
||||||
|
insertIndex = tracks.length; // Bottom of timeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetTrackId = insertTrackAt("audio", insertIndex);
|
||||||
|
const updatedTracks = useTimelineStore.getState().tracks;
|
||||||
|
const newTargetTrack = updatedTracks.find(
|
||||||
|
(t) => t.id === targetTrackId
|
||||||
|
);
|
||||||
|
if (!newTargetTrack) return;
|
||||||
|
targetTrack = newTargetTrack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Button } from "../ui/button";
|
|||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Handlebars } from "./handlebars";
|
import { Handlebars } from "./handlebars";
|
||||||
@@ -17,16 +17,13 @@ interface HeroProps {
|
|||||||
export function Hero({ signupCount }: HeroProps) {
|
export function Hero({ signupCount }: HeroProps) {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!email.trim()) {
|
if (!email.trim()) {
|
||||||
toast({
|
toast.error("Email required", {
|
||||||
title: "Email required",
|
|
||||||
description: "Please enter your email address.",
|
description: "Please enter your email address.",
|
||||||
variant: "destructive",
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -45,25 +42,20 @@ export function Hero({ signupCount }: HeroProps) {
|
|||||||
const data = (await response.json()) as { error: string };
|
const data = (await response.json()) as { error: string };
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast({
|
toast.success("Welcome to the waitlist! 🎉", {
|
||||||
title: "Welcome to the waitlist! 🎉",
|
|
||||||
description: "You'll be notified when we launch.",
|
description: "You'll be notified when we launch.",
|
||||||
});
|
});
|
||||||
setEmail("");
|
setEmail("");
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast.error("Oops!", {
|
||||||
title: "Oops!",
|
|
||||||
description:
|
description:
|
||||||
(data as { error: string }).error ||
|
(data as { error: string }).error ||
|
||||||
"Something went wrong. Please try again.",
|
"Something went wrong. Please try again.",
|
||||||
variant: "destructive",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast.error("Network error", {
|
||||||
title: "Network error",
|
|
||||||
description: "Please check your connection and try again.",
|
description: "Please check your connection and try again.",
|
||||||
variant: "destructive",
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user