From bbf44fd0aa92abf6b53a4fac964d3cce8774cc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Thu, 26 Feb 2026 15:19:11 +0300 Subject: [PATCH] feat: Introduce `pauseAfterSeek` option to the video player, refresh landing page content and animations, and update the dashboard image. --- components/LandingPage.tsx | 1315 +++++++---------- components/video-page/comments-pane.tsx | 8 +- .../video-page/hooks/use-video-player.ts | 10 +- public/landing/compare.webp | Bin 49706 -> 0 bytes public/landing/deep-dive-dashboard-2.webp | Bin 0 -> 60138 bytes public/landing/deep-dive-dashboard.webp | Bin 65462 -> 0 bytes public/landing/workflow-comment.webp | Bin 14456 -> 0 bytes public/landing/workflow-upload.webp | Bin 8676 -> 0 bytes public/landing/workflow-version.webp | Bin 6102 -> 0 bytes 9 files changed, 512 insertions(+), 821 deletions(-) delete mode 100644 public/landing/compare.webp create mode 100644 public/landing/deep-dive-dashboard-2.webp delete mode 100644 public/landing/deep-dive-dashboard.webp delete mode 100644 public/landing/workflow-comment.webp delete mode 100644 public/landing/workflow-upload.webp delete mode 100644 public/landing/workflow-version.webp diff --git a/components/LandingPage.tsx b/components/LandingPage.tsx index de404b9..4b24fb0 100644 --- a/components/LandingPage.tsx +++ b/components/LandingPage.tsx @@ -5,25 +5,28 @@ 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, + MoveRight, + Play, + Mic, + PenTool, + Keyboard, + BellRing, + FolderOpen, + FileDown, + History, + Smartphone, + Link as LinkIcon, + CheckSquare, + MessageSquare, + Github, + ArrowRight, + XCircle, + ArrowDown, + CheckCircle } from 'lucide-react'; + interface LandingPageProps { isLoggedIn: boolean; } @@ -31,229 +34,113 @@ 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 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 coreWorkflowFeatures = [ + { + title: 'Version Compare', + description: 'Compare any two versions side-by-side on a single timeline.', + icon: History, + }, + { + title: 'Asset Management', + description: 'Keep images and supplementary videos grouped perfectly per cut.', + icon: FolderOpen, + }, + { + title: 'Version History', + description: 'Infinite versioning. Toggle between V1 and V10 without losing where you are.', + icon: History, + }, + { + 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, + }, +]; -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 = [ +const workflowAcceleratorFeatures = [ { - step: '01', - title: 'Upload Or Import', - description: 'Drop MP4 files or import YouTube links into one project timeline.', - kind: 'upload', + title: 'Keyboard Shortcuts', + description: 'J, K, L, Space, and M controls for professional editing workflows.', + icon: Keyboard, }, { - step: '02', - title: 'Comment At Timestamp', - description: 'Text and voice notes are attached to exact moments on the timeline.', - kind: 'comment', + title: 'PDF/CSV Exports', + description: 'Turn video comments into a professional feedback report in one click.', + icon: FileDown, }, { - 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: 'Real-time Webhooks', + description: 'Get instant Telegram or Slack alerts the second a comment is dropped.', + icon: BellRing, }, { - 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: 'Mobile-Optimized Review', + description: 'Touch-optimized player for clients reviewing cuts on the move.', + icon: Smartphone, }, - { - 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(null); const navbarRef = useRef(null); - const timelineCounterRef = useRef(null); - const heroAnnotationCircleRef = useRef(null); - const featureAnnotationCircleRef = useRef(null); + const hostedCtaHref = isLoggedIn ? '/dashboard' : '/register'; useEffect(() => { const cleanupHandlers: Array<() => void> = []; const ctx = gsap.context(() => { + // General Reveal Animations gsap.from('[data-hero-copy]', { - y: 30, + y: 40, opacity: 0, - duration: 0.9, - stagger: 0.12, - ease: 'power3.out', + duration: 1, + stagger: 0.15, + ease: 'power4.out', }); gsap.from('[data-reveal]', { - y: 24, + y: 30, opacity: 0, - duration: 0.8, - stagger: 0.08, + duration: 0.9, + stagger: 0.1, ease: 'power3.out', + scrollTrigger: { + trigger: '[data-reveal]', + start: 'top 85%', + }, }); - 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('.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'); - }); - + // Voice Notes Waveform Animation const waveformBars = gsap.utils.toArray('.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), + scaleY: gsap.utils.random(0.3, 1.5), + duration: gsap.utils.random(0.4, 0.8), repeat: -1, yoyo: true, - delay: index * 0.03, - ease: 'power3.out', + delay: index * 0.05, + ease: 'power2.inOut', }); }); - 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); - + // Navbar Scroll Effect const nav = navbarRef.current; - if (nav) { const updateNavbar = () => { - const hasScrolled = window.scrollY > 12; - + const hasScrolled = window.scrollY > 20; gsap.to(nav, { - backgroundColor: hasScrolled - ? 'color-mix(in oklab, var(--background) 80%, transparent)' - : 'transparent', - backdropFilter: hasScrolled ? 'blur(12px)' : 'blur(0px)', + backgroundColor: hasScrolled ? 'color-mix(in oklab, var(--background) 85%, transparent)' : 'transparent', + backdropFilter: hasScrolled ? 'blur(16px)' : 'blur(0px)', + borderBottomColor: hasScrolled ? 'var(--border)' : 'transparent', 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('[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 () => { @@ -264,57 +151,45 @@ export function LandingPage({ isLoggedIn }: LandingPageProps) { return (
-
+ {/* Header */} +
- +
-
-
-
-
-

- OPEN SOURCE VIDEO REVIEW TOOL -

-

- Review Video. Without the Chaos. -

-

- Timestamped text, voice feedback, and version control for modern creative teams. -

+ {/* 1) HERO */} +
+
-
- - - - Start using OpenFrame - - - - - - - - GitHub Repo - - -
+
+
+ + + + + Open Source Video Review
-
-
-
-
- - PLAYHEAD 00:02:14 -
- v2.3.1 -
+

+ Cut Video Approval Time in Half.
+ Stop Chasing Timecodes. +

-
- Annotated video frame preview -
+

+ OpenFrame puts comments, voice notes, and annotations on a single timeline so clients say "yes" faster and your team stops guessing. +

- - - +
+ + Start free + + -
-

ANNOTATION

-

Focus this subject isolation for scene 04.

-
-
+ + Prefer self-hosting? + +
+
+ +
+
+ Product Interface Preview +
+ + {/* Toolbar floating UI */} +
+ +
+ +
-
-
- - 00:01:23 -
-

Re-time this beat hit by 4 frames.

-
+
+
+
-
-
- - Voice Note -
-

Transition pacing reads cleaner in this cut.

+ {/* 2) PROBLEM BLOCK */} +
+
+
+

Feedback chaos looks like this:

+
    +
  • + + Comments spread across WhatsApp, email, and random screenshots. +
  • +
  • + + "Around 1:12" turns into 10 minutes of guessing. +
  • +
  • + + Nobody knows which version is actually the latest. +
  • +
  • + + One unclear note becomes a full extra revision round. +
  • +
+
+

+ OpenFrame replaces all of that with one link, one timeline, one source of truth. +

-
+ {/* 3) HOW IT WORKS */} +
-
-

- Fast, precise collaborative video review. -

-

- Guest commenting via shareable links and workspace-level permissions are available on every project. -

+
+

From upload to approval - in one flow.

- -
-
-
-
- -

The Timeline

+
+ {['Upload a cut', 'Share a review link', 'Get timestamped feedback', 'Mark approved and move on'].map((step, idx, arr) => ( +
+
+
+ {idx + 1} +
+ {step}
- - 00:01:23 - + {idx < arr.length - 1 && ( + <> +
+ +
+
+ +
+ + )}
-

- Comments attach to exact timestamps so every request references the exact moment on the video. + ))} +

+
+
+ + {/* 4) FEATURES */} +
+ + {/* Feature 1 */} +
+
+
+
+ Comparison Mode +
+
+
+
+

Compare versions side-by-side. End "which cut is this?"

+

+ See what actually changed between versions then approve with confidence.

+

Cuts revision cycles.

+
+
+
-
-
- SEQUENCE.A / MASTER CUT - 24 FPS -
-
-
-
-
-
-
- -
- 00:00:23 - -
-
- 00:00:52 - -
-
- 00:01:34 - -
-
-
- - -
-
- -

Voice Notes

-
-

- Record a voice note at any timestamp. Teammates play it back in context. + {/* Feature 2 */} +

+
+
+

Explain it better with Voice.

+

+ No more "What did you mean by this?" emails. Every note lands at the exact moment in the video.

-
-
- - REC 00:00:12 -
-
-
-
- {Array.from({ length: 22 }).map((_, index) => ( - +

Faster feedback. Fewer misunderstandings.

+
+
+
+
+
+
+
Y
+
+

Yusuf İpek

+

00:03:45

+
+
+ +
+
+ + {Array.from({ length: 40 }).map((_, i) => ( + ))}
-

MIC-01 / SAVED TO 00:01:23

-
+
+
+
-
-
- -

Version Management

+ {/* Feature 3 */} +
+
+
+
+ Annotate + + + + + + {/* Circle Editor UI mock */} +
+
+
+
+
+
+
+
+
+ + +
+
-

- Upload multiple cuts under one video and keep each version named and ordered. +

+
+

Point. Draw. Done.

+

+ Precise feedback that leaves zero room for error. Circle, sketch, and point directly on the video frame.

- -
-
- - - v1.0.0 - rough-cut - - Archived -
-
- - - v1.0.1 - client-notes - - Previous -
-
- - - v1.0.2 - current - - Active -
-
- -
- - Upload New Version -
-
- -
-
- -

Drawing & Annotation

-
-

- Draw directly on the video image to point at exact areas during review. -

- -
-
- Video frame with annotation overlay -
- - - -
-
- DRAW ON FRAME / SAVE WITH COMMENT -
-
-
+

Stops "I thought you meant..."

+
-
+ {/* 5) EVERYTHING YOUR TEAM EXPECTS */} +
-
-

- Upload. Review. Version. In one continuous loop. -

+
+

Everything a real production workflow needs.

+

The core tools teams expect without the complexity that slows clients down.

-
- {workflowProofItems.map((item) => ( -
-
- {item.kind === 'upload' && ( -
-
-

- - Add Source -

-
-
- Paste URL - Direct Upload -
-
-
https://youtube.com/watch?v=...
-
Video title (optional)
-
Description (optional)
-
-
- - Add Video - -
-
- )} - - {item.kind === 'comment' && ( -
-
-
- - Y - - Yusuf İpek -
- 00:04:14 -
-

- wrong tweet here's what you should use: -

-

https://x.com/...

-
- 2/21/2026 - Feedback -
-
- ↩ Reply -
-
- )} - - {item.kind === 'version' && ( -
-
-

- - New Version -

-

v1.0.3

-
-
- Link URL - Upload File -
-
-
Video URL
-
Version Label
-
-
- Ship Version -
-
- )} -
- -
-

{item.step}

-

{item.title}

-

{item.description}

-
-
- ))} -
-
-
- -
-
-
-

- One screen. Full review context. -

-

- This panel maps exactly where timestamp comments, voice notes, and frame annotation live. -

-
- -
-
- OpenFrame dashboard deep dive screenshot -
+
+
+

Core workflow

+
+ {coreWorkflowFeatures.map((feat) => ( +
+ +

{feat.title}

+

{feat.description}

+
+ ))} +
-
-
-

- - Timestamp Comments -

-

Attached to exact timeline positions.

-
-
-

- - Voice Notes -

-

Captured in context, played back in thread.

-
-
-

- - Frame Annotation -

-

Circle and draw directly on the frame.

+
+

Workflow Accelerators

+
+ {workflowAcceleratorFeatures.map((feat) => ( +
+ +

{feat.title}

+

{feat.description}

+
+ ))}
-
-
-
-

- Sync 2-4 videos and compare cuts in real time. -

-

- Play multiple versions side-by-side with synchronized playback to validate pacing, edit decisions, and grading changes. -

+ {/* 6) BUILT FOR CLIENTS */} +
+
+
+

Clients don't need an account. They just review.

+

If clients can't adopt the tool, approvals don't happen. OpenFrame keeps it frictionless.

- -
-
- OpenFrame compare mode screenshot -
+
+
+ +
+

One link. Review in the browser.

+
- -
-
-

- - 2-4 Video Panels -

-

Load multiple versions into one comparison view.

+
+ +
+

Works great on mobile.

-
-

- - Synchronized Playback -

-

Play, pause, and scrub all panels at once.

-
-
-

- - Compare Feedback Impact -

-

Confirm that requested changes are visible across versions.

+
+
+ +
+

Timestamped notes that are impossible to miss.

-
-
-
- -
-
-
-

- Built for agencies, solo editors, and internal teams. -

-
- -
- {useCaseRails.map((item) => ( -
-
- {`${item.title} -
-
- -

{item.label}

-

{item.title}

-

{item.description}

-
- ))}
-
+ {/* 7) PRICING */} +
-
-

Deploy it yourself - or let us run it for you.

+
+

+ Deploy it yourself - or let us run it for you. +

-
-
-
-

For the builders.

-

Open Source (Self-Hosted)

-

Free / OSS

+
+ {/* Card 1: Open Source */} +
+
+

Open Source (Self-hosted)

-
    -
  • - - Full codebase access -
  • -
  • - - Self-hosted infrastructure control -
  • -
  • - - Manual update cadence -
  • +
    + Free +
    +

    For teams who want full control and can run their own infrastructure.

    + +
      +
    • Full codebase access
    • +
    • Self-hosted infrastructure control
    • +
    • Manual update cadence
    - - - - - View GitHub - + + View on GitHub -
+
-
-
-
-

- For modern creative teams. -

-

Hosted Cloud

-

$10 / month

-

For fast, collaborative video review.

+ {/* Card 2: Hosted Cloud */} +
+
+

Best for teams who want zero setup.

+

Hosted Cloud

-
    -
  • - - Unlimited Seats (Collaborators/Guests) -
  • -
  • - - Unlimited Workspaces & Projects -
  • -
  • - - Unlimited YouTube video imports -
  • -
  • - - 200 GB video storage included -
  • -
  • - - +100 GB for $5 -
  • -
  • - - Download uploaded original files as-is -
  • -
  • - - Instant Webhook (Telegram/Email) Setup -
  • -
  • - - No per-seat pricing -
  • -
  • - - Unlimited reviewers -
  • -
  • - - Keep control of your files -
  • -
+
+ $10 + / month +
- - - - {isLoggedIn ? 'Launch workspace' : 'Start using OpenFrame'} - - +
    +
  • Unlimited collaborators
  • +
  • Timestamped comments + voice notes
  • +
  • Annotations + version compare
  • +
  • Share links with permissions
  • +
  • Exports (PDF/CSV)
  • +
  • Unlimited YouTube Video Imports
  • +
  • Includes: 200 GB Storage
  • +
+

Need more storage? Add 100 GB for $5/mo.

+ + + Start free -
+
- +
+
+ + {/* 8) SECURITY & PRIVACY */} +
+
+
+

Security & privacy, by design.

+
    +
  • - Permissioned share links (control who can view/comment)
  • +
  • - Private-by-default projects
  • +
  • - Delete videos and projects anytime
  • +
  • - Self-host option for full data control
  • +
+
+
+
+ + {/* 9) FAQ */} +
+
+
+

FAQ

+
+ {[ + { + q: 'Do clients need an account?', + a: 'No. They can review in the browser with a share link.', + }, + { + q: 'What happens if I exceed my storage?', + a: 'You can add 100 GB for $5/mo. If you need much more, contact us and we’ll help you choose the best setup.', + }, + { + q: 'How is YouTube unlimited?', + a: 'There is no storage limit on YouTube imports. You can import an unlimited amount of unlisted YouTube videos and use all of our review features exactly the same.', + }, + { + q: 'Can I self-host?', + a: 'Yes. The core is open-source and self-hostable. Hosted Cloud is for teams who want zero setup.', + }, + { + q: 'How is this different from sending a Google Drive link?', + a: 'Drive doesn’t give timestamped discussion, voice notes, annotations, or version compare, which is where approval time is actually saved.', + }, + { + q: 'Is it mobile-friendly?', + a: 'Yes. Clients can review and comment from mobile.', + }, + { + q: 'Can I export feedback?', + a: 'Yes. Export to PDF/CSV for archiving or client handoff.', + }, + { + q: 'Who can access my videos?', + a: 'Only people you invite or share a link with (based on permissions you set).', + }, + { + q: 'Can I cancel anytime?', + a: 'Yes. Cancel anytime from your billing settings.', + }, + ].map((item) => ( +
+

{item.q}

+

{item.a}

+
+ ))} +
+
+
+
+ + {/* 10) FINAL CTA STRIP */} +
+
+
+

Stop chasing feedback. Start getting approvals.

+

Your first review link takes minutes.

+
+ + Start free + +
+
-