diff --git a/components/LandingPage.tsx b/components/LandingPage.tsx index d73f2f0..3797640 100644 --- a/components/LandingPage.tsx +++ b/components/LandingPage.tsx @@ -10,24 +10,20 @@ import { Video, MoveRight, Play, - PenTool, - Keyboard, - BellRing, - FolderOpen, - FileDown, - History, - Smartphone, - Link as LinkIcon, - CheckSquare, - MessageSquare, - Github, - ArrowRight, - XCircle, - ArrowDown, - CheckCircle, + Mic, + Users, + Tag, + Code, + Lock, Upload, Share2, + MessageSquare, Check, + CheckCircle2, + Copy, + Link as LinkIcon, + Github, + ArrowRight, } from 'lucide-react'; interface LandingPageProps { @@ -37,57 +33,162 @@ interface LandingPageProps { const controlButtonClass = 'group relative isolate inline-flex h-8 items-center justify-center overflow-hidden border border-border bg-background px-2.5 text-[11px] font-medium text-foreground transition-colors duration-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:h-9 sm:px-4 sm:text-xs'; -const coreWorkflowFeatures = [ +const primaryCtaClass = + 'group relative isolate inline-flex min-h-12 items-center justify-center gap-2 overflow-hidden border border-primary bg-primary px-6 py-3 text-center text-[13px] font-semibold text-primary-foreground transition-colors duration-300 hover:bg-primary/90 sm:px-8 sm:text-sm'; + +const labelClass = 'text-[11px] uppercase tracking-[0.14em] text-muted-foreground'; + +const trustSignals = [ + { label: 'No client accounts', icon: Users }, + { label: 'Flat $10 per month', icon: Tag }, + { label: 'Fair Source, self-hostable', icon: Code }, + { label: 'Private by default', icon: Lock }, +]; + +const steps = [ { - title: 'Version Compare', - description: 'Compare any two versions side-by-side on a single timeline.', - icon: History, + label: 'Upload a cut', + description: 'Drop a file, or import an unlisted YouTube video.', + icon: Upload, }, { - title: 'Asset Management', - description: 'Keep images and supplementary videos grouped perfectly per cut.', - icon: FolderOpen, + label: 'Share the link', + description: 'Set permissions once, the client needs no account.', + icon: Share2, }, { - title: 'Version History', - description: 'Infinite versioning. Toggle between V1 and V10 without losing where you are.', - icon: History, + label: 'Timestamped feedback', + description: 'Comments, voice notes and drawings land on the frame.', + icon: MessageSquare, }, { - title: 'Approval Workflow', - description: - 'Assign specific team members or clients to review and sign off on a cut. Get an exact \"Approved\" status.', - icon: CheckCircle, + label: 'Approve and move on', + description: 'The cut gets a signed off Approved status, export the notes.', + icon: Check, }, ]; -const workflowAcceleratorFeatures = [ +const hostedFeatures = [ + 'Unlimited collaborators and clients', + 'Comments, voice notes, annotations', + 'Version compare, history, approvals', + 'Permissioned share links, PDF and CSV export', + 'Unlimited unlisted YouTube imports', + '200 GB storage, add 100 GB for $5/mo', +]; + +const selfHostedFeatures = [ + 'Full source code, read it and audit it', + 'Docker setup for self-hosting', + 'Every release becomes Apache 2.0 two years after publication', +]; + +const faq = [ { - title: 'Keyboard Shortcuts', - description: 'J, K, L, Space, and M controls for professional editing workflows.', - icon: Keyboard, + q: 'Do clients need an account?', + a: 'No. They can review in the browser with a share link.', }, { - title: 'PDF/CSV Exports', - description: 'Turn video comments into a professional feedback report in one click.', - icon: FileDown, + q: 'Is OpenFrame open source?', + a: 'OpenFrame is Fair Source, licensed under the Functional Source License (FSL). You can read and audit the full source code and self-host it, and every release automatically becomes Apache 2.0 open source two years after publication.', }, { - title: 'Real-time Webhooks', - description: - 'Get instant Telegram alerts the second a comment is dropped. More integrations coming soon.', - icon: BellRing, + q: 'Is there a free trial?', + a: 'Yes. Hosted Cloud starts with a 7-day free trial and never asks for a card to begin it. After that it is a flat $10/mo, with no per-seat or per-client fees.', }, { - title: 'Mobile-Optimized Review', - description: 'Touch-optimized player for clients reviewing cuts on the move.', - icon: Smartphone, + q: 'How is this different from a Google Drive link?', + a: 'Drive does not give timestamped discussion, voice notes, annotations, or version compare, which is where approval time is actually saved.', + }, + { + q: 'What happens if I exceed my storage?', + a: 'You can add 100 GB for $5/mo. If you need much more, contact us at info@open-frame.net and we will help you choose the best setup.', + }, + { + q: 'Can I self-host?', + a: 'Yes. The full source code is public and ships with a Docker setup for self-hosting. Hosted Cloud is for teams who want zero setup.', }, ]; +// Bar heights for the voice note waveform, in percent. The first twenty read +// as "played", the rest as "remaining". +const waveformBars = [ + 28, 52, 74, 40, 96, 62, 34, 80, 46, 90, 38, 66, 88, 30, 72, 50, 84, 42, 94, 56, 32, 68, 44, 86, + 36, 60, 92, 48, 26, 70, 54, 82, 38, 64, +]; + +const shareOptions = [ + { label: 'Can comment', on: true }, + { label: 'Ask for a name before commenting', on: true }, + { label: 'Allow download of the original file', on: false }, + { label: 'Show earlier versions', on: false }, +]; + +const heroGridStyle = { + backgroundImage: + 'linear-gradient(to right, color-mix(in oklab, var(--foreground) 5%, transparent) 1px, transparent 1px), linear-gradient(to bottom, color-mix(in oklab, var(--foreground) 5%, transparent) 1px, transparent 1px)', + backgroundSize: '48px 48px', + maskImage: + 'radial-gradient(ellipse 820px 640px at 50% 30%, #000 0%, rgba(0,0,0,0.6) 52%, transparent 80%)', + WebkitMaskImage: + 'radial-gradient(ellipse 820px 640px at 50% 30%, #000 0%, rgba(0,0,0,0.6) 52%, transparent 80%)', +} as const; + +const heroGlowStyle = { + background: + 'radial-gradient(closest-side, color-mix(in oklab, var(--primary) 18%, transparent), color-mix(in oklab, var(--primary) 6%, transparent) 55%, transparent 100%)', +} as const; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ); +} + +function MockToolbar({ left, right }: { left: React.ReactNode; right?: React.ReactNode }) { + return ( +
+ + {left} + + {right} +
+ ); +} + +function Avatar({ initial }: { initial: string }) { + return ( +
+ {initial} +
+ ); +} + +function Timecode({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +function Toggle({ on }: { on: boolean }) { + return ( +