feat(landing): redesign homepage with GSAP-driven landing page and new showcase assets
@@ -124,3 +124,20 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noise-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 40;
|
||||||
|
opacity: 0.03;
|
||||||
|
filter: url(#openframe-noise);
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noise-overlay::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--foreground);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { JetBrains_Mono } from "next/font/google";
|
import { Geist_Mono, JetBrains_Mono } from "next/font/google";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
@@ -11,6 +11,13 @@ const jetbrainsMono = JetBrains_Mono({
|
|||||||
preload: true,
|
preload: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
subsets: ['latin'],
|
||||||
|
variable: '--font-geist-mono',
|
||||||
|
display: 'swap',
|
||||||
|
preload: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "OpenFrame - Video Feedback Platform",
|
title: "OpenFrame - Video Feedback Platform",
|
||||||
description: "Collect timestamped video feedback with text and voice comments",
|
description: "Collect timestamped video feedback with text and voice comments",
|
||||||
@@ -22,15 +29,21 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={jetbrainsMono.variable} suppressHydrationWarning>
|
<html lang="en" className={`${jetbrainsMono.variable} ${geistMono.variable}`} suppressHydrationWarning>
|
||||||
<body className="antialiased min-h-screen bg-background">
|
<body className="antialiased min-h-screen bg-background font-sans">
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="dark"
|
defaultTheme="dark"
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
|
<svg aria-hidden="true" className="fixed h-0 w-0">
|
||||||
|
<filter id="openframe-noise">
|
||||||
|
<feTurbulence type="fractalNoise" baseFrequency="0.92" numOctaves="2" stitchTiles="stitch" />
|
||||||
|
</filter>
|
||||||
|
</svg>
|
||||||
{children}
|
{children}
|
||||||
|
<div aria-hidden="true" className="noise-overlay" />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,411 +1,8 @@
|
|||||||
import Link from 'next/link';
|
import { LandingPage } from '@/components/LandingPage';
|
||||||
import { Video, MessageSquare, Mic, Share2, ArrowRight, Play, Github, Server, History, Users, Clock, ArrowUpRight, PenTool, Image as ImageIcon, Sparkles, FileText } from 'lucide-react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const isLoggedIn = !!session?.user;
|
|
||||||
|
|
||||||
return (
|
return <LandingPage isLoggedIn={Boolean(session?.user)} />;
|
||||||
<div className="min-h-screen bg-background selection:bg-primary/30">
|
|
||||||
{/* Header */}
|
|
||||||
<header className="border-b bg-background/80 backdrop-blur-md sticky top-0 z-50">
|
|
||||||
<div className="px-6 lg:px-12 flex h-16 items-center justify-between w-full max-w-7xl mx-auto">
|
|
||||||
<Link href="/" className="flex items-center gap-2 group">
|
|
||||||
<div className="bg-primary/10 p-2 rounded-xl group-hover:bg-primary/20 transition-colors">
|
|
||||||
<Video className="h-5 w-5 text-primary" />
|
|
||||||
</div>
|
|
||||||
<span className="font-bold text-xl tracking-tight">OpenFrame</span>
|
|
||||||
</Link>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link href="https://github.com/yusufipk/OpenFrame" target="_blank" className="text-muted-foreground hover:text-foreground transition-colors hidden sm:flex items-center gap-2 text-sm font-medium">
|
|
||||||
<Github className="h-4 w-4" />
|
|
||||||
Star on GitHub
|
|
||||||
</Link>
|
|
||||||
{isLoggedIn ? (
|
|
||||||
<Button asChild variant="default" className="rounded-full px-6">
|
|
||||||
<Link href="/dashboard">Dashboard</Link>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button asChild variant="ghost" className="hidden sm:inline-flex">
|
|
||||||
<Link href="/login">Sign in</Link>
|
|
||||||
</Button>
|
|
||||||
<Button asChild className="rounded-full px-6 shadow-lg shadow-primary/20">
|
|
||||||
<Link href="/register">Get Started</Link>
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
{/* Hero Section */}
|
|
||||||
<section className="relative px-6 pt-10 pb-16 md:pt-10 md:pb-24 overflow-hidden">
|
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-background to-background pointer-events-none" />
|
|
||||||
<div className="relative flex flex-col items-center text-center max-w-4xl mx-auto z-10 space-y-6">
|
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-sm font-medium border border-primary/20">
|
|
||||||
<span className="flex h-2 w-2 rounded-full bg-primary animate-pulse"></span>
|
|
||||||
Open Source Video Feedback Tool
|
|
||||||
</div>
|
|
||||||
<h1 className="text-5xl md:text-7xl font-bold tracking-tighter leading-[1.1]">
|
|
||||||
Review videos without <br className="hidden md:block" />
|
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-primary/60">
|
|
||||||
losing your mind.
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-xl text-muted-foreground max-w-2xl leading-relaxed mt-2">
|
|
||||||
Paste a YouTube link or upload directly. Get timestamped comments, voice notes, and version control in one beautiful frame. Stop chasing feedback in Slack threads.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto pt-4">
|
|
||||||
{isLoggedIn ? (
|
|
||||||
<Button asChild size="lg" className="rounded-full h-12 px-8 text-base">
|
|
||||||
<Link href="/dashboard">
|
|
||||||
Go to Dashboard
|
|
||||||
<ArrowRight className="h-4 w-4 ml-2" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button asChild size="lg" className="rounded-full h-12 px-8 text-base shadow-xl shadow-primary/20">
|
|
||||||
<Link href="/register">
|
|
||||||
Start for free
|
|
||||||
<ArrowRight className="h-4 w-4 ml-2" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button asChild variant="outline" size="lg" className="rounded-full h-12 px-8 text-base bg-background">
|
|
||||||
<Link href="#features">
|
|
||||||
Explore Features
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Hero Video */}
|
|
||||||
<div className="max-w-6xl mx-auto mt-20 relative rounded-2xl border border-border/50 bg-card/50 shadow-2xl overflow-hidden aspect-video flex items-center justify-center group">
|
|
||||||
<video
|
|
||||||
autoPlay
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
playsInline
|
|
||||||
className="absolute inset-0 w-full h-full object-cover"
|
|
||||||
>
|
|
||||||
<source src="/hero-demo.mp4" type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-tr from-primary/5 via-transparent to-transparent opacity-50 pointer-events-none" />
|
|
||||||
<div className="relative z-10 flex flex-col items-center gap-4 text-muted-foreground/0 group-hover:text-muted-foreground/60 transition-all duration-500">
|
|
||||||
<Play className="h-16 w-16" />
|
|
||||||
<p className="font-medium text-sm tracking-widest uppercase">Preview</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Feature 1: Visual Left, Text Right */}
|
|
||||||
<section id="features" className="px-6 py-24 md:py-32 bg-accent/30 border-y">
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center flex-col-reverse lg:flex-row">
|
|
||||||
<div className="order-1 lg:order-1 aspect-square md:aspect-video lg:aspect-square bg-card rounded-3xl border shadow-xl flex items-center justify-center p-8 relative overflow-hidden w-full">
|
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/5 to-transparent" />
|
|
||||||
{/* Visual Placeholder for Comments */}
|
|
||||||
<div className="flex flex-col gap-4 w-full max-w-md relative z-10">
|
|
||||||
<div className="bg-background border rounded-lg p-4 shadow-sm flex gap-4 opacity-80 translate-y-4">
|
|
||||||
<div className="h-8 w-8 rounded-full bg-primary/20 shrink-0" />
|
|
||||||
<div className="space-y-2 w-full">
|
|
||||||
<div className="h-2 w-24 bg-muted rounded" />
|
|
||||||
<div className="h-2 w-full bg-muted rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-background border-2 border-primary/20 rounded-lg p-4 shadow-md flex gap-4 transform scale-105">
|
|
||||||
<div className="h-8 w-8 rounded-full bg-primary flex items-center justify-center text-[10px] font-bold text-primary-foreground">YI</div>
|
|
||||||
<div className="space-y-3 w-full">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs font-bold text-foreground">Yusuf İpek</span>
|
|
||||||
<div className="inline-flex items-center gap-1 bg-primary/10 text-primary px-1.5 py-0.5 rounded text-[10px] font-bold">
|
|
||||||
<Clock className="h-3 w-3" />
|
|
||||||
0:39
|
|
||||||
<ArrowUpRight className="h-2 w-2 ml-0.5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Mic className="h-3 w-3 text-primary" />
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-medium leading-tight">"Let's change the color to red."</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="order-2 lg:order-2 space-y-8 w-full">
|
|
||||||
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
||||||
<MessageSquare className="h-6 w-6 text-primary" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-4xl font-bold tracking-tight">Pinpoint exactly what needs fixing.</h2>
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
|
||||||
Stop guessing what "that thing around 2 minutes in" means.
|
|
||||||
Drop timestamped comments directly on the timeline. Feeling lazy? Leave a voice note instead.
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-4">
|
|
||||||
{['Threaded replies to keep conversations clean', 'Mark comments as resolved', 'Voice recording with Web Audio API'].map((item, i) => (
|
|
||||||
<li key={i} className="flex items-center gap-3 text-foreground font-medium">
|
|
||||||
<div className="h-6 w-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
</div>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Feature 2: Text Left, Visual Right */}
|
|
||||||
<section className="px-6 py-24 md:py-32">
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center flex-col-reverse lg:flex-row">
|
|
||||||
<div className="order-2 lg:order-1 space-y-8 w-full">
|
|
||||||
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
||||||
<History className="h-6 w-6 text-primary" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-4xl font-bold tracking-tight">Never lose track of a version again.</h2>
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
|
||||||
"Final_v3_ACTUAL_final_v2.mp4" is dead. Upload or link new versions cleanly under the same project. Compare iterations and see what actually changed.
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-4">
|
|
||||||
{['Clean v1, v2, v3 organization', 'Keep old comments preserved on previous versions', 'Support for YouTube, Vimeo, and direct links'].map((item, i) => (
|
|
||||||
<li key={i} className="flex items-center gap-3 text-foreground font-medium">
|
|
||||||
<div className="h-6 w-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
</div>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="order-1 lg:order-2 aspect-square md:aspect-video lg:aspect-square bg-card rounded-3xl border shadow-xl flex items-center justify-center p-8 relative overflow-hidden w-full">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-background to-muted/50" />
|
|
||||||
<div className="relative z-10 w-full max-w-sm space-y-3">
|
|
||||||
{[3, 2, 1].map((v) => (
|
|
||||||
<div key={v} className={`p-4 rounded-xl border flex items-center justify-between ${v === 3 ? 'bg-background shadow-md border-primary/30' : 'bg-muted/30 opacity-60'}`}>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Video className="h-5 w-5 text-muted-foreground" />
|
|
||||||
<span className="font-semibold">Version {v}</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs text-muted-foreground">{v === 3 ? 'Current' : '3 days ago'}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Feature 3: Visual Left, Text Right */}
|
|
||||||
<section className="px-6 py-24 md:py-32 bg-accent/30 border-y">
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center flex-col-reverse lg:flex-row">
|
|
||||||
<div className="order-1 lg:order-1 aspect-square md:aspect-video lg:aspect-square bg-card rounded-3xl border shadow-xl flex items-center justify-center p-8 relative overflow-hidden w-full">
|
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom,_var(--tw-gradient-stops))] from-primary/5 to-transparent" />
|
|
||||||
<div className="relative z-10 grid grid-cols-2 gap-4 w-full max-w-sm">
|
|
||||||
<div className="bg-background rounded-xl p-6 border text-center space-y-2 shadow-sm">
|
|
||||||
<Users className="h-6 w-6 mx-auto text-primary" />
|
|
||||||
<div className="font-semibold text-sm">Workspace</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-background rounded-xl p-6 border text-center space-y-2 shadow-sm">
|
|
||||||
<Share2 className="h-6 w-6 mx-auto text-primary" />
|
|
||||||
<div className="font-semibold text-sm">Guest Links</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-2 bg-primary text-primary-foreground rounded-xl p-4 flex items-center justify-center gap-2 font-medium shadow-md">
|
|
||||||
Notifications (Email & Telegram)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="order-2 lg:order-2 space-y-8 w-full">
|
|
||||||
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
||||||
<Share2 className="h-6 w-6 text-primary" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-4xl font-bold tracking-tight">Share seamlessly.</h2>
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
|
||||||
Invite your core team to your workspace, or generate public links for clients.
|
|
||||||
Guests can leave comments without dealing with the friction of creating an account.
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-4">
|
|
||||||
{['Public, Private, and Invite-only projects', 'Frictionless guest commenting', 'Telegram & Email webhook integrations'].map((item, i) => (
|
|
||||||
<li key={i} className="flex items-center gap-3 text-foreground font-medium">
|
|
||||||
<div className="h-6 w-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
</div>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Feature 4: Text Left, Visual Right */}
|
|
||||||
<section className="px-6 py-24 md:py-32">
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center flex-col-reverse lg:flex-row">
|
|
||||||
<div className="order-2 lg:order-1 space-y-8 w-full">
|
|
||||||
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
||||||
<PenTool className="h-6 w-6 text-primary" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-4xl font-bold tracking-tight">Communicate visually.</h2>
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
|
||||||
Sometimes words aren't enough. Draw directly on video frames to highlight exact details, and attach reference pictures directly to your comments.
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-4">
|
|
||||||
{['Draw and annotate on frames', 'Attach images to comments', 'Leave no room for ambiguity'].map((item, i) => (
|
|
||||||
<li key={i} className="flex items-center gap-3 text-foreground font-medium">
|
|
||||||
<div className="h-6 w-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
</div>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="order-1 lg:order-2 aspect-square md:aspect-video lg:aspect-square bg-card rounded-3xl border shadow-xl flex items-center justify-center p-8 relative overflow-hidden w-full">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-background to-muted/50" />
|
|
||||||
<div className="relative z-10 w-full max-w-sm space-y-6">
|
|
||||||
<div className="relative rounded-xl overflow-hidden border shadow-lg aspect-video bg-muted/30 flex items-center justify-center">
|
|
||||||
<Play className="h-8 w-8 text-muted-foreground/30" />
|
|
||||||
<svg className="absolute inset-0 w-full h-full text-primary stroke-current opacity-60" fill="none" viewBox="0 0 100 100" strokeWidth="2">
|
|
||||||
<ellipse cx="60" cy="45" rx="25" ry="35" className="animate-[pulse_3s_ease-in-out_infinite]" />
|
|
||||||
</svg>
|
|
||||||
<div className="absolute top-4 right-4 bg-background/90 p-2 rounded-lg border shadow-sm flex items-center justify-center">
|
|
||||||
<PenTool className="h-4 w-4 text-primary" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-background rounded-xl p-4 border shadow-xl flex gap-4 ml-8 transform rotate-1">
|
|
||||||
<div className="h-8 w-8 rounded-full bg-primary/20 flex items-center justify-center text-[10px] font-bold text-primary shrink-0">AI</div>
|
|
||||||
<div className="space-y-2 w-full">
|
|
||||||
<p className="text-sm font-medium">"Reference for the color grade:"</p>
|
|
||||||
<div className="h-20 w-32 bg-muted rounded-lg border border-dashed flex items-center justify-center overflow-hidden bg-[url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?auto=format&fit=crop&q=80&w=200')] bg-cover bg-center">
|
|
||||||
<ImageIcon className="h-5 w-5 text-white/50" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Feature 5: Visual Left, Text Right */}
|
|
||||||
<section className="px-6 py-24 md:py-32 bg-accent/30 border-y">
|
|
||||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center flex-col-reverse lg:flex-row">
|
|
||||||
<div className="order-1 lg:order-1 aspect-square md:aspect-video lg:aspect-square bg-card rounded-3xl border shadow-xl flex items-center justify-center p-8 relative overflow-hidden w-full">
|
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_left,_var(--tw-gradient-stops))] from-primary/5 to-transparent" />
|
|
||||||
<div className="relative z-10 w-full max-w-sm space-y-4">
|
|
||||||
{/* AI Summary */}
|
|
||||||
<div className="bg-background rounded-2xl p-5 border shadow-xl relative overflow-hidden">
|
|
||||||
<div className="absolute top-0 left-0 w-1.5 h-full bg-gradient-to-b from-primary to-primary/40" />
|
|
||||||
<div className="flex items-center gap-2 mb-3">
|
|
||||||
<Sparkles className="h-4 w-4 text-primary" />
|
|
||||||
<span className="text-[10px] font-black text-primary uppercase tracking-[0.2em]">AI Intelligence</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-medium leading-relaxed italic text-foreground/90">"User suggests the transition at 0:45 is too sharp. Prefer a smoother fade to match the music."</p>
|
|
||||||
</div>
|
|
||||||
{/* Export */}
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="group bg-background flex-1 rounded-2xl p-4 border shadow-md flex items-center justify-center gap-3 font-bold text-sm text-foreground/80 hover:bg-primary hover:text-primary-foreground hover:border-primary transition-all cursor-pointer">
|
|
||||||
<FileText className="h-5 w-5 text-primary group-hover:text-primary-foreground transition-colors" /> PDF Export
|
|
||||||
</div>
|
|
||||||
<div className="group bg-background flex-1 rounded-2xl p-4 border shadow-md flex items-center justify-center gap-3 font-bold text-sm text-foreground/80 hover:bg-primary hover:text-primary-foreground hover:border-primary transition-all cursor-pointer">
|
|
||||||
<FileText className="h-5 w-5 text-primary group-hover:text-primary-foreground transition-colors" /> CSV Export
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="order-2 lg:order-2 space-y-8 w-full">
|
|
||||||
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
|
|
||||||
<Sparkles className="h-6 w-6 text-primary" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-4xl font-bold tracking-tight">Powerful workflows.</h2>
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
|
||||||
Let AI summarize long-winded voice notes automatically, and export all your project feedback into neat PDF or CSV files ready for the team.
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-4">
|
|
||||||
{['AI-powered voice summaries', 'One-click PDF/CSV exports', 'Structured feedback data'].map((item, i) => (
|
|
||||||
<li key={i} className="flex items-center gap-3 text-foreground font-medium">
|
|
||||||
<div className="h-6 w-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
|
||||||
<div className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
</div>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Business Model Section */}
|
|
||||||
<section className="px-6 py-32 relative overflow-hidden">
|
|
||||||
<div className="absolute inset-0 bg-background border-t" />
|
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[400px] bg-primary/10 blur-[120px] rounded-full pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="relative max-w-4xl mx-auto text-center space-y-12 z-10">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h2 className="text-4xl md:text-5xl font-bold tracking-tight">Open Source or Managed. <br /> Your call.</h2>
|
|
||||||
<p className="text-xl text-muted-foreground w-full max-w-2xl mx-auto">
|
|
||||||
We believe in code freedom. Self-host it if you like configuring servers, or use our managed platform if you value your time.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6 text-left items-stretch">
|
|
||||||
<div className="bg-card border rounded-3xl p-8 shadow-sm relative overflow-hidden flex flex-col h-full">
|
|
||||||
<div className="absolute top-0 right-0 p-6 opacity-10">
|
|
||||||
<Github className="h-24 w-24 z-0" />
|
|
||||||
</div>
|
|
||||||
{/* Visual alignment block to match the other card's badge */}
|
|
||||||
<div className="mb-12 relative z-10 pt-8">
|
|
||||||
<h3 className="text-2xl font-bold mb-2">Self-Hosted</h3>
|
|
||||||
<p className="text-muted-foreground">Grab the code. Deploy it on your own infrastructure. It's 100% free and open source.</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-auto relative z-10">
|
|
||||||
<Button asChild variant="outline" className="w-full h-12 rounded-xl">
|
|
||||||
<a href="https://github.com/yusufipk/OpenFrame" target="_blank" rel="noreferrer">
|
|
||||||
<Github className="mr-2 h-4 w-4" /> View Source
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gradient-to-br from-primary/10 to-primary/5 border-primary/20 border rounded-3xl p-8 shadow-lg relative overflow-hidden flex flex-col h-full">
|
|
||||||
<div className="absolute top-0 right-0 p-6 opacity-10">
|
|
||||||
<Server className="h-24 w-24 text-primary z-0" />
|
|
||||||
</div>
|
|
||||||
<div className="mb-12 relative z-10">
|
|
||||||
<div className="inline-flex items-center self-start gap-2 px-3 py-1 rounded-full bg-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-2 border border-primary/20">
|
|
||||||
Recommended
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-2">Managed Hosting</h3>
|
|
||||||
<p className="text-muted-foreground text-foreground/80">Skip the DevOps. Use open-frame.net. We handle the servers, storage, and updates.</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-auto relative z-10">
|
|
||||||
<Button asChild className="w-full h-12 rounded-xl shadow-lg shadow-primary/20">
|
|
||||||
<Link href="/register">Start on open-frame.net</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<footer className="border-t py-12 bg-card relative z-10">
|
|
||||||
<div className="px-6 lg:px-8 max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-6">
|
|
||||||
<div className="flex items-center gap-2 opacity-80">
|
|
||||||
<Video className="h-5 w-5" />
|
|
||||||
<span className="font-semibold tracking-tight">OpenFrame</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-8 text-sm text-muted-foreground font-medium">
|
|
||||||
<Link href="https://github.com/yusufipk/OpenFrame" className="hover:text-primary transition-colors">GitHub</Link>
|
|
||||||
<Link href="/login" className="hover:text-primary transition-colors">Sign In</Link>
|
|
||||||
<Link href="/register" className="hover:text-primary transition-colors">Get Started</Link>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground opacity-60">
|
|
||||||
© {new Date().getFullYear()} OpenFrame. Constructed with precision.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"gsap": "^3.14.2",
|
||||||
"hls.js": "^1.6.15",
|
"hls.js": "^1.6.15",
|
||||||
"lucide-react": "^0.563.0",
|
"lucide-react": "^0.563.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
@@ -1140,6 +1141,8 @@
|
|||||||
|
|
||||||
"graphql": ["[email protected]", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
|
"graphql": ["[email protected]", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
|
||||||
|
|
||||||
|
"gsap": ["[email protected]", "", {}, "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA=="],
|
||||||
|
|
||||||
"has-bigints": ["[email protected]", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
"has-bigints": ["[email protected]", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||||
|
|
||||||
"has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
"has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|||||||
@@ -0,0 +1,960 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { gsap } from 'gsap';
|
||||||
|
import {
|
||||||
|
CheckSquare,
|
||||||
|
Clock3,
|
||||||
|
FileVideo,
|
||||||
|
GitBranch,
|
||||||
|
Github,
|
||||||
|
GitMerge,
|
||||||
|
Grid3X3,
|
||||||
|
Layers3,
|
||||||
|
MessageSquare,
|
||||||
|
MessageSquareDot,
|
||||||
|
Mic,
|
||||||
|
MoveRight,
|
||||||
|
PenTool,
|
||||||
|
Play,
|
||||||
|
Upload,
|
||||||
|
Video,
|
||||||
|
Waves,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface LandingPageProps {
|
||||||
|
isLoggedIn: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 primaryButtonClass =
|
||||||
|
'group relative isolate inline-flex h-10 items-center justify-center overflow-hidden border border-primary bg-primary px-4 text-[11px] font-medium text-primary-foreground transition-transform duration-300 hover:scale-[1.02] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:h-11 sm:px-5 sm:text-xs';
|
||||||
|
|
||||||
|
const secondaryButtonClass =
|
||||||
|
'group relative isolate inline-flex h-10 items-center justify-center overflow-hidden border border-border bg-background px-4 text-[11px] font-medium text-foreground transition-colors duration-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:h-11 sm:px-5 sm:text-xs';
|
||||||
|
|
||||||
|
const workflowProofItems = [
|
||||||
|
{
|
||||||
|
step: '01',
|
||||||
|
title: 'Upload Or Import',
|
||||||
|
description: 'Drop MP4 files or import YouTube links into one project timeline.',
|
||||||
|
kind: 'upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: '02',
|
||||||
|
title: 'Comment At Timestamp',
|
||||||
|
description: 'Text and voice notes are attached to exact moments on the timeline.',
|
||||||
|
kind: 'comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step: '03',
|
||||||
|
title: 'Ship Next Version',
|
||||||
|
description: 'Upload a new cut while preserving feedback history and review context.',
|
||||||
|
kind: 'version',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const useCaseRails = [
|
||||||
|
{
|
||||||
|
title: 'Agency Teams',
|
||||||
|
label: 'CLIENT REVIEW',
|
||||||
|
description: 'Share guest links and collect timestamped approvals without account friction.',
|
||||||
|
image:
|
||||||
|
'https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=1200&q=80',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Solo Editors',
|
||||||
|
label: 'PERSONAL QC',
|
||||||
|
description: 'Freelance editors can share review links with clients and close notes without account friction.',
|
||||||
|
image:
|
||||||
|
'https://images.unsplash.com/photo-1627244714766-94dab62ed964?q=80&w=765&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Internal Teams',
|
||||||
|
label: 'CAMPAIGN OPS',
|
||||||
|
description: 'Coordinate editors, PMs, and stakeholders across one shared workspace.',
|
||||||
|
image:
|
||||||
|
'https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1200&q=80',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function formatTime(totalSeconds: number): string {
|
||||||
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
return `00:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LandingPage({ isLoggedIn }: LandingPageProps) {
|
||||||
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const navbarRef = useRef<HTMLElement | null>(null);
|
||||||
|
const timelineCounterRef = useRef<HTMLSpanElement | null>(null);
|
||||||
|
const heroAnnotationCircleRef = useRef<SVGCircleElement | null>(null);
|
||||||
|
const featureAnnotationCircleRef = useRef<SVGCircleElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanupHandlers: Array<() => void> = [];
|
||||||
|
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
gsap.from('[data-hero-copy]', {
|
||||||
|
y: 30,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.9,
|
||||||
|
stagger: 0.12,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.from('[data-reveal]', {
|
||||||
|
y: 24,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.8,
|
||||||
|
stagger: 0.08,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.to('.hero-float-card', {
|
||||||
|
y: -14,
|
||||||
|
duration: 2.6,
|
||||||
|
stagger: 0.2,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const timelineCounter = timelineCounterRef.current;
|
||||||
|
|
||||||
|
if (timelineCounter) {
|
||||||
|
const playhead = { seconds: 83 };
|
||||||
|
|
||||||
|
gsap.to(playhead, {
|
||||||
|
seconds: 224,
|
||||||
|
duration: 12,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'none',
|
||||||
|
onUpdate: () => {
|
||||||
|
timelineCounter.textContent = formatTime(Math.floor(playhead.seconds));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const timelineDrops = gsap.utils.toArray<HTMLElement>('.timeline-drop');
|
||||||
|
|
||||||
|
timelineDrops.forEach((drop, index) => {
|
||||||
|
const timeline = gsap.timeline({ repeat: -1, delay: index * 0.35 });
|
||||||
|
|
||||||
|
timeline
|
||||||
|
.fromTo(
|
||||||
|
drop,
|
||||||
|
{ y: -18, opacity: 0 },
|
||||||
|
{ y: 0, opacity: 1, duration: 0.8, ease: 'power3.out' }
|
||||||
|
)
|
||||||
|
.to(drop, { opacity: 0, duration: 0.35, ease: 'power3.out' }, '+=0.5');
|
||||||
|
});
|
||||||
|
|
||||||
|
const waveformBars = gsap.utils.toArray<HTMLElement>('.voice-bar');
|
||||||
|
|
||||||
|
waveformBars.forEach((bar, index) => {
|
||||||
|
gsap.set(bar, { transformOrigin: 'center bottom' });
|
||||||
|
gsap.to(bar, {
|
||||||
|
scaleY: gsap.utils.random(0.35, 1.45),
|
||||||
|
duration: gsap.utils.random(0.45, 0.9),
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
delay: index * 0.03,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
'.voice-scan',
|
||||||
|
{ width: '0%' },
|
||||||
|
{
|
||||||
|
width: '100%',
|
||||||
|
duration: 2.4,
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
ease: 'power3.out',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const animateStroke = (shape: SVGCircleElement | null, duration: number) => {
|
||||||
|
if (!shape) return;
|
||||||
|
|
||||||
|
const shapeLength = shape.getTotalLength();
|
||||||
|
|
||||||
|
gsap.set(shape, {
|
||||||
|
strokeDasharray: shapeLength,
|
||||||
|
strokeDashoffset: shapeLength,
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.to(shape, {
|
||||||
|
strokeDashoffset: 0,
|
||||||
|
duration,
|
||||||
|
repeat: -1,
|
||||||
|
yoyo: true,
|
||||||
|
repeatDelay: 0.6,
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
animateStroke(heroAnnotationCircleRef.current, 2.4);
|
||||||
|
animateStroke(featureAnnotationCircleRef.current, 2.1);
|
||||||
|
|
||||||
|
const nav = navbarRef.current;
|
||||||
|
|
||||||
|
if (nav) {
|
||||||
|
const updateNavbar = () => {
|
||||||
|
const hasScrolled = window.scrollY > 12;
|
||||||
|
|
||||||
|
gsap.to(nav, {
|
||||||
|
backgroundColor: hasScrolled
|
||||||
|
? 'color-mix(in oklab, var(--background) 80%, transparent)'
|
||||||
|
: 'transparent',
|
||||||
|
backdropFilter: hasScrolled ? 'blur(12px)' : 'blur(0px)',
|
||||||
|
duration: 0.3,
|
||||||
|
overwrite: 'auto',
|
||||||
|
ease: 'power3.out',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateNavbar();
|
||||||
|
window.addEventListener('scroll', updateNavbar, { passive: true });
|
||||||
|
cleanupHandlers.push(() => window.removeEventListener('scroll', updateNavbar));
|
||||||
|
}
|
||||||
|
|
||||||
|
const magneticItems = gsap.utils.toArray<HTMLElement>('[data-magnetic]');
|
||||||
|
|
||||||
|
magneticItems.forEach((item) => {
|
||||||
|
const xTo = gsap.quickTo(item, 'x', { duration: 0.35, ease: 'power3.out' });
|
||||||
|
const yTo = gsap.quickTo(item, 'y', { duration: 0.35, ease: 'power3.out' });
|
||||||
|
|
||||||
|
const onMove = (event: MouseEvent) => {
|
||||||
|
const bounds = item.getBoundingClientRect();
|
||||||
|
const x = event.clientX - (bounds.left + bounds.width / 2);
|
||||||
|
const y = event.clientY - (bounds.top + bounds.height / 2);
|
||||||
|
|
||||||
|
xTo((x / bounds.width) * 10);
|
||||||
|
yTo((y / bounds.height) * 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLeave = () => {
|
||||||
|
xTo(0);
|
||||||
|
yTo(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
item.addEventListener('mousemove', onMove);
|
||||||
|
item.addEventListener('mouseleave', onLeave);
|
||||||
|
|
||||||
|
cleanupHandlers.push(() => {
|
||||||
|
item.removeEventListener('mousemove', onMove);
|
||||||
|
item.removeEventListener('mouseleave', onLeave);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, rootRef);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cleanupHandlers.forEach((cleanup) => cleanup());
|
||||||
|
ctx.revert();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={rootRef} className="min-h-screen overflow-x-hidden bg-background text-foreground font-sans selection:bg-primary/20">
|
||||||
|
<header ref={navbarRef} className="fixed inset-x-0 top-0 z-50 border-b border-border bg-transparent">
|
||||||
|
<div className="mx-auto flex h-14 w-full max-w-[1200px] items-center justify-between px-4 sm:h-16 sm:px-6 lg:px-10">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
data-magnetic
|
||||||
|
className="group relative isolate inline-flex items-center gap-2 overflow-hidden border border-border bg-background px-3 py-2"
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<Video className="relative z-10 h-4 w-4 text-primary" />
|
||||||
|
<span className="relative z-10 text-xs font-semibold tracking-[0.12em]">OPENFRAME</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<nav className="hidden items-center gap-6 text-xs font-medium uppercase tracking-[0.14em] md:flex">
|
||||||
|
<Link className="text-muted-foreground transition-colors hover:text-foreground" href="#features">
|
||||||
|
Features
|
||||||
|
</Link>
|
||||||
|
<Link className="text-muted-foreground transition-colors hover:text-foreground" href="#pricing">
|
||||||
|
Pricing
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1.5 sm:gap-2">
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<>
|
||||||
|
<Link href="/dashboard" data-magnetic className={controlButtonClass}>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10 inline-flex items-center gap-1.5 sm:gap-2">
|
||||||
|
<span className="sm:hidden">Dashboard</span>
|
||||||
|
<span className="hidden sm:inline">Go to Dashboard</span>
|
||||||
|
<MoveRight className="h-3.5 w-3.5" />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<Link href="/dashboard" data-magnetic className={controlButtonClass}>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10">Workspace</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link href="/login" data-magnetic className={controlButtonClass}>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10">Login</span>
|
||||||
|
</Link>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<Link href="/register" data-magnetic className={controlButtonClass}>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10">Get Started</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="relative">
|
||||||
|
<section className="min-h-screen border-b border-border px-4 pb-20 pt-24 sm:px-6 sm:pb-24 sm:pt-28 lg:pt-32">
|
||||||
|
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-background via-background to-secondary/40" />
|
||||||
|
<div className="relative mx-auto grid min-h-[calc(100vh-8rem)] w-full max-w-[1200px] items-center gap-10 lg:grid-cols-[1fr_1.05fr] lg:gap-14">
|
||||||
|
<div className="space-y-7">
|
||||||
|
<p data-hero-copy className="font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground">
|
||||||
|
VIDEO FEEDBACK PROTOCOL
|
||||||
|
</p>
|
||||||
|
<h1 data-hero-copy className="max-w-2xl text-4xl font-semibold leading-[1.03] tracking-[-0.03em] sm:text-5xl md:text-8xl">
|
||||||
|
Review Video. Without the Chaos.
|
||||||
|
</h1>
|
||||||
|
<p data-hero-copy className="max-w-xl text-base text-muted-foreground md:text-xl">
|
||||||
|
Timestamped text, voice feedback, and version control for modern creative teams.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div data-hero-copy className="flex flex-col gap-3 sm:flex-row">
|
||||||
|
<Link
|
||||||
|
href={isLoggedIn ? '/dashboard' : '/register'}
|
||||||
|
data-magnetic
|
||||||
|
className={primaryButtonClass}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary-foreground/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10 inline-flex items-center gap-2">
|
||||||
|
Start using OpenFrame
|
||||||
|
<MoveRight className="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://github.com/yusufipk/OpenFrame"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
data-magnetic
|
||||||
|
className={secondaryButtonClass}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10 inline-flex items-center gap-2">
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
GitHub Repo
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-hero-copy className="relative">
|
||||||
|
<div className="relative border border-border bg-card p-3">
|
||||||
|
<div className="mb-3 flex items-center justify-between border border-border/50 bg-background px-3 py-2">
|
||||||
|
<div className="inline-flex items-center gap-2 text-xs">
|
||||||
|
<Play className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono text-muted-foreground">PLAYHEAD 00:02:14</span>
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[11px] text-muted-foreground">v2.3.1</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative aspect-[16/10] overflow-hidden border border-border/50 bg-background">
|
||||||
|
<Image
|
||||||
|
src="https://images.unsplash.com/photo-1492684223066-81342ee5ff30?auto=format&fit=crop&w=1400&q=80"
|
||||||
|
alt="Annotated video frame preview"
|
||||||
|
fill
|
||||||
|
className="object-cover opacity-75"
|
||||||
|
sizes="(min-width: 1024px) 50vw, 100vw"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-background/20 via-background/5 to-background/35" />
|
||||||
|
|
||||||
|
<svg className="pointer-events-none absolute inset-0" viewBox="0 0 640 400" fill="none">
|
||||||
|
<circle
|
||||||
|
ref={heroAnnotationCircleRef}
|
||||||
|
cx="462"
|
||||||
|
cy="198"
|
||||||
|
r="88"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
className="text-primary"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div className="absolute left-4 top-4 border border-border/70 bg-popover px-3 py-2 text-xs">
|
||||||
|
<p className="font-mono text-muted-foreground">ANNOTATION</p>
|
||||||
|
<p className="mt-1">Focus this subject isolation for scene 04.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hero-float-card absolute -left-7 top-8 hidden border border-border bg-popover px-4 py-3 shadow-sm sm:block">
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<MessageSquare className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono text-muted-foreground">00:01:23</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-xs">Re-time this beat hit by 4 frames.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hero-float-card absolute -right-5 bottom-6 hidden border border-border bg-popover px-4 py-3 shadow-sm sm:block">
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<Mic className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono text-muted-foreground">Voice Note</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-xs">Transition pacing reads cleaner in this cut.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="features" className="scroll-mt-20 border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 flex flex-col gap-4">
|
||||||
|
<h2 className="text-3xl font-semibold tracking-[-0.02em] md:text-5xl">
|
||||||
|
Collaborative video review as precision software.
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||||
|
Guest commenting via shareable links and workspace-level permissions are available on every project.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-12 md:auto-rows-[minmax(180px,auto)]">
|
||||||
|
<article data-reveal className="border border-border bg-card p-4 sm:p-6 md:col-span-7">
|
||||||
|
<div className="mb-5 flex items-center justify-between">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<Clock3 className="h-4 w-4 text-primary" />
|
||||||
|
<h3 className="text-sm font-semibold">The Timeline</h3>
|
||||||
|
</div>
|
||||||
|
<span ref={timelineCounterRef} className="font-mono text-xs text-primary">
|
||||||
|
00:01:23
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="max-w-md text-sm text-muted-foreground">
|
||||||
|
Comments attach to exact timestamps so every request references the exact moment on the video.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-6 border border-border/50 bg-background p-4">
|
||||||
|
<div className="mb-3 flex items-center justify-between text-[11px] font-mono text-muted-foreground">
|
||||||
|
<span>SEQUENCE.A / MASTER CUT</span>
|
||||||
|
<span>24 FPS</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative h-20 border border-border/50 bg-card">
|
||||||
|
<div className="absolute left-4 right-4 top-1/2 h-px -translate-y-1/2 bg-border" />
|
||||||
|
<div className="absolute left-[14%] top-1/2 h-3 w-px -translate-y-1/2 bg-border" />
|
||||||
|
<div className="absolute left-[38%] top-1/2 h-3 w-px -translate-y-1/2 bg-border" />
|
||||||
|
<div className="absolute left-[64%] top-1/2 h-3 w-px -translate-y-1/2 bg-border" />
|
||||||
|
<div className="absolute left-[88%] top-1/2 h-3 w-px -translate-y-1/2 bg-border" />
|
||||||
|
|
||||||
|
<div className="timeline-drop absolute left-[22%] top-1.5 flex flex-col items-center gap-1">
|
||||||
|
<span className="font-mono text-[10px] text-primary">00:00:23</span>
|
||||||
|
<span className="h-4 w-px bg-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="timeline-drop absolute left-[46%] top-1.5 flex flex-col items-center gap-1">
|
||||||
|
<span className="font-mono text-[10px] text-primary">00:00:52</span>
|
||||||
|
<span className="h-4 w-px bg-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="timeline-drop absolute left-[74%] top-1.5 flex flex-col items-center gap-1">
|
||||||
|
<span className="font-mono text-[10px] text-primary">00:01:34</span>
|
||||||
|
<span className="h-4 w-px bg-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article data-reveal className="border border-border bg-card p-4 sm:p-6 md:col-span-5">
|
||||||
|
<div className="mb-4 inline-flex items-center gap-2">
|
||||||
|
<Waves className="h-4 w-4 text-primary" />
|
||||||
|
<h3 className="text-sm font-semibold">Voice Notes</h3>
|
||||||
|
</div>
|
||||||
|
<p className="mb-5 text-sm text-muted-foreground">
|
||||||
|
Record a voice note at any timestamp. Teammates play it back in context.
|
||||||
|
</p>
|
||||||
|
<div className="border border-border/50 bg-background p-4">
|
||||||
|
<div className="mb-3 inline-flex items-center gap-2 border border-border/50 bg-card px-3 py-1.5 text-[11px]">
|
||||||
|
<span className="h-1.5 w-1.5 animate-pulse bg-primary" />
|
||||||
|
<span className="font-mono text-muted-foreground">REC 00:00:12</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative overflow-hidden border border-border/50 bg-card px-3 py-4">
|
||||||
|
<div className="voice-scan pointer-events-none absolute inset-y-0 left-0 bg-primary/10" />
|
||||||
|
<div className="relative flex h-12 items-end gap-1">
|
||||||
|
{Array.from({ length: 22 }).map((_, index) => (
|
||||||
|
<span key={index} className="voice-bar h-full w-full bg-primary/80" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 font-mono text-[11px] text-muted-foreground">MIC-01 / SAVED TO 00:01:23</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article data-reveal className="border border-border bg-card p-4 sm:p-6 md:col-span-5">
|
||||||
|
<div className="mb-4 inline-flex items-center gap-2">
|
||||||
|
<Layers3 className="h-4 w-4 text-primary" />
|
||||||
|
<h3 className="text-sm font-semibold">Version Management</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Upload multiple cuts under one video and keep each version named and ordered.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-6 border border-border/50 bg-background p-3">
|
||||||
|
<div className="version-row flex items-center justify-between border border-border/50 bg-card px-3 py-2 text-xs">
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<FileVideo className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono">v1.0.0 - rough-cut</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">Archived</span>
|
||||||
|
</div>
|
||||||
|
<div className="version-row mt-2 flex items-center justify-between border border-border/50 bg-card px-3 py-2 text-xs">
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<FileVideo className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono">v1.0.1 - client-notes</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground">Previous</span>
|
||||||
|
</div>
|
||||||
|
<div className="version-row mt-2 flex items-center justify-between border border-primary/40 bg-secondary px-3 py-2 text-xs">
|
||||||
|
<span className="inline-flex items-center gap-2">
|
||||||
|
<FileVideo className="h-3.5 w-3.5 text-primary" />
|
||||||
|
<span className="font-mono">v1.0.2 - current</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-primary">Active</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 inline-flex items-center gap-2 border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<Upload className="h-4 w-4 text-primary" />
|
||||||
|
<span className="font-mono text-muted-foreground">Upload New Version</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article data-reveal className="border border-border bg-card p-4 sm:p-6 md:col-span-7">
|
||||||
|
<div className="mb-4 inline-flex items-center gap-2">
|
||||||
|
<PenTool className="h-4 w-4 text-primary" />
|
||||||
|
<h3 className="text-sm font-semibold">Drawing & Annotation</h3>
|
||||||
|
</div>
|
||||||
|
<p className="mb-5 text-sm text-muted-foreground">
|
||||||
|
Draw directly on the video image to point at exact areas during review.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-[1fr_auto]">
|
||||||
|
<div className="relative aspect-[16/7] overflow-hidden border border-border/50 bg-background">
|
||||||
|
<Image
|
||||||
|
src="https://images.unsplash.com/photo-1516724562728-afc824a36e84?auto=format&fit=crop&w=1200&q=80"
|
||||||
|
alt="Video frame with annotation overlay"
|
||||||
|
fill
|
||||||
|
className="object-cover opacity-80"
|
||||||
|
sizes="(min-width: 1024px) 45vw, 100vw"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-background/20" />
|
||||||
|
<svg className="pointer-events-none absolute inset-0" viewBox="0 0 640 280" fill="none">
|
||||||
|
<circle
|
||||||
|
ref={featureAnnotationCircleRef}
|
||||||
|
cx="208"
|
||||||
|
cy="142"
|
||||||
|
r="58"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
className="text-primary"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center border border-border/50 bg-background px-4 py-3 text-xs">
|
||||||
|
<span className="font-mono text-muted-foreground">DRAW ON FRAME / SAVE WITH COMMENT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 space-y-4">
|
||||||
|
<h2 className="max-w-3xl text-3xl font-semibold tracking-[-0.02em] md:text-5xl">
|
||||||
|
Upload. Review. Version. In one continuous loop.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-3">
|
||||||
|
{workflowProofItems.map((item) => (
|
||||||
|
<article key={item.step} data-reveal className="border border-border bg-card p-4">
|
||||||
|
<div className="relative mb-4 aspect-[4/3] border border-border/50 bg-background p-3 sm:aspect-[16/10]">
|
||||||
|
{item.kind === 'upload' && (
|
||||||
|
<div className="h-full border border-border/50 bg-card p-3">
|
||||||
|
<div className="mb-2 flex items-center text-[11px]">
|
||||||
|
<p className="inline-flex items-center gap-1.5 font-mono text-muted-foreground">
|
||||||
|
<Upload className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Add Source
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2 flex gap-2 text-[10px]">
|
||||||
|
<span className="flex-1 border border-primary/40 bg-secondary px-2 py-1 font-mono text-primary">Paste URL</span>
|
||||||
|
<span className="flex-1 border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Direct Upload</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5 text-[10px]">
|
||||||
|
<div className="truncate border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">https://youtube.com/watch?v=...</div>
|
||||||
|
<div className="border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Video title (optional)</div>
|
||||||
|
<div className="h-8 border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Description (optional)</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 flex justify-end">
|
||||||
|
<span className="shrink-0 border border-primary/40 bg-secondary px-2 py-1 font-mono text-[10px] text-primary">
|
||||||
|
Add Video
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.kind === 'comment' && (
|
||||||
|
<div className="h-full border border-border/50 bg-card p-3">
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<div className="inline-flex items-center gap-2">
|
||||||
|
<span className="inline-flex h-5 w-5 items-center justify-center border border-primary/40 bg-secondary font-mono text-[10px] text-primary">
|
||||||
|
Y
|
||||||
|
</span>
|
||||||
|
<span className="font-mono text-[11px]">Yusuf İpek</span>
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[10px] text-muted-foreground">00:04:14</span>
|
||||||
|
</div>
|
||||||
|
<p className="font-mono text-xs leading-relaxed text-foreground/90">
|
||||||
|
wrong tweet here's what you should use:
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 truncate font-mono text-xs text-primary">https://x.com/...</p>
|
||||||
|
<div className="mt-3 flex items-center justify-between text-[10px]">
|
||||||
|
<span className="font-mono text-muted-foreground">2/21/2026</span>
|
||||||
|
<span className="border border-primary/40 bg-secondary px-2 py-1 font-mono text-primary">Feedback</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 border border-border/50 bg-background px-2 py-1 text-[10px] font-mono text-muted-foreground">
|
||||||
|
↩ Reply
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.kind === 'version' && (
|
||||||
|
<div className="h-full border border-border/50 bg-card p-3">
|
||||||
|
<div className="mb-3 flex items-center justify-between text-[11px]">
|
||||||
|
<p className="inline-flex items-center gap-1.5 font-mono text-muted-foreground">
|
||||||
|
<FileVideo className="h-3.5 w-3.5 text-primary" />
|
||||||
|
New Version
|
||||||
|
</p>
|
||||||
|
<p className="font-mono text-muted-foreground">v1.0.3</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2 flex gap-2 text-[10px]">
|
||||||
|
<span className="flex-1 border border-primary/40 bg-secondary px-2 py-1 font-mono text-primary">Link URL</span>
|
||||||
|
<span className="flex-1 border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Upload File</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 text-[10px]">
|
||||||
|
<div className="border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Video URL</div>
|
||||||
|
<div className="border border-border/50 bg-background px-2 py-1 font-mono text-muted-foreground">Version Label</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex justify-end">
|
||||||
|
<span className="border border-primary/40 bg-secondary px-2 py-1 text-[10px] font-mono text-primary">Ship Version</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="font-mono text-[11px] text-primary">{item.step}</p>
|
||||||
|
<h3 className="text-base font-semibold">{item.title}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 space-y-4">
|
||||||
|
<h2 className="max-w-3xl text-3xl font-semibold tracking-[-0.02em] md:text-5xl">
|
||||||
|
One screen. Full review context.
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||||
|
This panel maps exactly where timestamp comments, voice notes, and frame annotation live.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-reveal className="relative border border-border bg-card p-4 lg:p-6">
|
||||||
|
<div className="relative aspect-[1888/1048] overflow-hidden border border-border/50 bg-background">
|
||||||
|
<Image
|
||||||
|
src="/landing/deep-dive-dashboard.webp"
|
||||||
|
alt="OpenFrame dashboard deep dive screenshot"
|
||||||
|
fill
|
||||||
|
className="object-contain opacity-100"
|
||||||
|
sizes="100vw"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-background/10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 grid gap-2 md:grid-cols-3">
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<MessageSquareDot className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Timestamp Comments
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Attached to exact timeline positions.</p>
|
||||||
|
</div>
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<Mic className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Voice Notes
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Captured in context, played back in thread.</p>
|
||||||
|
</div>
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<PenTool className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Frame Annotation
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Circle and draw directly on the frame.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 space-y-4">
|
||||||
|
<h2 className="max-w-3xl text-3xl font-semibold tracking-[-0.02em] md:text-5xl">
|
||||||
|
Sync 2-4 videos and compare cuts in real time.
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||||
|
Play multiple versions side-by-side with synchronized playback to validate pacing, edit decisions, and grading changes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article data-reveal className="border border-border bg-card p-4 lg:p-6">
|
||||||
|
<div className="relative aspect-[1888/1048] overflow-hidden border border-border/50 bg-background">
|
||||||
|
<Image
|
||||||
|
src="/landing/compare-v2.webp"
|
||||||
|
alt="OpenFrame compare mode screenshot"
|
||||||
|
fill
|
||||||
|
className="object-contain opacity-100"
|
||||||
|
sizes="100vw"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-background/10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 grid gap-2 md:grid-cols-3">
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<GitMerge className="h-3.5 w-3.5 text-primary" />
|
||||||
|
2-4 Video Panels
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Load multiple versions into one comparison view.</p>
|
||||||
|
</div>
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<Play className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Synchronized Playback
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Play, pause, and scrub all panels at once.</p>
|
||||||
|
</div>
|
||||||
|
<div className="border border-border/50 bg-background px-3 py-2 text-xs">
|
||||||
|
<p className="inline-flex items-center gap-2 font-mono text-muted-foreground">
|
||||||
|
<MessageSquareDot className="h-3.5 w-3.5 text-primary" />
|
||||||
|
Compare Feedback Impact
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-muted-foreground">Confirm that requested changes are visible across versions.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 space-y-4">
|
||||||
|
<h2 className="max-w-3xl text-3xl font-semibold tracking-[-0.02em] md:text-5xl">
|
||||||
|
Built for agencies, solo editors, and internal teams.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-3">
|
||||||
|
{useCaseRails.map((item) => (
|
||||||
|
<article key={item.title} data-reveal className="border border-border bg-card p-4">
|
||||||
|
<div className="relative mb-4 aspect-[16/10] overflow-hidden border border-border/50 bg-background">
|
||||||
|
<Image src={item.image} alt={`${item.title} screenshot`} fill className="object-cover opacity-90" sizes="(min-width: 1024px) 33vw, 100vw" />
|
||||||
|
<div className="absolute inset-0 bg-background/15" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground">{item.label}</p>
|
||||||
|
<h3 className="mt-2 text-base font-semibold">{item.title}</h3>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground">{item.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="pricing" className="scroll-mt-20 border-b border-border px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
|
||||||
|
<div className="mx-auto w-full max-w-[1200px]">
|
||||||
|
<div data-reveal className="mb-10 space-y-4">
|
||||||
|
<h2 className="text-3xl font-semibold tracking-[-0.02em] md:text-5xl">Deploy self-hosted, or run fully managed.</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<article data-reveal className="flex h-full flex-col border border-border bg-card p-7">
|
||||||
|
<div className="mb-8 space-y-4">
|
||||||
|
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">For the builders.</p>
|
||||||
|
<h3 className="text-2xl font-semibold">Open Source (Self-Hosted)</h3>
|
||||||
|
<p className="font-mono text-3xl text-primary">Free / OSS</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mb-10 space-y-3 text-sm text-muted-foreground">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<GitBranch className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Full codebase access</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<Grid3X3 className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Self-hosted infrastructure control</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<Clock3 className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Manual update cadence</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://github.com/yusufipk/OpenFrame"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
data-magnetic
|
||||||
|
className={secondaryButtonClass}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10 inline-flex items-center gap-2">
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
View GitHub
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article data-reveal className="relative flex h-full flex-col border border-primary/30 bg-secondary p-7 shadow-sm">
|
||||||
|
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-primary/8 to-transparent" />
|
||||||
|
<div className="relative mb-8 space-y-4">
|
||||||
|
<p className="font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
|
||||||
|
For modern creative teams.
|
||||||
|
</p>
|
||||||
|
<h3 className="text-2xl font-semibold">Hosted Cloud</h3>
|
||||||
|
<p className="font-mono text-3xl text-primary">$10 / month</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="relative mb-10 space-y-3 text-sm text-foreground">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckSquare className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Unlimited Seats (Collaborators/Guests)</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckSquare className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Unlimited Workspaces & Projects</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckSquare className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Unlimited YouTube video imports</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckSquare className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>100 GB of direct video upload storage (+$5 per next 100 GB)</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<CheckSquare className="mt-0.5 h-4 w-4 text-primary" />
|
||||||
|
<span>Instant Webhook (Telegram/Email) Setup</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={isLoggedIn ? '/dashboard' : '/register'}
|
||||||
|
data-magnetic
|
||||||
|
className={`${primaryButtonClass} relative`}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute inset-0 -translate-x-[101%] bg-primary-foreground/10 transition-transform duration-300 group-hover:translate-x-0" />
|
||||||
|
<span className="relative z-10 inline-flex items-center gap-2">
|
||||||
|
Deploy on Cloud
|
||||||
|
<MoveRight className="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="border-t border-border bg-background">
|
||||||
|
<div className="mx-auto grid w-full max-w-[1200px] gap-8 px-4 py-12 sm:px-6 md:grid-cols-4 lg:px-10">
|
||||||
|
<div>
|
||||||
|
<p className="mb-2 text-sm font-semibold">OpenFrame</p>
|
||||||
|
<p className="max-w-xs text-sm text-muted-foreground">
|
||||||
|
Fast video review with timestamped feedback, version tracking, and guest collaboration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-3 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Legal</p>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<Link href="/" className="block text-muted-foreground transition-colors hover:text-foreground">
|
||||||
|
Terms
|
||||||
|
</Link>
|
||||||
|
<Link href="/" className="block text-muted-foreground transition-colors hover:text-foreground">
|
||||||
|
Privacy
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-3 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Open Source</p>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<a
|
||||||
|
href="https://github.com/yusufipk/OpenFrame"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="block text-muted-foreground transition-colors hover:text-foreground"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<Link href="#pricing" className="block text-muted-foreground transition-colors hover:text-foreground">
|
||||||
|
Pricing
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-3 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Console</p>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<a
|
||||||
|
href="https://x.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="block text-muted-foreground transition-colors hover:text-foreground"
|
||||||
|
>
|
||||||
|
Twitter
|
||||||
|
</a>
|
||||||
|
<Link href="/" className="block text-muted-foreground transition-colors hover:text-foreground">
|
||||||
|
Docs
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"gsap": "^3.14.2",
|
||||||
"hls.js": "^1.6.15",
|
"hls.js": "^1.6.15",
|
||||||
"lucide-react": "^0.563.0",
|
"lucide-react": "^0.563.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 6.0 KiB |