feat: add theme system with swappable palettes and rebrand to NeuralCut

- Add multi-palette theme system using CSS custom properties via data-palette attribute
- Create NeuralCut palette (rose/pink accent #F55385, dark charcoal #1B212B) as default
- Preserve Classic (OpenCut blue) palette as selectable alternative
- Add PaletteSelector component with dark/light toggle and palette picker
- Migrate export button hardcoded colors to theme tokens
- Rebrand all user-visible OpenCut references to NeuralCut across UI
- Update package names and turbo filters for new identity
This commit is contained in:
Luis Esteban Acevedo Ladino 2026-04-29 12:57:27 -05:00
parent 9750ed0ed0
commit 92caa27d7c
38 changed files with 450 additions and 132 deletions

View File

@ -1,5 +1,5 @@
{
"name": "@opencut/web",
"name": "@neuralcut/web",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.2.18",

View File

@ -1,5 +1,5 @@
{
"name": "OpenCut",
"name": "NeuralCut",
"description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone",
"start_url": "/",

View File

@ -6,13 +6,13 @@ import { getPosts } from "@/lib/blog/query";
import type { Post } from "@/lib/blog/types";
export const metadata: Metadata = {
title: "Blog - OpenCut",
title: "Blog - NeuralCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
"Read the latest news and updates about NeuralCut, the free and open-source video editor.",
openGraph: {
title: "Blog - OpenCut",
title: "Blog - NeuralCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
"Read the latest news and updates about NeuralCut, the free and open-source video editor.",
type: "website",
},
};
@ -24,7 +24,7 @@ export default async function BlogPage() {
return (
<BasePage
title="Blog"
description="Read the latest news and updates about OpenCut, the free and open-source video editor."
description="Read the latest news and updates about NeuralCut, the free and open-source video editor."
>
<div className="flex flex-col">
{data.posts.map((post) => (

View File

@ -48,7 +48,7 @@ const ASSET_SECTIONS: AssetSection[] = [
{
title: "Symbol",
description:
"Use the symbol on its own when the OpenCut name is already present nearby or space is limited.",
"Use the symbol on its own when the NeuralCut name is already present nearby or space is limited.",
cols: "2",
assets: [
{
@ -112,7 +112,7 @@ export default function BrandPage() {
title="Brand"
description={
<>
Download OpenCut brand assets for use in your projects.{" "}
Download NeuralCut brand assets for use in your projects.{" "}
<Link
href="#guidelines"
className="underline underline-offset-4"
@ -173,10 +173,10 @@ export default function BrandPage() {
<div className="flex flex-col gap-3">
<h2 className="font-semibold text-lg">Usage</h2>
<p className="text-muted-foreground text-base leading-relaxed">
OpenCut is open source the code is free to use under its license.
NeuralCut is open source the code is free to use under its license.
That license does not cover the name or logo. You can say you use
OpenCut, that your project integrates with OpenCut, or that it was
built on top of OpenCut. You cannot name your product OpenCut, imply
NeuralCut, that your project integrates with NeuralCut, or that it was
built on top of NeuralCut. You cannot name your product NeuralCut, imply
we made or endorse your product, or use the marks commercially
without asking first. For anything unclear, reach out at{" "}
<Link
@ -193,8 +193,8 @@ export default function BrandPage() {
<h2 className="font-semibold text-lg">What's not allowed</h2>
<ul className="text-muted-foreground text-base flex flex-col gap-2 leading-relaxed">
{[
"Using OpenCut in the name of your product, service, or domain.",
"Implying that OpenCut made, sponsors, or endorses your work.",
"Using NeuralCut in the name of your product, service, or domain.",
"Implying that NeuralCut made, sponsors, or endorses your work.",
"Using the logo or name on merchandise or commercial marketing.",
"Modifying the marks.",
].map((item) => (

View File

@ -24,7 +24,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
const release = getReleaseByVersion({ version });
if (!release) return {};
return {
title: `${release.title} (${release.version}) - OpenCut Changelog`,
title: `${release.title} (${release.version}) - NeuralCut Changelog`,
description: release.description,
};
}

View File

@ -14,25 +14,25 @@ import {
} from "@/lib/changelog/components/release";
export const metadata: Metadata = {
title: "Changelog - OpenCut",
description: "What's new in OpenCut",
title: "Changelog - NeuralCut",
description: "What's new in NeuralCut",
openGraph: {
title: "Changelog - OpenCut",
description: "Every update, improvement, and fix to OpenCut — documented.",
title: "Changelog - NeuralCut",
description: "Every update, improvement, and fix to NeuralCut — documented.",
type: "website",
images: [
{
url: "/open-graph/changlog.jpg",
width: 1200,
height: 630,
alt: "OpenCut Changelog",
alt: "NeuralCut Changelog",
},
],
},
twitter: {
card: "summary_large_image",
title: "Changelog - OpenCut",
description: "What's new in OpenCut",
title: "Changelog - NeuralCut",
description: "What's new in NeuralCut",
images: ["/open-graph/changlog.jpg"],
},
};
@ -41,7 +41,7 @@ export default function ChangelogPage() {
const releases = getSortedReleases();
return (
<BasePage title="Changelog" description="See what's new in OpenCut">
<BasePage title="Changelog" description="See what's new in NeuralCut">
<div className="mx-auto w-full max-w-3xl">
<div className="relative">
<div

View File

@ -7,13 +7,13 @@ import { EXTERNAL_TOOLS } from "@/lib/site/external-tools";
import { BasePage } from "../base-page";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
title: "Contributors - NeuralCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
"Meet the amazing people who contribute to NeuralCut, the free and open-source video editor.",
openGraph: {
title: "Contributors - OpenCut",
title: "Contributors - NeuralCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
"Meet the amazing people who contribute to NeuralCut, the free and open-source video editor.",
type: "website",
},
};
@ -70,7 +70,7 @@ export default async function ContributorsPage() {
return (
<BasePage
title="Contributors"
description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor."
description="Meet the amazing people who contribute to NeuralCut, the free and open-source video editor."
>
<div className="-mt-4 flex items-center justify-center gap-8 text-sm">
<StatItem value={contributors.length} label="contributors" />
@ -87,7 +87,7 @@ export default async function ContributorsPage() {
<ExternalToolsSection />
<GitHubContributeSection
title="Join the community"
description="OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone."
description="NeuralCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone."
/>
</div>
</BasePage>
@ -169,7 +169,7 @@ function AllContributorsSection({
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">All contributors</h2>
<p className="text-muted-foreground">
Everyone who makes OpenCut better
Everyone who makes NeuralCut better
</p>
</div>
@ -211,7 +211,7 @@ function ExternalToolsSection() {
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">External tools</h2>
<p className="text-muted-foreground">Tools we use to build OpenCut</p>
<p className="text-muted-foreground">Tools we use to build NeuralCut</p>
</div>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2">

View File

@ -55,7 +55,7 @@ function DegradedRendererBanner() {
return (
<div className="bg-accent border-b h-9 flex items-center justify-center gap-2 text-xs text-muted-foreground">
<span>For the best experience, open OpenCut in Chrome.</span>
<span>For the best experience, open NeuralCut in Chrome.</span>
<Button
variant="text"
size="icon"

View File

@ -47,6 +47,54 @@
--sidebar-border: hsl(0 0% 85.1%);
--sidebar-ring: hsl(0 0% 16.9%);
--sidebar: hsl(0 0% 98%);
--export-ring: hsl(199, 89%, 60%);
--export-gradient-from: hsl(215, 85%, 54%);
--export-gradient-to: hsl(199, 91%, 59%);
}
[data-palette="neuralcut"] {
--background: hsl(220, 20%, 97%);
--foreground: hsl(217, 23%, 14%);
--card: hsl(220, 18%, 99%);
--card-foreground: hsl(217, 23%, 14%);
--popover: hsl(220, 18%, 99%);
--popover-hover: hsl(220, 15%, 94%);
--popover-foreground: hsl(217, 23%, 14%);
--primary: hsl(342, 89%, 64%);
--primary-foreground: hsl(0, 0%, 100%);
--secondary: hsl(342, 40%, 95%);
--secondary-border: hsl(342, 40%, 90%);
--secondary-foreground: hsl(342, 80%, 45%);
--muted: hsl(220, 10%, 85%);
--muted-foreground: hsl(220, 10%, 48%);
--accent: hsl(220, 15%, 94%);
--accent-foreground: hsl(217, 23%, 14%);
--destructive: hsl(0, 83%, 50%);
--destructive-foreground: hsl(0, 0%, 100%);
--constructive: hsl(141, 71%, 48%);
--constructive-foreground: hsl(0, 0%, 100%);
--caution: hsl(38, 92%, 50%);
--caution-foreground: hsl(0, 0%, 10%);
--border: hsl(220, 12%, 90%);
--input: hsl(0, 0%, 100%);
--ring: hsl(342, 89%, 64%);
--chart-1: hsl(342 80% 55%);
--chart-2: hsl(217 40% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(308 31% 45%);
--chart-5: hsl(38 92% 55%);
--sidebar-background: hsl(220, 18%, 96%);
--sidebar-foreground: hsl(217, 23%, 14%);
--sidebar-primary: hsl(217, 23%, 14%);
--sidebar-primary-foreground: hsl(220, 12%, 90%);
--sidebar-accent: hsl(220, 10%, 90%);
--sidebar-accent-foreground: hsl(217, 23%, 14%);
--sidebar-border: hsl(220, 10%, 90%);
--sidebar-ring: hsl(217, 23%, 30%);
--sidebar: hsl(220, 20%, 98%);
--export-ring: hsl(342, 70%, 75%);
--export-gradient-from: hsl(342, 89%, 50%);
--export-gradient-to: hsl(342, 80%, 68%);
}
.panel {
@ -97,6 +145,43 @@
--sidebar-border: hsl(0 0% 20%);
--sidebar-ring: hsl(0 0% 83.1%);
--sidebar: hsl(0 0% 6%);
--export-ring: hsl(199, 89%, 60%);
--export-gradient-from: hsl(215, 85%, 54%);
--export-gradient-to: hsl(199, 91%, 59%);
}
.dark[data-palette="neuralcut"] {
--background: hsl(217, 23%, 14%);
--foreground: hsl(220, 20%, 93%);
--card: hsl(217, 20%, 17%);
--card-foreground: hsl(220, 20%, 93%);
--popover: hsl(217, 20%, 15%);
--popover-hover: hsl(217, 18%, 22%);
--popover-foreground: hsl(220, 20%, 95%);
--secondary: hsl(342, 25%, 18%);
--secondary-border: hsl(342, 25%, 23%);
--secondary-foreground: hsl(342, 80%, 72%);
--muted: hsl(217, 15%, 25%);
--muted-foreground: hsl(220, 10%, 55%);
--accent: hsl(217, 18%, 20%);
--accent-foreground: hsl(220, 20%, 93%);
--border: hsl(217, 18%, 22%);
--input: hsl(217, 20%, 16%);
--ring: hsl(342, 89%, 64%);
--caution: hsl(38, 92%, 60%);
--caution-foreground: hsl(0, 0%, 10%);
--sidebar-background: hsl(217, 22%, 11%);
--sidebar-foreground: hsl(220, 20%, 93%);
--sidebar-primary: hsl(342, 89%, 64%);
--sidebar-primary-foreground: hsl(0, 0%, 100%);
--sidebar-accent: hsl(217, 15%, 20%);
--sidebar-accent-foreground: hsl(220, 20%, 93%);
--sidebar-border: hsl(217, 15%, 20%);
--sidebar-ring: hsl(342, 89%, 64%);
--sidebar: hsl(217, 22%, 12%);
--export-ring: hsl(342, 70%, 75%);
--export-gradient-from: hsl(342, 89%, 50%);
--export-gradient-to: hsl(342, 80%, 68%);
}
.dark .panel {
@ -115,6 +200,42 @@
--ring: hsl(0, 0%, 52%);
}
[data-palette="neuralcut"] .panel {
--background: hsl(220, 18%, 95%);
--foreground: hsl(217, 23%, 14%);
--card: hsl(220, 16%, 96%);
--card-foreground: hsl(217, 23%, 14%);
--primary-foreground: hsl(0, 0%, 100%);
--secondary: hsl(342, 35%, 93%);
--secondary-border: hsl(342, 35%, 88%);
--secondary-foreground: hsl(342, 80%, 42%);
--muted: hsl(220, 10%, 83%);
--muted-foreground: hsl(220, 10%, 45%);
--accent: hsl(220, 12%, 92%);
--accent-foreground: hsl(217, 23%, 14%);
--destructive-foreground: hsl(0, 0%, 100%);
--constructive-foreground: hsl(0, 0%, 100%);
--border: hsl(220, 10%, 88%);
--input: hsl(220, 12%, 92%);
--ring: hsl(342, 89%, 64%);
}
.dark[data-palette="neuralcut"] .panel {
--background: hsl(217, 20%, 18%);
--foreground: hsl(220, 18%, 90%);
--card: hsl(217, 18%, 19%);
--card-foreground: hsl(220, 18%, 90%);
--secondary: hsl(342, 30%, 15%);
--secondary-border: hsl(342, 30%, 20%);
--secondary-foreground: hsl(342, 80%, 68%);
--muted: hsl(217, 12%, 28%);
--accent: hsl(217, 15%, 22%);
--accent-foreground: hsl(220, 18%, 90%);
--border: hsl(217, 15%, 24%);
--input: hsl(217, 15%, 24%);
--ring: hsl(342, 89%, 64%);
}
@layer base {
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
@ -217,6 +338,10 @@
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-export-ring: var(--export-ring);
--color-export-gradient-from: var(--export-gradient-from);
--color-export-gradient-to: var(--export-gradient-to);
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;

View File

@ -1,4 +1,3 @@
import { ThemeProvider } from "next-themes";
import Script from "next/script";
import "./globals.css";
import { Toaster } from "../components/ui/sonner";
@ -8,6 +7,7 @@ import { baseMetaData } from "./metadata";
import { BotIdClient } from "botid/client";
import { webEnv } from "@/lib/env/web";
import { Inter } from "next/font/google";
import { NeuralCutThemeProvider } from "@/lib/themes/theme-provider";
const siteFont = Inter({ subsets: ["latin"] });
@ -40,11 +40,7 @@ export default function RootLayout({
)}
</head>
<body className={`${siteFont.className} font-sans antialiased`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
disableTransitionOnChange={true}
>
<NeuralCutThemeProvider>
<TooltipProvider>
<Toaster />
<Script
@ -61,7 +57,7 @@ export default function RootLayout({
/>
{children}
</TooltipProvider>
</ThemeProvider>
</NeuralCutThemeProvider>
</body>
</html>
);

View File

@ -17,7 +17,7 @@ export const baseMetaData: Metadata = {
url: SITE_INFO.openGraphImage,
width: 1200,
height: 630,
alt: "OpenCut Wordmark",
alt: "NeuralCut Wordmark",
},
],
},
@ -25,7 +25,7 @@ export const baseMetaData: Metadata = {
card: "summary_large_image",
title: SITE_INFO.title,
description: SITE_INFO.description,
creator: "@opencutapp",
creator: "@neuralcut",
images: [SITE_INFO.twitterImage],
},
pinterest: {

View File

@ -10,13 +10,13 @@ import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/lib/site/social";
export const metadata: Metadata = {
title: "Privacy Policy - OpenCut",
title: "Privacy Policy - NeuralCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
"Learn how NeuralCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
openGraph: {
title: "Privacy Policy - OpenCut",
title: "Privacy Policy - NeuralCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
"Learn how NeuralCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
type: "website",
},
};
@ -49,7 +49,7 @@ export default function PrivacyPage() {
too - nothing is uploaded
</li>
<li>
OpenCut does not currently require an account or login
NeuralCut does not currently require an account or login
</li>
<li>Project data stays on your device, not our servers</li>
<li>
@ -88,7 +88,7 @@ export default function PrivacyPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Accounts & Authentication</h2>
<p>
OpenCut does not currently offer user accounts, login, or Google
NeuralCut does not currently offer user accounts, login, or Google
sign-in.
</p>
<p>
@ -139,7 +139,7 @@ export default function PrivacyPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Third-Party Services</h2>
<p>OpenCut integrates with these services:</p>
<p>NeuralCut integrates with these services:</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Vercel:</strong> For hosting and content delivery
@ -154,7 +154,7 @@ export default function PrivacyPage() {
<h2 className="text-2xl font-semibold">Your Rights</h2>
<p>You have complete control over your data:</p>
<ul className="list-disc space-y-2 pl-6">
<li>No account is required to use OpenCut today</li>
<li>No account is required to use NeuralCut today</li>
<li>Clear local storage to remove all saved projects</li>
<li>Contact us with any privacy concerns</li>
</ul>
@ -163,7 +163,7 @@ export default function PrivacyPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Open Source Transparency</h2>
<p>
OpenCut is completely open source. You can review our code, see
NeuralCut 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>

View File

@ -51,7 +51,7 @@ const roadmapItems: RoadmapItem[] = [
{
title: "Native app (mobile/desktop)",
description:
"Native OpenCut apps for Mac, Windows, Linux, and iOS/Android.",
"Native NeuralCut apps for Mac, Windows, Linux, and iOS/Android.",
status: {
text: "Not started",
type: "default",
@ -60,28 +60,28 @@ const roadmapItems: RoadmapItem[] = [
];
export const metadata: Metadata = {
title: "Roadmap - OpenCut",
title: "Roadmap - NeuralCut",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
"See what's coming next for NeuralCut - the free, open-source video editor that respects your privacy.",
openGraph: {
title: "OpenCut Roadmap - What's Coming Next",
title: "NeuralCut Roadmap - What's Coming Next",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
"See what's coming next for NeuralCut - 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",
alt: "NeuralCut Roadmap",
},
],
},
twitter: {
card: "summary_large_image",
title: "OpenCut Roadmap - What's Coming Next",
title: "NeuralCut Roadmap - What's Coming Next",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
"See what's coming next for NeuralCut - the free, open-source video editor that respects your privacy.",
images: ["/open-graph/roadmap.jpg"],
},
};
@ -90,7 +90,7 @@ export default function RoadmapPage() {
return (
<BasePage
title="Roadmap"
description={`What's coming next for OpenCut (last updated: ${LAST_UPDATED})`}
description={`What's coming next for NeuralCut (last updated: ${LAST_UPDATED})`}
>
<div className="mx-auto flex max-w-4xl flex-col gap-16">
<div className="flex flex-col gap-6">
@ -100,7 +100,7 @@ export default function RoadmapPage() {
</div>
<GitHubContributeSection
title="Want to help?"
description="OpenCut is open source and built by the community. Every contribution,
description="NeuralCut is open source and built by the community. Every contribution,
no matter how small, helps us build the best free video editor
possible."
/>

View File

@ -9,13 +9,13 @@ import { LinkSquare02Icon } from "@hugeicons/core-free-icons";
import { cn } from "@/utils/ui";
export const metadata: Metadata = {
title: "Sponsors - OpenCut",
title: "Sponsors - NeuralCut",
description:
"Support OpenCut and help us build the future of free and open-source video editing.",
"Support NeuralCut and help us build the future of free and open-source video editing.",
openGraph: {
title: "Sponsors - OpenCut",
title: "Sponsors - NeuralCut",
description:
"Support OpenCut and help us build the future of free and open-source video editing.",
"Support NeuralCut and help us build the future of free and open-source video editing.",
type: "website",
},
};
@ -28,7 +28,7 @@ export default function SponsorsPage() {
Sponsors
</h1>
<p className="text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed text-pretty">
Support OpenCut and help us build the future of privacy-first video
Support NeuralCut and help us build the future of privacy-first video
editing.
</p>
</div>

View File

@ -10,13 +10,13 @@ import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/lib/site/social";
export const metadata: Metadata = {
title: "Terms of Service - OpenCut",
title: "Terms of Service - NeuralCut",
description:
"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
"NeuralCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
openGraph: {
title: "Terms of Service - OpenCut",
title: "Terms of Service - NeuralCut",
description:
"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
"NeuralCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
type: "website",
},
};
@ -83,7 +83,7 @@ export default function TermsPage() {
processing happens locally on your device. We never see, store, or
have access to your files. We make no claims to ownership, licensing,
or rights over your videos, projects, or any content you create using
OpenCut.
NeuralCut.
</p>
<ul className="list-disc space-y-2 pl-6">
<li>
@ -91,25 +91,25 @@ export default function TermsPage() {
</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>
<li>No watermarks, no licensing restrictions from NeuralCut</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">How You Can Use OpenCut</h2>
<p>OpenCut is free for personal and commercial use. You can:</p>
<h2 className="text-2xl font-semibold">How You Can Use NeuralCut</h2>
<p>NeuralCut is free for personal and commercial use. You can:</p>
<ul className="list-disc space-y-2 pl-6">
<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>Use NeuralCut for client work and paid projects</li>
<li>Share and distribute videos created with NeuralCut</li>
<li>
Modify and distribute the OpenCut software (under MIT license)
Modify and distribute the NeuralCut software (under MIT license)
</li>
</ul>
<p>
You're responsible for how you use OpenCut and the content you create.
You're responsible for how you use NeuralCut and the content you create.
Don't use it for anything illegal in your jurisdiction.
</p>
</section>
@ -119,14 +119,14 @@ export default function TermsPage() {
<p>
AI features like auto captions run entirely in your browser using
on-device models. No content is uploaded to any server. These features
are optional - you can use OpenCut without them.
are optional - you can use NeuralCut without them.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Service</h2>
<p>
OpenCut does not currently require an account. The service is provided
NeuralCut does not currently require an account. The service is provided
"as is" without warranties. While we strive for reliability, we can't
guarantee uninterrupted service.
</p>
@ -134,10 +134,10 @@ export default function TermsPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Open Source Benefits</h2>
<p>Because OpenCut is open source, you have additional rights:</p>
<p>Because NeuralCut is open source, you have additional rights:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Review our code to see exactly how we handle your data</li>
<li>Self-host OpenCut on your own servers</li>
<li>Self-host NeuralCut on your own servers</li>
<li>Modify the software to suit your needs</li>
<li>Contribute improvements back to the community</li>
</ul>
@ -158,7 +158,7 @@ export default function TermsPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Limitations and Liability</h2>
<p>
OpenCut is provided free of charge. To the extent permitted by law:
NeuralCut is provided free of charge. To the extent permitted by law:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>We're not liable for any loss of data or content</li>
@ -178,7 +178,7 @@ export default function TermsPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Service Changes</h2>
<p>We may update OpenCut and these terms:</p>
<p>We may update NeuralCut and these terms:</p>
<ul className="list-disc space-y-2 pl-6">
<li>We'll notify you of significant changes to these terms</li>
<li>Continued use means you accept any updates</li>
@ -189,7 +189,7 @@ export default function TermsPage() {
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Stopping Use</h2>
<p>You can stop using OpenCut at any time:</p>
<p>You can stop using NeuralCut at any time:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Clear your browser data to remove local projects</li>
</ul>

View File

@ -26,7 +26,7 @@ export function StoragePersistenceDialog() {
runs low.
</p>
<p className="text-base text-muted-foreground">
Allow OpenCut to protect them?
Allow NeuralCut to protect them?
</p>
</DialogBody>
<DialogFooter>

View File

@ -16,7 +16,7 @@ import { useRouter } from "next/navigation";
import { FaDiscord } from "react-icons/fa6";
import { ExportButton } from "./export-button";
import { FeedbackPopover } from "@/lib/feedback/components/feedback-popover";
import { ThemeToggle } from "../theme-toggle";
import { PaletteSelector } from "../palette-selector";
import { DEFAULT_LOGO_URL } from "@/lib/site/brand";
import { SOCIAL_LINKS } from "@/lib/site/social";
import { toast } from "sonner";
@ -37,7 +37,7 @@ export function EditorHeader() {
<nav className="flex items-center gap-2">
<FeedbackPopover />
<ExportButton />
<ThemeToggle />
<PaletteSelector />
</nav>
</header>
);

View File

@ -66,7 +66,7 @@ export function ExportButton() {
<button
type="button"
className={cn(
"flex items-center gap-1.5 rounded-md bg-[#38BDF8] px-[0.12rem] py-[0.12rem] text-white",
"flex items-center gap-1.5 rounded-md bg-export-ring px-[0.12rem] py-[0.12rem] text-white",
hasProject ? "cursor-pointer" : "cursor-not-allowed opacity-50",
)}
onClick={hasProject ? () => setIsExportPopoverOpen(true) : undefined}
@ -78,11 +78,11 @@ export function ExportButton() {
}
}}
>
<div className="relative flex items-center gap-1.5 rounded-[0.6rem] bg-linear-270 from-[#2567EC] to-[#37B6F7] px-4 py-1 shadow-[0_1px_3px_0px_rgba(0,0,0,0.65)]">
<div className="relative flex items-center gap-1.5 rounded-[0.6rem] bg-linear-270 from-export-gradient-from to-export-gradient-to px-4 py-1 shadow-[0_1px_3px_0px_rgba(0,0,0,0.65)]">
<HugeiconsIcon icon={TransitionTopIcon} className="z-50 size-3.5" />
<span className="z-50 text-[0.875rem]">Export</span>
<div className="absolute top-0 left-0 z-10 flex size-full items-center justify-center rounded-[0.6rem] bg-linear-to-t from-white/0 to-white/50">
<div className="absolute top-[0.08rem] z-50 h-[calc(100%-2px)] w-[calc(100%-2px)] rounded-[0.6rem] bg-linear-270 from-[#2567EC] to-[#37B6F7]"></div>
<div className="absolute top-[0.08rem] z-50 h-[calc(100%-2px)] w-[calc(100%-2px)] rounded-[0.6rem] bg-linear-270 from-export-gradient-from to-export-gradient-to"></div>
</div>
</div>
</button>

View File

@ -52,7 +52,7 @@ export function MobileGate({ children }: MobileGateProps) {
Desktop only (for now)
</h1>
<p className="text-muted-foreground text-sm leading-relaxed">
OpenCut isn't optimized for mobile or iPad yet. Things will break
NeuralCut isn't optimized for mobile or iPad yet. Things will break
and the layout will be a mess. Come back on a desktop for the real
experience.
</p>

View File

@ -28,13 +28,13 @@ export function Onboarding() {
const getStepTitle = () => {
switch (step) {
case 0:
return "Welcome to OpenCut Beta! 🎉";
return "Welcome to NeuralCut Beta! 🎉";
case 1:
return "⚠️ This is a super early beta!";
case 2:
return "🦋 Have fun testing!";
default:
return "OpenCut Onboarding";
return "NeuralCut Onboarding";
}
};
@ -44,8 +44,8 @@ export function Onboarding() {
return (
<div className="space-y-5">
<div className="space-y-3">
<Title title="Welcome to OpenCut Beta! 🎉" />
<Description description="You're among the first to try OpenCut - the fully open source CapCut alternative." />
<Title title="Welcome to NeuralCut Beta! 🎉" />
<Description description="You're among the first to try NeuralCut - the fully open source CapCut alternative." />
</div>
<NextButton onClick={handleNext}>Next</NextButton>
</div>
@ -57,7 +57,7 @@ export function Onboarding() {
<Title title={getStepTitle()} />
<Description description="There's still a ton of things to do to make this editor amazing." />
<Description description="A lot of features are still missing. We're working hard to build them out!" />
<Description description="If you're curious, check out our roadmap [here](https://opencut.app/roadmap)" />
<Description description="If you're curious, check out our roadmap [here](https://neuralcut.app/roadmap)" />
</div>
<NextButton onClick={handleNext}>Next</NextButton>
</div>
@ -68,7 +68,7 @@ export function Onboarding() {
<div className="space-y-3">
<Title title={getStepTitle()} />
<Description
description={`Join our [Discord](${SOCIAL_LINKS.discord}), chat with cool people and share feedback to help make OpenCut the best editor ever.`}
description={`Join our [Discord](${SOCIAL_LINKS.discord}), chat with cool people and share feedback to help make NeuralCut the best editor ever.`}
/>
</div>
<NextButton onClick={handleClose}>Finish</NextButton>

View File

@ -41,12 +41,12 @@ export function Footer() {
<div className="mb-4 flex items-center justify-start gap-2">
<Image
src={DEFAULT_LOGO_URL}
alt="OpenCut"
width={24}
height={24}
className="invert dark:invert-0"
/>
<span className="text-lg font-bold">OpenCut</span>
alt="NeuralCut"
width={24}
height={24}
className="invert dark:invert-0"
/>
<span className="text-lg font-bold">NeuralCut</span>
</div>
<p className="text-muted-foreground mb-5 text-sm md:text-left">
The privacy-first video editor that feels simple to use.
@ -114,7 +114,7 @@ export function Footer() {
<div className="flex flex-col items-start justify-between gap-4 pt-2 md:flex-row">
<div className="text-muted-foreground flex items-center gap-4 text-sm">
<span>
© {new Date().getFullYear()} OpenCut, All Rights Reserved
© {new Date().getFullYear()} NeuralCut, All Rights Reserved
</span>
</div>
</div>

View File

@ -6,7 +6,7 @@ import { motion } from "motion/react";
import { Button } from "./ui/button";
import { ArrowRight } from "lucide-react";
import Image from "next/image";
import { ThemeToggle } from "./theme-toggle";
import { PaletteSelector } from "./palette-selector";
import {
Copy01Icon,
Download01Icon,
@ -57,7 +57,7 @@ export function Header() {
<Link href="/" className="flex items-center gap-3">
<Image
src={DEFAULT_LOGO_URL}
alt="OpenCut Logo"
alt="NeuralCut Logo"
className="invert dark:invert-0"
width={32}
height={32}
@ -79,7 +79,7 @@ export function Header() {
onClick={() => {
const a = document.createElement("a");
a.href = DEFAULT_LOGO_URL;
a.download = "opencut-logo.svg";
a.download = "neuralcut-logo.svg";
a.click();
}}
>
@ -130,7 +130,7 @@ export function Header() {
<ArrowRight className="size-4" />
</Button>
</Link>
<ThemeToggle />
<PaletteSelector />
</div>
</div>
<div
@ -182,13 +182,8 @@ export function Header() {
</motion.div>
))}
</nav>
<ThemeToggle
className="absolute right-8 bottom-8 size-10"
iconClassName="!size-[1.2rem]"
onToggle={(e) => {
e.preventDefault();
e.stopPropagation();
}}
<PaletteSelector
className="absolute right-8 bottom-8 [&>button]:size-10 [&_.hugeicons-icon]:!size-[1.2rem]"
/>
</div>
</div>

View File

@ -14,7 +14,7 @@ export function Hero() {
src="/landing-page-dark.png"
height={1903.5}
width={1269}
alt="OpenCut video editor landing page background"
alt="NeuralCut video editor landing page background"
/>
<div className="mx-auto flex w-full max-w-3xl flex-1 flex-col justify-center">
<div className="inline-block text-4xl font-bold tracking-tighter md:text-[4rem]">

View File

@ -0,0 +1,93 @@
"use client";
import { useTheme } from "next-themes";
import { useThemeStore, PALETTES } from "@/lib/themes";
import { cn } from "@/utils/ui";
import { CheckmarkCircle01Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Sun03Icon } from "@hugeicons/core-free-icons";
export function PaletteSelector({ className }: { className?: string }) {
const { theme, setTheme } = useTheme();
const { paletteId, setPalette } = useThemeStore();
const activePalette = PALETTES.find((p) => p.id === paletteId);
return (
<div className={cn("flex items-center gap-1", className)}>
<Button
size="icon"
variant="ghost"
className="size-8"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<HugeiconsIcon icon={Sun03Icon} className="!size-[1.1rem]" />
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="size-8 gap-0 p-1">
<div className="flex gap-0.5">
{activePalette?.preview && (
<>
<div
className="size-3 rounded-full border border-border"
style={{ backgroundColor: activePalette.preview.primary }}
/>
<div
className="size-3 rounded-full border border-border"
style={{ backgroundColor: activePalette.preview.accent }}
/>
</>
)}
</div>
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-48 p-2">
<div className="flex flex-col gap-1">
<p className="text-muted-foreground px-2 text-xs font-medium">
Palette
</p>
{PALETTES.map((palette) => (
<button
key={palette.id}
type="button"
className={cn(
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-accent",
paletteId === palette.id && "bg-accent",
)}
onClick={() => setPalette(palette.id)}
>
<div className="flex gap-0.5">
<div
className="size-3.5 rounded-full border border-border"
style={{ backgroundColor: palette.preview.primary }}
/>
<div
className="size-3.5 rounded-full border border-border"
style={{ backgroundColor: palette.preview.background }}
/>
<div
className="size-3.5 rounded-full border border-border"
style={{ backgroundColor: palette.preview.accent }}
/>
</div>
<span className="flex-1 text-left">{palette.name}</span>
{paletteId === palette.id && (
<HugeiconsIcon
icon={CheckmarkCircle01Icon}
className="text-primary !size-4"
/>
)}
</button>
))}
</div>
</PopoverContent>
</Popover>
</div>
);
}

View File

@ -2,7 +2,7 @@
import { useEffect, useState } from "react";
const DISMISSED_KEY = "opencut-storage-persist-dismissed";
const DISMISSED_KEY = "neuralcut-storage-persist-dismissed";
function isFirefox(): boolean {
return navigator.userAgent.toLowerCase().includes("firefox");

View File

@ -36,7 +36,7 @@ export const auth = betterAuth({
},
},
baseURL: webEnv.NEXT_PUBLIC_SITE_URL,
appName: "OpenCut",
appName: "NeuralCut",
trustedOrigins: [webEnv.NEXT_PUBLIC_SITE_URL],
});

View File

@ -1,7 +1,7 @@
export const SITE_URL = "https://opencut.app";
export const SITE_URL = "https://neuralcut.app";
export const SITE_INFO = {
title: "OpenCut",
title: "NeuralCut",
description:
"A simple but powerful video editor that gets the job done. In your browser.",
url: SITE_URL,

View File

@ -11,13 +11,13 @@ export const EXTERNAL_TOOLS: ExternalTool[] = [
{
name: "Marble",
description:
"Modern headless CMS for content management and the blog for OpenCut",
"Modern headless CMS for content management and the blog for NeuralCut",
url: "https://marblecms.com?utm_source=opencut",
icon: OcMarbleIcon,
},
{
name: "Databuddy",
description: "GDPR compliant analytics and user insights for OpenCut",
description: "GDPR compliant analytics and user insights for NeuralCut",
url: "https://databuddy.cc?utm_source=opencut",
icon: OcDataBuddyIcon,
},

View File

@ -18,7 +18,7 @@ export const SPONSORS: Sponsor[] = [
name: "Vercel",
url: "https://vercel.com?utm_source=opencut",
logo: "/logos/others/vercel.svg",
description: "Platform where we deploy and host OpenCut.",
description: "Platform where we deploy and host NeuralCut.",
invertOnDark: true,
},
];

View File

@ -0,0 +1,3 @@
export { useThemeStore } from "./theme-store";
export { PALETTES, getPaletteById } from "./palettes";
export type { PaletteMeta } from "./types";

View File

@ -0,0 +1,11 @@
import type { PaletteMeta } from "../types";
export const CLASSIC_PALETTE: PaletteMeta = {
id: "classic",
name: "Classic",
preview: {
primary: "#15A0E6",
background: "#0D0D0D",
accent: "#333333",
},
};

View File

@ -0,0 +1,9 @@
import { NEURALCUT_PALETTE } from "./neuralcut";
import { CLASSIC_PALETTE } from "./classic";
import type { PaletteMeta } from "../types";
export const PALETTES: PaletteMeta[] = [NEURALCUT_PALETTE, CLASSIC_PALETTE];
export function getPaletteById(id: string): PaletteMeta | undefined {
return PALETTES.find((p) => p.id === id);
}

View File

@ -0,0 +1,11 @@
import type { PaletteMeta } from "../types";
export const NEURALCUT_PALETTE: PaletteMeta = {
id: "neuralcut",
name: "NeuralCut",
preview: {
primary: "#F55385",
background: "#1B212B",
accent: "#BC466D",
},
};

View File

@ -0,0 +1,48 @@
"use client";
import { useEffect } from "react";
import { ThemeProvider } from "next-themes";
import { useThemeStore } from "@/lib/themes";
const PALETTE_SCRIPT = `
(function(){
try {
var s = localStorage.getItem('neuralcut-theme');
if (s) {
var d = JSON.parse(s);
if (d && d.state && d.state.paletteId) {
document.documentElement.setAttribute('data-palette', d.state.paletteId);
}
} else {
document.documentElement.setAttribute('data-palette', 'neuralcut');
}
} catch(e) {
document.documentElement.setAttribute('data-palette', 'neuralcut');
}
})();
`;
export function NeuralCutThemeProvider({
children,
}: {
children: React.ReactNode;
}) {
const paletteId = useThemeStore((s) => s.paletteId);
useEffect(() => {
document.documentElement.setAttribute("data-palette", paletteId);
}, [paletteId]);
return (
<>
<script dangerouslySetInnerHTML={{ __html: PALETTE_SCRIPT }} />
<ThemeProvider
attribute="class"
defaultTheme="system"
disableTransitionOnChange
>
{children}
</ThemeProvider>
</>
);
}

View File

@ -0,0 +1,22 @@
"use client";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { NEURALCUT_PALETTE } from "./palettes/neuralcut";
interface ThemeState {
paletteId: string;
setPalette: (id: string) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
paletteId: NEURALCUT_PALETTE.id,
setPalette: (id: string) => set({ paletteId: id }),
}),
{
name: "neuralcut-theme",
},
),
);

View File

@ -0,0 +1,5 @@
export interface PaletteMeta {
id: string;
name: string;
preview: { primary: string; background: string; accent: string };
}

View File

@ -162,7 +162,7 @@ export const useKeybindingsStore = create<KeybindingsState>()(
},
}),
{
name: "opencut-keybindings",
name: "neuralcut-keybindings",
version: CURRENT_VERSION,
partialize: (state) => ({
keybindings: state.keybindings,

View File

@ -1,25 +1,25 @@
{
"name": "opencut",
"name": "neuralcut",
"packageManager": "bun@1.2.18",
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build:tools": "turbo run build --filter=@opencut/tools",
"build:tools": "turbo run build --filter=@neuralcut/tools",
"build:wasm": "wasm-pack build rust/wasm --target bundler --out-dir pkg",
"build:web": "turbo run build --filter=@opencut/web",
"deploy:web": "turbo run deploy --filter=@opencut/web",
"dev:tools": "turbo run dev --filter=@opencut/tools",
"build:web": "turbo run build --filter=@neuralcut/web",
"deploy:web": "turbo run deploy --filter=@neuralcut/web",
"dev:tools": "turbo run dev --filter=@neuralcut/tools",
"dev:wasm": "cargo watch -w rust/crates -w rust/wasm/src -s 'wasm-pack build rust/wasm --target bundler --out-dir pkg'",
"dev:web": "turbo run dev --filter=@opencut/web",
"dev:web": "turbo run dev --filter=@neuralcut/web",
"format:web": "biome format apps/web/src/services/renderer --write --max-diagnostics=1000",
"generate:fonts": "npx tsx apps/web/scripts/generate-font-sprites.ts",
"lint:web": "biome lint apps/web/src --max-diagnostics=1000",
"lint:web:fix": "biome lint apps/web/src --write --unsafe --max-diagnostics=1000",
"preview:web": "turbo run preview --filter=@opencut/web",
"preview:web": "turbo run preview --filter=@neuralcut/web",
"publish:wasm": "bun run build:wasm && npm publish rust/wasm/pkg --access public",
"start:tools": "turbo run start --filter=@opencut/tools",
"start:tools": "turbo run start --filter=@neuralcut/tools",
"test": "bun test"
},
"dependencies": {