Merge pull request #9 from luisacevelad/feat/theme-system

feat: add theme system with swappable palettes and rebrand to NeuralCut
This commit is contained in:
luisacevelad 2026-04-30 10:52:33 -05:00 committed by GitHub
commit d65435554f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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", "version": "0.1.0",
"private": true, "private": true,
"packageManager": "bun@1.2.18", "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.", "description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone", "display": "standalone",
"start_url": "/", "start_url": "/",

View File

@ -6,13 +6,13 @@ import { getPosts } from "@/lib/blog/query";
import type { Post } from "@/lib/blog/types"; import type { Post } from "@/lib/blog/types";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Blog - OpenCut", title: "Blog - NeuralCut",
description: 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: { openGraph: {
title: "Blog - OpenCut", title: "Blog - NeuralCut",
description: 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", type: "website",
}, },
}; };
@ -24,7 +24,7 @@ export default async function BlogPage() {
return ( return (
<BasePage <BasePage
title="Blog" 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"> <div className="flex flex-col">
{data.posts.map((post) => ( {data.posts.map((post) => (

View File

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

View File

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

View File

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

View File

@ -7,13 +7,13 @@ import { EXTERNAL_TOOLS } from "@/lib/site/external-tools";
import { BasePage } from "../base-page"; import { BasePage } from "../base-page";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Contributors - OpenCut", title: "Contributors - NeuralCut",
description: 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: { openGraph: {
title: "Contributors - OpenCut", title: "Contributors - NeuralCut",
description: 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", type: "website",
}, },
}; };
@ -70,7 +70,7 @@ export default async function ContributorsPage() {
return ( return (
<BasePage <BasePage
title="Contributors" 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"> <div className="-mt-4 flex items-center justify-center gap-8 text-sm">
<StatItem value={contributors.length} label="contributors" /> <StatItem value={contributors.length} label="contributors" />
@ -87,7 +87,7 @@ export default async function ContributorsPage() {
<ExternalToolsSection /> <ExternalToolsSection />
<GitHubContributeSection <GitHubContributeSection
title="Join the community" 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> </div>
</BasePage> </BasePage>
@ -169,7 +169,7 @@ function AllContributorsSection({
<div className="flex flex-col gap-2 text-center"> <div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">All contributors</h2> <h2 className="text-2xl font-semibold">All contributors</h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Everyone who makes OpenCut better Everyone who makes NeuralCut better
</p> </p>
</div> </div>
@ -211,7 +211,7 @@ function ExternalToolsSection() {
<div className="flex flex-col gap-10"> <div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center"> <div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">External tools</h2> <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>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2"> <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 ( return (
<div className="bg-accent border-b h-9 flex items-center justify-center gap-2 text-xs text-muted-foreground"> <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 <Button
variant="text" variant="text"
size="icon" size="icon"

View File

@ -47,6 +47,54 @@
--sidebar-border: hsl(0 0% 85.1%); --sidebar-border: hsl(0 0% 85.1%);
--sidebar-ring: hsl(0 0% 16.9%); --sidebar-ring: hsl(0 0% 16.9%);
--sidebar: hsl(0 0% 98%); --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 { .panel {
@ -97,6 +145,43 @@
--sidebar-border: hsl(0 0% 20%); --sidebar-border: hsl(0 0% 20%);
--sidebar-ring: hsl(0 0% 83.1%); --sidebar-ring: hsl(0 0% 83.1%);
--sidebar: hsl(0 0% 6%); --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 { .dark .panel {
@ -115,6 +200,42 @@
--ring: hsl(0, 0%, 52%); --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 { @layer base {
/* /*
The default border color has changed to `currentcolor` in Tailwind CSS v4, The default border color has changed to `currentcolor` in Tailwind CSS v4,
@ -217,6 +338,10 @@
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --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 */ /* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out; --animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 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 Script from "next/script";
import "./globals.css"; import "./globals.css";
import { Toaster } from "../components/ui/sonner"; import { Toaster } from "../components/ui/sonner";
@ -8,6 +7,7 @@ import { baseMetaData } from "./metadata";
import { BotIdClient } from "botid/client"; import { BotIdClient } from "botid/client";
import { webEnv } from "@/lib/env/web"; import { webEnv } from "@/lib/env/web";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { NeuralCutThemeProvider } from "@/lib/themes/theme-provider";
const siteFont = Inter({ subsets: ["latin"] }); const siteFont = Inter({ subsets: ["latin"] });
@ -40,11 +40,7 @@ export default function RootLayout({
)} )}
</head> </head>
<body className={`${siteFont.className} font-sans antialiased`}> <body className={`${siteFont.className} font-sans antialiased`}>
<ThemeProvider <NeuralCutThemeProvider>
attribute="class"
defaultTheme="system"
disableTransitionOnChange={true}
>
<TooltipProvider> <TooltipProvider>
<Toaster /> <Toaster />
<Script <Script
@ -61,7 +57,7 @@ export default function RootLayout({
/> />
{children} {children}
</TooltipProvider> </TooltipProvider>
</ThemeProvider> </NeuralCutThemeProvider>
</body> </body>
</html> </html>
); );

View File

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

View File

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

View File

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

View File

@ -9,13 +9,13 @@ import { LinkSquare02Icon } from "@hugeicons/core-free-icons";
import { cn } from "@/utils/ui"; import { cn } from "@/utils/ui";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Sponsors - OpenCut", title: "Sponsors - NeuralCut",
description: 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: { openGraph: {
title: "Sponsors - OpenCut", title: "Sponsors - NeuralCut",
description: 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", type: "website",
}, },
}; };
@ -28,7 +28,7 @@ export default function SponsorsPage() {
Sponsors Sponsors
</h1> </h1>
<p className="text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed text-pretty"> <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. editing.
</p> </p>
</div> </div>

View File

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

View File

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

View File

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

View File

@ -66,7 +66,7 @@ export function ExportButton() {
<button <button
type="button" type="button"
className={cn( 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", hasProject ? "cursor-pointer" : "cursor-not-allowed opacity-50",
)} )}
onClick={hasProject ? () => setIsExportPopoverOpen(true) : undefined} 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" /> <HugeiconsIcon icon={TransitionTopIcon} className="z-50 size-3.5" />
<span className="z-50 text-[0.875rem]">Export</span> <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 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>
</div> </div>
</button> </button>

View File

@ -52,7 +52,7 @@ export function MobileGate({ children }: MobileGateProps) {
Desktop only (for now) Desktop only (for now)
</h1> </h1>
<p className="text-muted-foreground text-sm leading-relaxed"> <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 and the layout will be a mess. Come back on a desktop for the real
experience. experience.
</p> </p>

View File

@ -28,13 +28,13 @@ export function Onboarding() {
const getStepTitle = () => { const getStepTitle = () => {
switch (step) { switch (step) {
case 0: case 0:
return "Welcome to OpenCut Beta! 🎉"; return "Welcome to NeuralCut Beta! 🎉";
case 1: case 1:
return "⚠️ This is a super early beta!"; return "⚠️ This is a super early beta!";
case 2: case 2:
return "🦋 Have fun testing!"; return "🦋 Have fun testing!";
default: default:
return "OpenCut Onboarding"; return "NeuralCut Onboarding";
} }
}; };
@ -44,8 +44,8 @@ export function Onboarding() {
return ( return (
<div className="space-y-5"> <div className="space-y-5">
<div className="space-y-3"> <div className="space-y-3">
<Title title="Welcome to OpenCut Beta! 🎉" /> <Title title="Welcome to NeuralCut Beta! 🎉" />
<Description description="You're among the first to try OpenCut - the fully open source CapCut alternative." /> <Description description="You're among the first to try NeuralCut - the fully open source CapCut alternative." />
</div> </div>
<NextButton onClick={handleNext}>Next</NextButton> <NextButton onClick={handleNext}>Next</NextButton>
</div> </div>
@ -57,7 +57,7 @@ export function Onboarding() {
<Title title={getStepTitle()} /> <Title title={getStepTitle()} />
<Description description="There's still a ton of things to do to make this editor amazing." /> <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="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> </div>
<NextButton onClick={handleNext}>Next</NextButton> <NextButton onClick={handleNext}>Next</NextButton>
</div> </div>
@ -68,7 +68,7 @@ export function Onboarding() {
<div className="space-y-3"> <div className="space-y-3">
<Title title={getStepTitle()} /> <Title title={getStepTitle()} />
<Description <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> </div>
<NextButton onClick={handleClose}>Finish</NextButton> <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"> <div className="mb-4 flex items-center justify-start gap-2">
<Image <Image
src={DEFAULT_LOGO_URL} src={DEFAULT_LOGO_URL}
alt="OpenCut" alt="NeuralCut"
width={24} width={24}
height={24} height={24}
className="invert dark:invert-0" className="invert dark:invert-0"
/> />
<span className="text-lg font-bold">OpenCut</span> <span className="text-lg font-bold">NeuralCut</span>
</div> </div>
<p className="text-muted-foreground mb-5 text-sm md:text-left"> <p className="text-muted-foreground mb-5 text-sm md:text-left">
The privacy-first video editor that feels simple to use. 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="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"> <div className="text-muted-foreground flex items-center gap-4 text-sm">
<span> <span>
© {new Date().getFullYear()} OpenCut, All Rights Reserved © {new Date().getFullYear()} NeuralCut, All Rights Reserved
</span> </span>
</div> </div>
</div> </div>

View File

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

View File

@ -14,7 +14,7 @@ export function Hero() {
src="/landing-page-dark.png" src="/landing-page-dark.png"
height={1903.5} height={1903.5}
width={1269} 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="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]"> <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"; import { useEffect, useState } from "react";
const DISMISSED_KEY = "opencut-storage-persist-dismissed"; const DISMISSED_KEY = "neuralcut-storage-persist-dismissed";
function isFirefox(): boolean { function isFirefox(): boolean {
return navigator.userAgent.toLowerCase().includes("firefox"); return navigator.userAgent.toLowerCase().includes("firefox");

View File

@ -36,7 +36,7 @@ export const auth = betterAuth({
}, },
}, },
baseURL: webEnv.NEXT_PUBLIC_SITE_URL, baseURL: webEnv.NEXT_PUBLIC_SITE_URL,
appName: "OpenCut", appName: "NeuralCut",
trustedOrigins: [webEnv.NEXT_PUBLIC_SITE_URL], 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 = { export const SITE_INFO = {
title: "OpenCut", title: "NeuralCut",
description: description:
"A simple but powerful video editor that gets the job done. In your browser.", "A simple but powerful video editor that gets the job done. In your browser.",
url: SITE_URL, url: SITE_URL,

View File

@ -11,13 +11,13 @@ export const EXTERNAL_TOOLS: ExternalTool[] = [
{ {
name: "Marble", name: "Marble",
description: 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", url: "https://marblecms.com?utm_source=opencut",
icon: OcMarbleIcon, icon: OcMarbleIcon,
}, },
{ {
name: "Databuddy", 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", url: "https://databuddy.cc?utm_source=opencut",
icon: OcDataBuddyIcon, icon: OcDataBuddyIcon,
}, },

View File

@ -18,7 +18,7 @@ export const SPONSORS: Sponsor[] = [
name: "Vercel", name: "Vercel",
url: "https://vercel.com?utm_source=opencut", url: "https://vercel.com?utm_source=opencut",
logo: "/logos/others/vercel.svg", logo: "/logos/others/vercel.svg",
description: "Platform where we deploy and host OpenCut.", description: "Platform where we deploy and host NeuralCut.",
invertOnDark: true, 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, version: CURRENT_VERSION,
partialize: (state) => ({ partialize: (state) => ({
keybindings: state.keybindings, keybindings: state.keybindings,

View File

@ -1,25 +1,25 @@
{ {
"name": "opencut", "name": "neuralcut",
"packageManager": "bun@1.2.18", "packageManager": "bun@1.2.18",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"packages/*" "packages/*"
], ],
"scripts": { "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:wasm": "wasm-pack build rust/wasm --target bundler --out-dir pkg",
"build:web": "turbo run build --filter=@opencut/web", "build:web": "turbo run build --filter=@neuralcut/web",
"deploy:web": "turbo run deploy --filter=@opencut/web", "deploy:web": "turbo run deploy --filter=@neuralcut/web",
"dev:tools": "turbo run dev --filter=@opencut/tools", "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: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", "format:web": "biome format apps/web/src/services/renderer --write --max-diagnostics=1000",
"generate:fonts": "npx tsx apps/web/scripts/generate-font-sprites.ts", "generate:fonts": "npx tsx apps/web/scripts/generate-font-sprites.ts",
"lint:web": "biome lint apps/web/src --max-diagnostics=1000", "lint:web": "biome lint apps/web/src --max-diagnostics=1000",
"lint:web:fix": "biome lint apps/web/src --write --unsafe --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", "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" "test": "bun test"
}, },
"dependencies": { "dependencies": {