diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2e2dbcea..8253a59a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -112,7 +112,7 @@ If you're unsure whether your idea falls into the preview category, feel free to ```bash # Database (matches docker-compose.yaml) - DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" + DATABASE_URL="postgresql://openscript:openscript@localhost:5432/openscript" # Generate a secure secret for Better Auth BETTER_AUTH_SECRET="your-generated-secret-here" diff --git a/.github/workflows/bun-ci.yml b/.github/workflows/bun-ci.yml index 6310470a..8cd0ac5f 100644 --- a/.github/workflows/bun-ci.yml +++ b/.github/workflows/bun-ci.yml @@ -22,7 +22,7 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] env: - DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut" + DATABASE_URL: "postgresql://openscript:openscript@localhost:5432/openscript" BETTER_AUTH_SECRET: "supersecret" NEXT_PUBLIC_SITE_URL: "http://localhost:3000" UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url" diff --git a/apps/desktop/Cargo.toml b/apps/desktop/Cargo.toml index e09443ab..39f88a59 100644 --- a/apps/desktop/Cargo.toml +++ b/apps/desktop/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "opencut-desktop" +name = "openscript-desktop" version = "0.1.0" edition = "2021" [[bin]] -name = "opencut" +name = "openscript" path = "src/main.rs" [dependencies] diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 845a3140..87cf2e31 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -39,7 +39,7 @@ powershell -ExecutionPolicy Bypass -File .\apps\desktop\script\setup.ps1 **3. Run:** ```bash -cargo run -p opencut-desktop +cargo run -p openscript-desktop ``` ## Platform notes diff --git a/apps/web/.env.example b/apps/web/.env.example index b15779a5..d4b3610b 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -9,7 +9,7 @@ NEXT_PUBLIC_SITE_URL=http://localhost:3000 NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com # Server -DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" +DATABASE_URL="postgresql://openscript:openscript@localhost:5432/openscript" BETTER_AUTH_SECRET=your_better_auth_secret UPSTASH_REDIS_REST_URL=http://localhost:8079 diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index a08d9b68..12fac682 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -22,7 +22,7 @@ ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # Build-time env stubs to pass zod validation -ENV DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" +ENV DATABASE_URL="postgresql://openscript:openscript@localhost:5432/openscript" ENV BETTER_AUTH_SECRET="build-time-secret" ENV UPSTASH_REDIS_REST_URL="http://localhost:8079" ENV UPSTASH_REDIS_REST_TOKEN="example_token" diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 71d96c23..ae3c2944 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -9,6 +9,11 @@ const nextConfig: NextConfig = { reactStrictMode: true, productionBrowserSourceMaps: true, output: "standalone", + // Multi-Zones: this app is the editor "zone" and owns the /app path prefix. + // The marketing site (apps/website) serves "/" and rewrites /app/* here. + // basePath also namespaces this app's /_next assets under /app/_next so they + // never collide with the marketing zone's assets. + basePath: "/app", images: { remotePatterns: [ { diff --git a/apps/web/src/actions/registry.ts b/apps/web/src/actions/registry.ts index ece14eaf..d2b3469c 100644 --- a/apps/web/src/actions/registry.ts +++ b/apps/web/src/actions/registry.ts @@ -11,7 +11,7 @@ import type { type ActionHandler = (arg: unknown, trigger?: TInvocationTrigger) => void; const boundActions: Partial> = {}; -// eslint-disable-next-line opencut/prefer-object-params -- action registries read best as (action, handler). +// eslint-disable-next-line openscript/prefer-object-params -- action registries read best as (action, handler). export function bindAction( action: A, handler: TActionFunc, @@ -25,7 +25,7 @@ export function bindAction( } } -// eslint-disable-next-line opencut/prefer-object-params -- action registries read best as (action, handler). +// eslint-disable-next-line openscript/prefer-object-params -- action registries read best as (action, handler). export function unbindAction( action: A, handler: TActionFunc, @@ -54,7 +54,7 @@ type InvokeActionFunc = { ): void; }; -// eslint-disable-next-line opencut/prefer-object-params -- dispatchers conventionally separate action, payload, and trigger. +// eslint-disable-next-line openscript/prefer-object-params -- dispatchers conventionally separate action, payload, and trigger. export const invokeAction: InvokeActionFunc = ( action: A, args?: TArgOfAction, diff --git a/apps/web/src/actions/use-action-handler.ts b/apps/web/src/actions/use-action-handler.ts index d4a37b50..50e7ad40 100644 --- a/apps/web/src/actions/use-action-handler.ts +++ b/apps/web/src/actions/use-action-handler.ts @@ -8,7 +8,7 @@ import type { } from "@/actions"; import { bindAction, unbindAction } from "@/actions"; -// eslint-disable-next-line opencut/prefer-object-params -- action subscriptions read best as (action, handler, isActive). +// eslint-disable-next-line openscript/prefer-object-params -- action subscriptions read best as (action, handler, isActive). export function useActionHandler( action: A, handler: TActionFunc, diff --git a/apps/web/src/auth/client.ts b/apps/web/src/auth/client.ts index 58be51c3..ebe7b39a 100644 --- a/apps/web/src/auth/client.ts +++ b/apps/web/src/auth/client.ts @@ -1,6 +1,8 @@ import { createAuthClient } from "better-auth/react"; import { webEnv } from "@/env/web"; +import { BASE_PATH } from "@/site/brand"; export const { signIn, signUp, useSession } = createAuthClient({ - baseURL: webEnv.NEXT_PUBLIC_SITE_URL, + // Mirror the server: endpoints live under the /app basePath. + baseURL: `${webEnv.NEXT_PUBLIC_SITE_URL}${BASE_PATH}`, }); diff --git a/apps/web/src/auth/server.ts b/apps/web/src/auth/server.ts index 2e79231d..6cc53ea9 100644 --- a/apps/web/src/auth/server.ts +++ b/apps/web/src/auth/server.ts @@ -3,6 +3,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { Redis } from "@upstash/redis"; import { db } from "@/db"; import { webEnv } from "@/env/web"; +import { BASE_PATH } from "@/site/brand"; const redis = new Redis({ url: webEnv.UPSTASH_REDIS_REST_URL, @@ -35,8 +36,11 @@ export const auth = betterAuth({ }, }, }, - baseURL: webEnv.NEXT_PUBLIC_SITE_URL, + // baseURL includes the /app basePath so Better Auth's endpoints resolve to + // /app/api/auth/* — matching where Next mounts the route handler. + baseURL: `${webEnv.NEXT_PUBLIC_SITE_URL}${BASE_PATH}`, appName: "OpenScript", + // trustedOrigins is an origin check (scheme+host, no path) — leave unprefixed. trustedOrigins: [webEnv.NEXT_PUBLIC_SITE_URL], }); diff --git a/apps/web/src/feedback/components/feedback-popover.tsx b/apps/web/src/feedback/components/feedback-popover.tsx index f94b7253..44b946ef 100644 --- a/apps/web/src/feedback/components/feedback-popover.tsx +++ b/apps/web/src/feedback/components/feedback-popover.tsx @@ -20,6 +20,7 @@ import { clearFormDraft, } from "@/components/ui/form"; import type { FeedbackEntry } from "../types"; +import { BASE_PATH } from "@/site/brand"; const PERSIST_KEY = "feedback-draft"; const HISTORY_KEY = "feedback-history"; @@ -61,7 +62,7 @@ function useFeedback() { setIsSubmitting(true); try { - const res = await fetch("/api/feedback", { + const res = await fetch(`${BASE_PATH}/api/feedback`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(values), diff --git a/apps/web/src/site/brand.ts b/apps/web/src/site/brand.ts index 38fd1e08..eef17372 100644 --- a/apps/web/src/site/brand.ts +++ b/apps/web/src/site/brand.ts @@ -1,5 +1,11 @@ export const SITE_URL = "https://openscript.app"; +// Keep in sync with `basePath` in next.config.ts. The editor ships as a +// Next.js Multi-Zones "zone" mounted under /app, so its API routes and the +// auth baseURL all live beneath this prefix. (next/link & next/navigation are +// basePath-aware automatically; raw fetch() and Better Auth's baseURL are not.) +export const BASE_PATH = "/app"; + export const SITE_INFO = { title: "OpenScript", description: diff --git a/apps/web/src/subtitles/components/assets-view.tsx b/apps/web/src/subtitles/components/assets-view.tsx index 84480fa4..6a114360 100644 --- a/apps/web/src/subtitles/components/assets-view.tsx +++ b/apps/web/src/subtitles/components/assets-view.tsx @@ -64,7 +64,7 @@ const IDLE_STATE: ProcessingState = { warnings: [], }; -/* eslint-disable opencut/prefer-object-params -- React reducers must accept (state, action). */ +/* eslint-disable openscript/prefer-object-params -- React reducers must accept (state, action). */ function processingReducer( state: ProcessingState, action: ProcessingAction, @@ -81,7 +81,7 @@ function processingReducer( return { status: "idle", error: action.error, warnings: [] }; } } -/* eslint-enable opencut/prefer-object-params */ +/* eslint-enable openscript/prefer-object-params */ export function Captions() { const [selectedLanguage, setSelectedLanguage] = diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index 7162e584..6bd1cfda 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -1,6 +1,6 @@ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "opencut", + "name": "openscript", "main": ".open-next/worker.js", "compatibility_date": "2025-04-01", "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], @@ -11,7 +11,7 @@ "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "opencut" + "service": "openscript" } ] } diff --git a/apps/website/app/editor/page.tsx b/apps/website/app/editor/page.tsx deleted file mode 100644 index 5437f2a5..00000000 --- a/apps/website/app/editor/page.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Toolbar } from "@/components/editor/toolbar"; -import { MediaLibrary } from "@/components/editor/media-library"; -import { VideoPreview } from "@/components/editor/video-preview"; -import { PropertiesPanel } from "@/components/editor/properties-panel"; -import { TranscriptEditor } from "@/components/editor/transcript-editor"; -import { Timeline } from "@/components/editor/timeline"; - -export interface TranscriptSegment { - id: string; - text: string; - startTime: number; - endTime: number; - deleted?: boolean; -} - -export default function EditorPage() { - const [segments, setSegments] = useState([ - { - id: "1", - text: "Welcome to our local-first video editor. This is a revolutionary way to edit video content.", - startTime: 0, - endTime: 5, - }, - { - id: "2", - text: "Simply edit the text transcript, and the video automatically updates. No complex timeline editing needed.", - startTime: 5, - endTime: 11, - }, - { - id: "3", - text: "Delete a paragraph, and that section is removed from your video. It's that simple.", - startTime: 11, - endTime: 16, - }, - { - id: "4", - text: "Everything runs locally on your machine. Your footage never touches the cloud.", - startTime: 16, - endTime: 21, - }, - ]); - - const [isPlaying, setIsPlaying] = useState(false); - const [currentTime, setCurrentTime] = useState(0); - - const handleDeleteSegment = (id: string) => { - setSegments((prev) => - prev.map((seg) => (seg.id === id ? { ...seg, deleted: !seg.deleted } : seg)) - ); - }; - - const handlePlayPause = () => { - setIsPlaying(!isPlaying); - }; - - const totalDuration = segments - .filter((s) => !s.deleted) - .reduce((acc, s) => acc + (s.endTime - s.startTime), 0); - - return ( -
- {/* Toolbar */} - - - {/* Main Editor Area */} -
- {/* Left Sidebar - Media Library */} - - - {/* Center - Video Preview */} -
-
- -
- - {/* Bottom Section - Transcript & Timeline */} -
- - -
-
- - {/* Right Sidebar - Properties */} - -
-
- ); -} diff --git a/apps/website/app/globals.css b/apps/website/app/globals.css index 4121a21d..f4f102fe 100644 --- a/apps/website/app/globals.css +++ b/apps/website/app/globals.css @@ -24,7 +24,7 @@ --color-zinc-400: #a1a1aa; --color-zinc-300: #d4d4d8; - /* Accent color - OpenCut blue */ + /* Accent color - OpenScript blue */ --color-blue-500: #0070f3; --color-blue-600: #0761d1; diff --git a/apps/website/app/success/page.tsx b/apps/website/app/success/page.tsx index 7f6020bd..e5a41ebd 100644 --- a/apps/website/app/success/page.tsx +++ b/apps/website/app/success/page.tsx @@ -60,13 +60,13 @@ export default function SuccessPage() { See OpenScript in Action

- Check out our interactive demo to see how text-based video editing works + Jump into the beta editor — it runs entirely in your browser, no upload required

- Try the Demo → + Launch the beta → diff --git a/apps/website/components/editor/export-modal.tsx b/apps/website/components/editor/export-modal.tsx deleted file mode 100644 index acc43a2d..00000000 --- a/apps/website/components/editor/export-modal.tsx +++ /dev/null @@ -1,213 +0,0 @@ -"use client"; - -import { X, Download, Check } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; - -interface ExportModalProps { - isOpen: boolean; - onClose: () => void; -} - -export function ExportModal({ isOpen, onClose }: ExportModalProps) { - const [format, setFormat] = useState("mp4"); - const [quality, setQuality] = useState("high"); - const [isExporting, setIsExporting] = useState(false); - const [exportComplete, setExportComplete] = useState(false); - const [progress, setProgress] = useState(0); - - const handleExport = () => { - setIsExporting(true); - setProgress(0); - - // Simulate export progress - const interval = setInterval(() => { - setProgress((prev) => { - if (prev >= 100) { - clearInterval(interval); - setIsExporting(false); - setExportComplete(true); - return 100; - } - return prev + 10; - }); - }, 200); - }; - - const handleClose = () => { - setIsExporting(false); - setExportComplete(false); - setProgress(0); - onClose(); - }; - - return ( - - {isOpen && ( - <> - {/* Backdrop */} - - - {/* Modal */} - - {/* Header */} -
-

Export Video

- -
- - {/* Content */} -
- {!exportComplete ? ( - <> - {/* Format Selection */} -
- -
- {["mp4", "mov", "webm"].map((fmt) => ( - - ))} -
-
- - {/* Quality Selection */} -
- -
- {[ - { value: "high", label: "High (1080p)", size: "~45 MB" }, - { value: "medium", label: "Medium (720p)", size: "~25 MB" }, - { value: "low", label: "Low (480p)", size: "~12 MB" }, - ].map((q) => ( - - ))} -
-
- - {/* Progress Bar */} - {isExporting && ( -
-
- Exporting... - {progress}% -
-
- -
-
- )} - - ) : ( - /* Success State */ -
-
- -
-

- Export Complete! -

-

- Your video has been exported successfully. -

- -
- )} -
- - {/* Footer */} - {!exportComplete && ( -
- - -
- )} -
- - )} -
- ); -} diff --git a/apps/website/components/editor/media-library.tsx b/apps/website/components/editor/media-library.tsx deleted file mode 100644 index 01210b54..00000000 --- a/apps/website/components/editor/media-library.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import { Video, FileVideo, Music } from "lucide-react"; - -export function MediaLibrary() { - const mediaFiles = [ - { id: 1, name: "interview.mp4", duration: "2:34", type: "video" }, - { id: 2, name: "background-music.mp3", duration: "3:45", type: "audio" }, - ]; - - return ( -
- {/* Header */} -
-

Media

-
- - {/* Media List */} -
- {mediaFiles.map((file) => ( -
-
- {/* Thumbnail */} -
- {file.type === "video" ? ( - - ) : ( - - )} -
- - {/* Info */} -
-
{file.name}
-
{file.duration}
-
-
-
- ))} -
- - {/* Add Media Button */} -
- -
-
- ); -} diff --git a/apps/website/components/editor/properties-panel.tsx b/apps/website/components/editor/properties-panel.tsx deleted file mode 100644 index 13f5a94d..00000000 --- a/apps/website/components/editor/properties-panel.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client"; - -import { Volume2, Gauge, Sparkles, Settings } from "lucide-react"; - -export function PropertiesPanel() { - return ( -
- {/* Header */} -
-

Properties

-
- - {/* Properties List */} -
- {/* Volume */} -
-
- - Volume -
- -
80%
-
- - {/* Speed */} -
-
- - Speed -
-
- {["0.5x", "1x", "1.5x"].map((speed) => ( - - ))} -
-
- - {/* Effects */} -
-
- - Effects -
-
- {["Remove Filler Words", "Studio Sound", "Eye Contact"].map((effect) => ( - - ))} -
-
- - {/* Export Settings */} -
-
- - Export -
- -
-
-
- ); -} diff --git a/apps/website/components/editor/timeline.tsx b/apps/website/components/editor/timeline.tsx deleted file mode 100644 index 4e617ee8..00000000 --- a/apps/website/components/editor/timeline.tsx +++ /dev/null @@ -1,129 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import { TranscriptSegment } from "@/app/editor/page"; -import { motion, AnimatePresence } from "framer-motion"; - -interface TimelineProps { - segments: TranscriptSegment[]; - isPlaying: boolean; - currentTime: number; - onTimeUpdate: (time: number) => void; -} - -export function Timeline({ - segments, - isPlaying, - currentTime, - onTimeUpdate, -}: TimelineProps) { - const animationRef = useRef(undefined); - - useEffect(() => { - if (isPlaying) { - const startTime = Date.now(); - const initialTime = currentTime; - - const animate = () => { - const elapsed = (Date.now() - startTime) / 1000; - const newTime = initialTime + elapsed; - - const totalDuration = segments - .filter((s) => !s.deleted) - .reduce((acc, s) => acc + (s.endTime - s.startTime), 0); - - if (newTime >= totalDuration) { - onTimeUpdate(0); - } else { - onTimeUpdate(newTime); - animationRef.current = requestAnimationFrame(animate); - } - }; - - animationRef.current = requestAnimationFrame(animate); - - return () => { - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - } - }; - } - }, [isPlaying, currentTime, segments, onTimeUpdate]); - - const totalDuration = segments - .filter((s) => !s.deleted) - .reduce((acc, s) => acc + (s.endTime - s.startTime), 0); - - const getSegmentColor = (index: number) => { - const colors = [ - "bg-blue-500", - "bg-purple-500", - "bg-pink-500", - "bg-orange-500", - ]; - return colors[index % colors.length]; - }; - - return ( -
- {/* Timeline Header */} -
-

Timeline

-
- - {/* Timeline Content */} -
- {/* Timeline Track */} -
- {/* Segments */} -
- - {segments.map((segment, index) => { - if (segment.deleted) return null; - - const duration = segment.endTime - segment.startTime; - const widthPercent = (duration / totalDuration) * 100; - - return ( - - {/* Waveform decoration */} -
- {[...Array(20)].map((_, i) => ( -
- ))} -
- - ); - })} - -
- - {/* Playhead */} -
-
-
-
-
-
- ); -} diff --git a/apps/website/components/editor/toolbar.tsx b/apps/website/components/editor/toolbar.tsx deleted file mode 100644 index ca7fbc8d..00000000 --- a/apps/website/components/editor/toolbar.tsx +++ /dev/null @@ -1,93 +0,0 @@ -"use client"; - -import { File, FolderOpen, Save, Download, Undo, Redo, Scissors, Copy, Clipboard } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useState } from "react"; -import { ExportModal } from "./export-modal"; - -interface ToolbarProps { - totalDuration: number; -} - -export function Toolbar({ totalDuration }: ToolbarProps) { - const [showExportModal, setShowExportModal] = useState(false); - - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, "0")}`; - }; - - return ( - <> -
- {/* Project Title */} -
- My Project -
- - {/* Divider */} -
- - {/* File Actions */} -
- - - -
- - {/* Divider */} -
- - {/* Edit Actions */} -
- - - - - -
- - {/* Spacer */} -
- - {/* Duration */} -
- {formatTime(totalDuration)} -
- - {/* Export Button */} - -
- - {/* Export Modal */} - setShowExportModal(false)} - /> - - ); -} diff --git a/apps/website/components/editor/transcript-editor.tsx b/apps/website/components/editor/transcript-editor.tsx deleted file mode 100644 index a8812fd7..00000000 --- a/apps/website/components/editor/transcript-editor.tsx +++ /dev/null @@ -1,93 +0,0 @@ -"use client"; - -import { Trash2, Clock } from "lucide-react"; -import { TranscriptSegment } from "@/app/editor/page"; -import { Button } from "@/components/ui/button"; - -interface TranscriptEditorProps { - segments: TranscriptSegment[]; - onDeleteSegment: (id: string) => void; - currentTime: number; -} - -export function TranscriptEditor({ - segments, - onDeleteSegment, - currentTime, -}: TranscriptEditorProps) { - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, "0")}`; - }; - - const isSegmentActive = (segment: TranscriptSegment) => { - return currentTime >= segment.startTime && currentTime < segment.endTime; - }; - - return ( -
- {/* Header */} -
-

Transcript

-
- - {/* Transcript Content */} -
- {segments.map((segment) => ( -
- {/* Timestamp */} -
- - - {formatTime(segment.startTime)} - {formatTime(segment.endTime)} - -
- - {/* Text Content */} -

- {segment.text} -

- - {/* Delete Button */} - -
- ))} -
-
- ); -} diff --git a/apps/website/components/editor/video-preview.tsx b/apps/website/components/editor/video-preview.tsx deleted file mode 100644 index 1be62ab5..00000000 --- a/apps/website/components/editor/video-preview.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client"; - -import { useRef, useEffect, useState } from "react"; -import { Play, Pause } from "lucide-react"; -import { Button } from "@/components/ui/button"; - -interface VideoPreviewProps { - isPlaying: boolean; - currentTime: number; - onPlayPause: () => void; -} - -export function VideoPreview({ isPlaying, currentTime, onPlayPause }: VideoPreviewProps) { - const videoRef = useRef(null); - const [videoLoaded, setVideoLoaded] = useState(false); - - // Sync video playback with isPlaying prop - useEffect(() => { - if (!videoRef.current) return; - - if (isPlaying) { - videoRef.current.play(); - } else { - videoRef.current.pause(); - } - }, [isPlaying]); - - // Sync video currentTime with prop - useEffect(() => { - if (!videoRef.current) return; - const diff = Math.abs(videoRef.current.currentTime - currentTime); - // Only update if difference is significant to avoid constant updates - if (diff > 0.5) { - videoRef.current.currentTime = currentTime; - } - }, [currentTime]); - - const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, "0")}`; - }; - - return ( -
- {/* Video Preview Area */} -
-
- {/* Actual Video Element */} - - - {/* Fallback if video fails to load */} - {!videoLoaded && ( -
-
- Loading video... -
-
- )} - - {/* Play/Pause Overlay */} -
- -
-
-
- - {/* Playback Controls */} -
- - -
- {formatTime(currentTime)} -
- - {/* Progress Bar */} -
-
-
- -
- {formatTime(21)} -
-
-
- ); -} diff --git a/apps/website/components/hero-section.tsx b/apps/website/components/hero-section.tsx index a07b0e55..44215fc5 100644 --- a/apps/website/components/hero-section.tsx +++ b/apps/website/components/hero-section.tsx @@ -65,24 +65,31 @@ export function HeroSection() { No cloud uploads. No monthly fees. - {/* Waitlist CTA */} + {/* Beta CTA */} - diff --git a/apps/website/next.config.ts b/apps/website/next.config.ts index e9ffa308..a68c9660 100644 --- a/apps/website/next.config.ts +++ b/apps/website/next.config.ts @@ -1,7 +1,22 @@ import type { NextConfig } from "next"; +// Multi-Zones: this is the default zone (serves "/"). The editor lives in +// apps/web under basePath "/app" and is stitched in here via rewrites, so the +// whole product is one origin: marketing at "/", editor at "/app". +// +// APP_ZONE_URL is the editor zone's own deployment URL (e.g. its Vercel URL or +// a custom domain). Locally, run the editor with `bun run dev:web` and it will +// be proxied from here. Set APP_ZONE_URL in the marketing site's environment +// for preview/production deploys. +const APP_ZONE_URL = process.env.APP_ZONE_URL ?? "http://localhost:3000"; + const nextConfig: NextConfig = { - /* config options here */ + async rewrites() { + return [ + { source: "/app", destination: `${APP_ZONE_URL}/app` }, + { source: "/app/:path*", destination: `${APP_ZONE_URL}/app/:path*` }, + ]; + }, }; export default nextConfig; diff --git a/docker-compose.yml b/docker-compose.yml index 0c4983b3..34045173 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,15 +3,15 @@ services: image: postgres:17 restart: unless-stopped environment: - POSTGRES_USER: opencut - POSTGRES_PASSWORD: opencut - POSTGRES_DB: opencut + POSTGRES_USER: openscript + POSTGRES_PASSWORD: openscript + POSTGRES_DB: openscript volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: - test: ["CMD-SHELL", "pg_isready -U opencut"] + test: ["CMD-SHELL", "pg_isready -U openscript"] interval: 30s timeout: 10s retries: 5 @@ -61,7 +61,7 @@ services: - "3100:3000" environment: - NODE_ENV=production - - DATABASE_URL=postgresql://opencut:opencut@db:5432/opencut + - DATABASE_URL=postgresql://openscript:openscript@db:5432/openscript - BETTER_AUTH_SECRET=your-production-secret-key-here - UPSTASH_REDIS_REST_URL=http://serverless-redis-http:80 - UPSTASH_REDIS_REST_TOKEN=example_token @@ -87,4 +87,4 @@ volumes: networks: default: - name: opencut-network + name: openscript-network diff --git a/eslint.config.mjs b/eslint.config.mjs index fd56f7d0..7232e430 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,9 +10,9 @@ import preferObjectParams from "./eslint/rules/prefer-object-params.mjs"; const webFiles = ["apps/web/src/**/*.{ts,tsx}"]; -const opencutEslintPlugin = { +const openscriptEslintPlugin = { meta: { - name: "eslint-plugin-opencut", + name: "eslint-plugin-openscript", version: "0.0.0", }, rules: { @@ -67,7 +67,7 @@ export default [ { files: webFiles, plugins: { - opencut: opencutEslintPlugin, + openscript: openscriptEslintPlugin, }, rules: { "@typescript-eslint/no-empty-object-type": "warn", @@ -82,7 +82,7 @@ export default [ }, ], "no-empty": "warn", - "opencut/prefer-object-params": "error", + "openscript/prefer-object-params": "error", // `react/prop-types` is for the JS-era React workflow where runtime // `propTypes` declarations are the prop contract. In this TS-only diff --git a/wrangler.jsonc b/wrangler.jsonc index 00b280ef..53cf9211 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "opencut", + "name": "openscript", "main": ".open-next/worker.js", "compatibility_date": "2025-04-01", "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], @@ -10,7 +10,7 @@ "services": [ { "binding": "WORKER_SELF_REFERENCE", - "service": "opencut", + "service": "openscript", }, ], }