diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-25 16:47:22 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:48:22 +0200 |
| commit | 8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch) | |
| tree | e9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/app | |
| parent | 39ea298a9ccba3104c549c0ca079c231981811f5 (diff) | |
| download | opencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz | |
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/app')
| -rw-r--r-- | apps/web/src/app/blog/[slug]/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/blog/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/app/globals.css | 438 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 19 | ||||
| -rw-r--r-- | apps/web/src/app/privacy/page.tsx | 121 | ||||
| -rw-r--r-- | apps/web/src/app/projects/page.tsx | 20 | ||||
| -rw-r--r-- | apps/web/src/app/projects/store.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/terms/page.tsx | 100 |
8 files changed, 298 insertions, 406 deletions
diff --git a/apps/web/src/app/blog/[slug]/page.tsx b/apps/web/src/app/blog/[slug]/page.tsx index e1afc3a8..90afd919 100644 --- a/apps/web/src/app/blog/[slug]/page.tsx +++ b/apps/web/src/app/blog/[slug]/page.tsx @@ -5,7 +5,7 @@ import { BasePage } from "@/app/base-page"; import Prose from "@/components/ui/prose"; import { Separator } from "@/components/ui/separator"; import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query"; -import type { Author, Post } from "@/types/blog"; +import type { Author, Post } from "@/lib/blog/types"; type PageProps = { params: Promise<{ slug: string }>; diff --git a/apps/web/src/app/blog/page.tsx b/apps/web/src/app/blog/page.tsx index 257a8f23..df0b195d 100644 --- a/apps/web/src/app/blog/page.tsx +++ b/apps/web/src/app/blog/page.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { BasePage } from "@/app/base-page"; import { Separator } from "@/components/ui/separator"; import { getPosts } from "@/lib/blog/query"; -import type { Post } from "@/types/blog"; +import type { Post } from "@/lib/blog/types"; export const metadata: Metadata = { title: "Blog - OpenCut", diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 6d3e984f..21219427 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -8,111 +8,111 @@ @plugin "tailwindcss-animate"; :root { - --background: hsl(0, 0%, 100%); - --foreground: hsl(0 0% 11%); - --card: hsl(0, 0%, 100%); - --card-foreground: hsl(0 0% 11%); - --popover: hsl(0, 0%, 100%); - --popover-hover: hsl(0, 0%, 96%); - --popover-foreground: hsl(0 0% 2%); - --primary: #009dff; - --primary-foreground: hsl(0, 0%, 100%); - --secondary: hsl(204, 100%, 97%); - --secondary-border: hsl(204, 100%, 94%); - --secondary-foreground: hsl(200, 98%, 39%); - --muted: hsl(0 0% 85.1%); - --muted-foreground: hsl(0 0% 50%); - --accent: hsl(0, 0%, 96%); - --accent-foreground: hsl(0 0% 2%); - --destructive: hsl(0, 83%, 50%); - --destructive-foreground: hsl(0, 0%, 100%); - --constructive: hsl(141, 71%, 48%); - --constructive-foreground: hsl(0, 0%, 100%); - --border: hsl(0 0% 91%); - --input: hsl(0, 0%, 100%); - --ring: hsl(0, 0%, 55%); - --chart-1: hsl(220 70% 50%); - --chart-2: hsl(160 60% 45%); - --chart-3: hsl(30 80% 55%); - --chart-4: hsl(280 65% 60%); - --chart-5: hsl(340 75% 55%); - --sidebar-background: hsl(0 0% 96.1%); - --sidebar-foreground: hsl(0 0% 2%); - --sidebar-primary: hsl(0 0% 2%); - --sidebar-primary-foreground: hsl(0 0% 91%); - --sidebar-accent: hsl(0 0% 85.1%); - --sidebar-accent-foreground: hsl(0 0% 2%); - --sidebar-border: hsl(0 0% 85.1%); - --sidebar-ring: hsl(0 0% 16.9%); - --sidebar: hsl(0 0% 98%); + --background: hsl(0, 0%, 100%); + --foreground: hsl(0 0% 11%); + --card: hsl(0, 0%, 100%); + --card-foreground: hsl(0 0% 11%); + --popover: hsl(0, 0%, 100%); + --popover-hover: hsl(0, 0%, 96%); + --popover-foreground: hsl(0 0% 2%); + --primary: hsl(200, 90%, 52%); + --primary-foreground: hsl(0, 0%, 100%); + --secondary: hsl(204, 100%, 97%); + --secondary-border: hsl(204, 100%, 94%); + --secondary-foreground: hsl(200, 98%, 39%); + --muted: hsl(0 0% 85.1%); + --muted-foreground: hsl(0 0% 50%); + --accent: hsl(0, 0%, 96%); + --accent-foreground: hsl(0 0% 2%); + --destructive: hsl(0, 83%, 50%); + --destructive-foreground: hsl(0, 0%, 100%); + --constructive: hsl(141, 71%, 48%); + --constructive-foreground: hsl(0, 0%, 100%); + --border: hsl(0 0% 91%); + --input: hsl(0, 0%, 100%); + --ring: hsl(0, 0%, 55%); + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); + --sidebar-background: hsl(0 0% 96.1%); + --sidebar-foreground: hsl(0 0% 2%); + --sidebar-primary: hsl(0 0% 2%); + --sidebar-primary-foreground: hsl(0 0% 91%); + --sidebar-accent: hsl(0 0% 85.1%); + --sidebar-accent-foreground: hsl(0 0% 2%); + --sidebar-border: hsl(0 0% 85.1%); + --sidebar-ring: hsl(0 0% 16.9%); + --sidebar: hsl(0 0% 98%); } .panel { - --background: hsl(216 13% 98%); - --foreground: hsl(0 0% 13%); - --card: hsl(0, 0%, 98%); - --card-foreground: hsl(0 0% 13%); - --primary-foreground: hsl(0, 0%, 98%); - --secondary: hsl(204, 100%, 95%); - --secondary-border: hsl(204, 100%, 92%); - --secondary-foreground: hsl(200, 98%, 37%); - --muted: hsl(0 0% 83.1%); - --muted-foreground: hsl(0 0% 48%); - --accent: hsl(0, 0%, 93%); - --accent-foreground: hsl(0 0% 5%); - --destructive-foreground: hsl(0, 0%, 98%); - --constructive-foreground: hsl(0, 0%, 98%); - --border: hsl(0 0% 89%); - --input: hsl(0 0% 93%); - --ring: hsl(0, 0%, 53%); + --background: hsl(210, 20%, 98%); + --foreground: hsl(0 0% 13%); + --card: hsl(0, 0%, 98%); + --card-foreground: hsl(0 0% 13%); + --primary-foreground: hsl(0, 0%, 98%); + --secondary: hsl(204, 100%, 95%); + --secondary-border: hsl(204, 100%, 92%); + --secondary-foreground: hsl(200, 98%, 37%); + --muted: hsl(0 0% 83.1%); + --muted-foreground: hsl(0 0% 48%); + --accent: hsl(0, 0%, 93%); + --accent-foreground: hsl(0 0% 5%); + --destructive-foreground: hsl(0, 0%, 98%); + --constructive-foreground: hsl(0, 0%, 98%); + --border: hsl(0 0% 87%); + --input: hsl(0 0% 93%); + --ring: hsl(0, 0%, 53%); } .dark { - --background: hsl(0, 0%, 5%); - --foreground: hsl(0 0% 87%); - --card: hsl(0, 0%, 5%); - --card-foreground: hsl(0 0% 87%); - --popover: hsl(0, 0%, 5%); - --popover-hover: hsl(0, 0%, 22%); - --popover-foreground: hsl(0 0% 95%); - --secondary: hsl(204, 100%, 12%); - --secondary-border: hsl(204, 100%, 15%); - --secondary-foreground: hsl(200, 98%, 61%); - --muted: hsl(0 0% 20%); - --accent: hsl(0, 0%, 14%); - --accent-foreground: hsl(0 0% 95%); - --border: hsl(0 0% 16%); - --input: hsl(0 0% 5%); - --ring: hsl(0, 0%, 50%); - --sidebar-background: hsl(0 0% 8%); - --sidebar-foreground: hsl(0 0% 95%); - --sidebar-primary: hsl(0 0% 95%); - --sidebar-primary-foreground: hsl(0 0% 15%); - --sidebar-accent: hsl(0 0% 20%); - --sidebar-accent-foreground: hsl(0 0% 95%); - --sidebar-border: hsl(0 0% 20%); - --sidebar-ring: hsl(0 0% 83.1%); - --sidebar: hsl(0 0% 6%); + --background: hsl(0, 0%, 5%); + --foreground: hsl(0 0% 87%); + --card: hsl(0, 0%, 5%); + --card-foreground: hsl(0 0% 87%); + --popover: hsl(0, 0%, 5%); + --popover-hover: hsl(0, 0%, 13%); + --popover-foreground: hsl(0 0% 95%); + --secondary: hsl(204, 100%, 12%); + --secondary-border: hsl(204, 100%, 15%); + --secondary-foreground: hsl(200, 98%, 61%); + --muted: hsl(0 0% 20%); + --accent: hsl(0, 0%, 14%); + --accent-foreground: hsl(0 0% 95%); + --border: hsl(0 0% 16%); + --input: hsl(0 0% 5%); + --ring: hsl(0, 0%, 50%); + --sidebar-background: hsl(0 0% 8%); + --sidebar-foreground: hsl(0 0% 95%); + --sidebar-primary: hsl(0 0% 95%); + --sidebar-primary-foreground: hsl(0 0% 15%); + --sidebar-accent: hsl(0 0% 20%); + --sidebar-accent-foreground: hsl(0 0% 95%); + --sidebar-border: hsl(0 0% 20%); + --sidebar-ring: hsl(0 0% 83.1%); + --sidebar: hsl(0 0% 6%); } .dark .panel { - --background: hsl(0 0% 10%); - --foreground: hsl(0 0% 85%); - --card: hsl(0, 0%, 10%); - --card-foreground: hsl(0 0% 85%); - --secondary: hsl(204, 67%, 9%); - --secondary-border: hsl(204, 100%, 14%); - --secondary-foreground: hsl(200, 98%, 63%); - --muted: hsl(0 0% 22%); - --accent: hsl(0, 0%, 15%); - --accent-foreground: hsl(0 0% 93%); - --border: hsl(0 0% 18%); - --input: hsl(0 0% 22%); - --ring: hsl(0, 0%, 52%); + --background: hsl(0 0% 10%); + --foreground: hsl(0 0% 85%); + --card: hsl(0, 0%, 10%); + --card-foreground: hsl(0 0% 85%); + --secondary: hsl(204, 67%, 9%); + --secondary-border: hsl(204, 100%, 14%); + --secondary-foreground: hsl(200, 98%, 63%); + --muted: hsl(0 0% 22%); + --accent: hsl(0, 0%, 15%); + --accent-foreground: hsl(0 0% 93%); + --border: hsl(0 0% 18%); + --input: hsl(0 0% 22%); + --ring: hsl(0, 0%, 52%); } @layer base { - /* + /* The default border color has changed to `currentcolor` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -120,165 +120,165 @@ If we ever want to remove these styles, we need to add an explicit border color utility to any element that depends on these defaults. */ - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - /* Other default base styles */ - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - /* Prevent back/forward swipe */ - overscroll-behavior-x: contain; - } - ::selection { - @apply bg-primary/35 selection:text-primary-foreground; - } + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } + /* Other default base styles */ + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + /* Prevent back/forward swipe */ + overscroll-behavior-x: contain; + } + ::selection { + @apply bg-primary/35 selection:text-primary-foreground; + } } @theme inline { - /* Responsive breakpoints */ - --breakpoint-xs: 30rem; + /* Responsive breakpoints */ + --breakpoint-xs: 30rem; - /* Typography */ - --font-sans: var(--font-inter), sans-serif; + /* Typography */ + --font-sans: var(--font-inter), sans-serif; - /* Font sizes */ - --text-xs: 0.72rem; - --text-sm: 0.79rem; - --text-base: 0.92rem; - --text-base--line-height: calc(1.5 / 0.95); - --text-xs--line-height: calc(1 / 0.8); + /* Font sizes */ + --text-xs: 0.72rem; + --text-sm: 0.79rem; + --text-base: 0.92rem; + --text-base--line-height: calc(1.5 / 0.95); + --text-xs--line-height: calc(1 / 0.8); - /* Border radius */ - --radius-lg: 0.82rem; - --radius-md: 0.65rem; - --radius-sm: 0.35rem; + /* Border radius */ + --radius-lg: 0.82rem; + --radius-md: 0.65rem; + --radius-sm: 0.35rem; - /* Palette mapped to root design tokens */ - --color-background: var(--background); - --color-foreground: var(--foreground); + /* Palette mapped to root design tokens */ + --color-background: var(--background); + --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-hover: var(--popover-hover); - --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-popover-hover: var(--popover-hover); + --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-border: var(--secondary-border); - --color-secondary-foreground: var(--secondary-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-border: var(--secondary-border); + --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); - --color-constructive: var(--constructive); - --color-constructive-foreground: var(--constructive-foreground); + --color-constructive: var(--constructive); + --color-constructive-foreground: var(--constructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); - /* Chart colors */ - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); + /* Chart colors */ + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); - /* Sidebar */ - --color-sidebar: var(--sidebar-background); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); + /* Sidebar */ + --color-sidebar: var(--sidebar-background); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); - /* Animations */ - --animate-accordion-down: accordion-down 0.2s ease-out; - --animate-accordion-up: accordion-up 0.2s ease-out; + /* Animations */ + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; - @keyframes accordion-down { - from { - height: 0; - } - to { - height: var(--radix-accordion-content-height); - } - } + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } - @keyframes accordion-up { - from { - height: var(--radix-accordion-content-height); - } - to { - height: 0; - } - } + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } } @utility scrollbar-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } } @utility scrollbar-x-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:horizontal { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:horizontal { + display: none; + } } @utility scrollbar-y-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:vertical { - display: none; - } + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:vertical { + display: none; + } } @utility scrollbar-thin { - &::-webkit-scrollbar { - width: 6px; - height: 8px; - } - &::-webkit-scrollbar-track { - background: transparent; - } - &::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 4px; - } - &::-webkit-scrollbar-thumb:hover { - background: var(--muted-foreground); - } + &::-webkit-scrollbar { + width: 7px; + height: 7px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; + } + &::-webkit-scrollbar-thumb:hover { + background: var(--muted-foreground); + } } @layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index c2608bd9..f8e6040f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -29,11 +29,20 @@ export default function RootLayout({ <head> <BotIdClient protect={protectedRoutes} /> {process.env.NODE_ENV === "development" && ( - <Script - src="//unpkg.com/react-scan/dist/auto.global.js" - crossOrigin="anonymous" - strategy="beforeInteractive" - /> + <> + <Script + src="//unpkg.com/react-scan/dist/auto.global.js" + crossOrigin="anonymous" + strategy="beforeInteractive" + /> + + {/* code to figma */} + {/* <script + dangerouslySetInnerHTML={{ + __html: `(function(){var s=document.createElement('script');s.src='https://mcp.figma.com/mcp/html-to-design/capture.js';document.head.appendChild(s);})();`, + }} + /> */} + </> )} </head> <body className={`${siteFont.className} font-sans antialiased`}> diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index 4b06405a..0672cfe5 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -37,7 +37,7 @@ export default function PrivacyPage() { </AccordionTrigger> <AccordionContent> <h3 className="mb-3 text-lg font-medium"> - Your content stays private and encrypted. + Your content never leaves your device. </h3> <ol className="list-decimal space-y-2 pl-6"> <li> @@ -45,22 +45,19 @@ export default function PrivacyPage() { your files </li> <li> - AI features require encrypted uploads - your content is - encrypted before leaving your device + AI features like auto captions run locally in your browser + too - nothing is uploaded </li> <li> - We only collect your email and basic profile info for your - account + OpenCut does not currently require an account or login </li> <li>Project data stays on your device, not our servers</li> <li> - We use analytics to improve the app, but no personal video + We use anonymized analytics to improve the app, but no personal video content is tracked </li> - <li> - You can delete your account anytime and all data gets removed - </li> - <li>We don't sell your data or share it with advertisers</li> + <li>You can clear local data from your browser at any time</li> + <li>We don't sell or share your data with anyone (we don't even have it)</li> </ol> <p className="mt-4"> Questions? Email us at{" "} @@ -78,84 +75,30 @@ export default function PrivacyPage() { <section className="flex flex-col gap-3"> <h2 className="text-2xl font-semibold">How We Handle Your Content</h2> <p> - <strong>Basic video editing happens locally on your device.</strong>{" "} - For standard editing features, we never upload, store, or have access - to your video files. Your content remains completely private and under - your control. - </p> - <p> - <strong>AI features require secure processing:</strong> When you - choose to use AI features like auto captions, your audio/video content - is encrypted on your device before being uploaded to our servers for - processing. We use zero-knowledge encryption, meaning we cannot - decrypt or view your content. - </p> - <p> - After AI processing is complete, the encrypted content is immediately - deleted from our servers. Only the results (like generated captions) - are returned to your device. + <strong> + All editing and processing happens locally on your device. + </strong>{" "} + We never upload, store, or have access to your video or audio files. + Your content remains completely private and under your control. + AI-powered features like auto captions also run in your browser using + on-device models - no content ever leaves your device. </p> </section> <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold">Account Information</h2> - <p>When you create an account, we only collect:</p> - <ul className="list-disc space-y-2 pl-6"> - <li>Email address (for account access)</li> - <li> - Profile information from Google OAuth (if you choose to sign in with - Google) - </li> - </ul> - <p> - <strong>We do NOT store your projects on our servers.</strong> All - project data, including names, thumbnails, and creation dates, is - stored locally in your browser using IndexedDB. - </p> + <h2 className="text-2xl font-semibold">Accounts & Authentication</h2> <p> - We use{" "} - <a - href="https://www.better-auth.com" - target="_blank" - rel="noopener" - className="text-primary hover:underline" - > - Better Auth - </a>{" "} - for secure authentication and follow industry-standard security - practices. + OpenCut does not currently offer user accounts, login, or Google + sign-in. </p> - </section> - - <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold">AI Features & Encryption</h2> <p> - When you use AI-powered features (like auto captions, content - analysis, or enhancement tools), your content needs to be processed on - our servers. Here's how we protect your privacy: + Because there is no account system today, we do not collect account + emails, profile information, or OAuth identity data. </p> - <ul className="list-disc space-y-2 pl-6"> - <li> - <strong>Client-side encryption:</strong> Your content is encrypted - on your device before upload - </li> - <li> - <strong>Zero-knowledge processing:</strong> We cannot decrypt or - view your original content - </li> - <li> - <strong>Temporary processing:</strong> Encrypted content is deleted - immediately after processing - </li> - <li> - <strong>Opt-in only:</strong> AI features are optional - basic - editing remains fully local - </li> - </ul> <p> - Different AI features may process different types of content (audio - for captions, video for analysis, etc.), but all follow the same - zero-knowledge encryption approach. + Your projects are never stored on our servers. All project data, + including names, thumbnails, and creation dates, is stored locally + in your browser using IndexedDB. </p> </section> @@ -171,13 +114,12 @@ export default function PrivacyPage() { > Databuddy </a>{" "} - for completely anonymized and non-invasive analytics to understand how - people use OpenCut. + for basic, anonymized visitor counts. We do not track clicks, + interactions, or how you use the editor. </p> <p> - This helps us improve the editor, but we never collect personal - information, track individual users, or store any data that could - identify you. + No personal information is collected, no individual users are tracked, + and no data that could identify you is stored. </p> </section> @@ -187,7 +129,7 @@ export default function PrivacyPage() { <ul className="list-disc space-y-2 pl-6"> <li>Save your projects locally on your device</li> <li>Remember your editor preferences and settings</li> - <li>Keep you logged in across browser sessions</li> + <li>Store app state needed for the editor to work between sessions</li> </ul> <p> All data stays on your device and can be cleared at any time through @@ -200,10 +142,6 @@ export default function PrivacyPage() { <p>OpenCut integrates with these services:</p> <ul className="list-disc space-y-2 pl-6"> <li> - <strong>Google OAuth:</strong> For optional Google sign-in (governed - by Google's privacy policy) - </li> - <li> <strong>Vercel:</strong> For hosting and content delivery </li> <li> @@ -216,8 +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>Delete your account and all associated data at any time</li> - <li>Export your project data</li> + <li>No account is required to use OpenCut today</li> <li>Clear local storage to remove all saved projects</li> <li>Contact us with any privacy concerns</li> </ul> @@ -280,7 +217,7 @@ export default function PrivacyPage() { <Separator /> <p className="text-muted-foreground text-sm"> - Last updated: July 14, 2025 + Last updated: March 15, 2026 </p> </BasePage> ); diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index c25d75b9..9618de27 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -7,6 +7,7 @@ import type { KeyboardEvent, MouseEvent } from "react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; +import { StoragePersistenceDialog } from "@/components/editor/dialogs/storage-persistence-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; @@ -18,7 +19,7 @@ import type { TProjectMetadata, TProjectSortKey, TProjectSortOption, -} from "@/types/project"; +} from "@/lib/project/types"; import { formatTimeCode } from "@/lib/time"; import { formatDate } from "@/utils/date"; import { HugeiconsIcon } from "@hugeicons/react"; @@ -86,6 +87,13 @@ const VIEW_MODE_OPTIONS = [ export default function ProjectsPage() { const { searchQuery, sortKey, sortOrder, viewMode } = useProjectsStore(); const editor = useEditor(); + const sortOption: TProjectSortOption = `${sortKey}-${sortOrder}`; + + const isLoading = useEditor((e) => e.project.getIsLoading()); + const isInitialized = useEditor((e) => e.project.getIsInitialized()); + const projectsToDisplay = useEditor((e) => + e.project.getFilteredAndSortedProjects({ searchQuery, sortOption }), + ); useEffect(() => { if (!editor.project.getIsInitialized()) { @@ -93,18 +101,10 @@ export default function ProjectsPage() { } }, [editor.project]); - const sortOption: TProjectSortOption = `${sortKey}-${sortOrder}`; - const projectsToDisplay = editor.project.getFilteredAndSortedProjects({ - searchQuery, - sortOption, - }); - - const isLoading = editor.project.getIsLoading(); - const isInitialized = editor.project.getIsInitialized(); - return ( <div className="bg-background min-h-screen"> <MigrationDialog /> + <StoragePersistenceDialog /> <ProjectsHeader /> <ProjectsToolbar projectIds={projectsToDisplay.map((p) => p.id)} /> <main className="mx-auto px-4 pt-2 pb-6 flex flex-col gap-4"> diff --git a/apps/web/src/app/projects/store.ts b/apps/web/src/app/projects/store.ts index 767f08df..2c79aa0a 100644 --- a/apps/web/src/app/projects/store.ts +++ b/apps/web/src/app/projects/store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { TProjectSortKey } from "@/types/project"; +import type { TProjectSortKey } from "@/lib/project/types"; export type ProjectsViewMode = "grid" | "list"; diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index d5d43ba7..697a89ef 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -41,18 +41,17 @@ export default function TermsPage() { </h3> <ol className="list-decimal space-y-2 pl-6"> <li> - Your content stays private - basic editing is local, AI features - use encrypted uploads + Everything runs locally in your browser - nothing is ever + uploaded to our servers </li> <li> - We never claim ownership of your content, even when processing - AI features + We never claim ownership of your content </li> <li> Free for personal and commercial use with no watermarks or restrictions </li> - <li>Don't use OpenCut for illegal activities or harassment</li> + <li>You're responsible for how you use it - don't break the law</li> <li> Service provided "as is" - we can't guarantee perfect uptime </li> @@ -61,8 +60,7 @@ export default function TermsPage() { needed </li> <li> - You can delete your account anytime and keep using your exported - videos + No account required - your exported videos are always yours </li> </ol> <p className="mt-4"> @@ -81,21 +79,17 @@ export default function TermsPage() { <section className="flex flex-col gap-3"> <h2 className="text-2xl font-semibold">Your Content, Your Rights</h2> <p> - <strong>You own everything you create.</strong> OpenCut processes - basic editing locally on your device. For AI features, content is - encrypted before upload and we cannot access your original files. We - make no claims to ownership, licensing, or rights over your videos, - projects, or any content you create using OpenCut. + <strong>You own everything you create.</strong> All editing and + 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. </p> <ul className="list-disc space-y-2 pl-6"> <li> - Your content remains private and under your control at all times + Your content never leaves your device </li> <li>You retain all intellectual property rights to your content</li> - <li> - Even when using AI features, we cannot access your unencrypted - content - </li> <li>You can export and use your content however you choose</li> <li>No watermarks, no licensing restrictions from OpenCut</li> </ul> @@ -115,53 +109,26 @@ export default function TermsPage() { </li> </ul> <p> - <strong>What we ask:</strong> Don't use OpenCut for illegal - activities, harassment, or creating harmful content. Be respectful of - others and follow applicable laws. + You're responsible for how you use OpenCut and the content you create. + Don't use it for anything illegal in your jurisdiction. </p> </section> <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold"> - AI Features and Data Processing - </h2> + <h2 className="text-2xl font-semibold">AI Features</h2> <p> - OpenCut offers optional AI-powered features that require server - processing: - </p> - <ul className="list-disc space-y-2 pl-6"> - <li> - AI features (auto captions, content analysis, etc.) are completely - optional - </li> - <li>Your content is encrypted on your device before any upload</li> - <li> - We use zero-knowledge encryption - we cannot decrypt your content - </li> - <li>Encrypted content is deleted immediately after processing</li> - <li> - You maintain full ownership and control of your content throughout - </li> - </ul> - <p> - By using AI features, you consent to the temporary, encrypted - processing of your content as described in our Privacy Policy. You can - always choose to use only local editing features. + 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. </p> </section> <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold">Account and Service</h2> - <p>To use certain features, you may create an account:</p> - <ul className="list-disc space-y-2 pl-6"> - <li>Provide accurate information when signing up</li> - <li>Keep your account secure and don't share credentials</li> - <li>You're responsible for activity under your account</li> - <li>You can delete your account at any time</li> - </ul> + <h2 className="text-2xl font-semibold">Service</h2> <p> - OpenCut is provided "as is" without warranties. While we strive for - reliability, we can't guarantee uninterrupted service. + OpenCut 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> </section> @@ -189,24 +156,6 @@ export default function TermsPage() { </section> <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold">Third-Party Content</h2> - <p> - When using OpenCut, make sure you have the right to use any content - you import: - </p> - <ul className="list-disc space-y-2 pl-6"> - <li>Only upload content you own or have permission to use</li> - <li> - Respect copyright, trademarks, and other intellectual property - </li> - <li> - Don't use copyrighted music, images, or videos without permission - </li> - <li>You're responsible for any claims related to your content</li> - </ul> - </section> - - <section 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: @@ -239,13 +188,10 @@ export default function TermsPage() { </section> <section className="flex flex-col gap-3"> - <h2 className="text-2xl font-semibold">Termination</h2> + <h2 className="text-2xl font-semibold">Stopping Use</h2> <p>You can stop using OpenCut at any time:</p> <ul className="list-disc space-y-2 pl-6"> - <li>Delete your account through your profile settings</li> <li>Clear your browser data to remove local projects</li> - <li>Your content remains yours even if you stop using OpenCut</li> - <li>We may suspend accounts for violations of these terms</li> </ul> </section> @@ -288,7 +234,7 @@ export default function TermsPage() { </section> <Separator /> <p className="text-muted-foreground text-sm"> - Last updated: July 14, 2025 + Last updated: March 15, 2026 </p> </BasePage> ); |
