diff --git a/app/globals.css b/app/globals.css
index d1d2ea0..ef16faa 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -123,4 +123,21 @@
body {
@apply bg-background text-foreground;
}
-}
\ No newline at end of file
+}
+
+.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);
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index fc696b4..dbb5802 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,5 +1,5 @@
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 { Toaster } from "sonner";
import "./globals.css";
@@ -11,6 +11,13 @@ const jetbrainsMono = JetBrains_Mono({
preload: true,
});
+const geistMono = Geist_Mono({
+ subsets: ['latin'],
+ variable: '--font-geist-mono',
+ display: 'swap',
+ preload: true,
+});
+
export const metadata: Metadata = {
title: "OpenFrame - Video Feedback Platform",
description: "Collect timestamped video feedback with text and voice comments",
@@ -22,15 +29,21 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
+
+
+
+
+
+
+
{children}
+
diff --git a/app/page.tsx b/app/page.tsx
index 692b90e..c8e9008 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,411 +1,8 @@
-import Link from 'next/link';
-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 { LandingPage } from '@/components/LandingPage';
import { auth } from '@/lib/auth';
export default async function HomePage() {
const session = await auth();
- const isLoggedIn = !!session?.user;
- return (
-
- {/* Header */}
-
-
-
- {/* Hero Section */}
-
-
-
-
-
- Open Source Video Feedback Tool
-
-
- Review videos without
-
- losing your mind.
-
-
-
- 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.
-
-
- {isLoggedIn ? (
-
-
- Go to Dashboard
-
-
-
- ) : (
-
-
- Start for free
-
-
-
- )}
-
-
- Explore Features
-
-
-
-
-
- {/* Hero Video */}
-
-
-
- {/* Feature 1: Visual Left, Text Right */}
-
-
-
-
- {/* Visual Placeholder for Comments */}
-
-
-
-
YI
-
-
-
"Let's change the color to red."
-
-
-
-
-
-
-
-
-
Pinpoint exactly what needs fixing.
-
- Stop guessing what "that thing around 2 minutes in" means.
- Drop timestamped comments directly on the timeline. Feeling lazy? Leave a voice note instead.
-
-
- {['Threaded replies to keep conversations clean', 'Mark comments as resolved', 'Voice recording with Web Audio API'].map((item, i) => (
-
-
- {item}
-
- ))}
-
-
-
-
-
- {/* Feature 2: Text Left, Visual Right */}
-
-
-
-
-
-
-
Never lose track of a version again.
-
- "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.
-
-
- {['Clean v1, v2, v3 organization', 'Keep old comments preserved on previous versions', 'Support for YouTube, Vimeo, and direct links'].map((item, i) => (
-
-
- {item}
-
- ))}
-
-
-
-
-
- {[3, 2, 1].map((v) => (
-
-
-
- Version {v}
-
-
{v === 3 ? 'Current' : '3 days ago'}
-
- ))}
-
-
-
-
-
- {/* Feature 3: Visual Left, Text Right */}
-
-
-
-
-
-
-
-
- Notifications (Email & Telegram)
-
-
-
-
-
-
-
-
Share seamlessly.
-
- 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.
-
-
- {['Public, Private, and Invite-only projects', 'Frictionless guest commenting', 'Telegram & Email webhook integrations'].map((item, i) => (
-
-
- {item}
-
- ))}
-
-
-
-
-
- {/* Feature 4: Text Left, Visual Right */}
-
-
-
-
-
Communicate visually.
-
- Sometimes words aren't enough. Draw directly on video frames to highlight exact details, and attach reference pictures directly to your comments.
-
-
- {['Draw and annotate on frames', 'Attach images to comments', 'Leave no room for ambiguity'].map((item, i) => (
-
-
- {item}
-
- ))}
-
-
-
-
-
-
-
-
AI
-
-
"Reference for the color grade:"
-
-
-
-
-
-
-
-
-
-
- {/* Feature 5: Visual Left, Text Right */}
-
-
-
-
-
- {/* AI Summary */}
-
-
-
-
- AI Intelligence
-
-
"User suggests the transition at 0:45 is too sharp. Prefer a smoother fade to match the music."
-
- {/* Export */}
-
-
- PDF Export
-
-
- CSV Export
-
-
-
-
-
-
-
-
-
Powerful workflows.
-
- Let AI summarize long-winded voice notes automatically, and export all your project feedback into neat PDF or CSV files ready for the team.
-
-
- {['AI-powered voice summaries', 'One-click PDF/CSV exports', 'Structured feedback data'].map((item, i) => (
-
-
- {item}
-
- ))}
-
-
-
-
-
- {/* Business Model Section */}
-
-
-
-
-
-
-
Open Source or Managed. Your call.
-
- We believe in code freedom. Self-host it if you like configuring servers, or use our managed platform if you value your time.
-
-
-
-
-
-
-
-
- {/* Visual alignment block to match the other card's badge */}
-
-
Self-Hosted
-
Grab the code. Deploy it on your own infrastructure. It's 100% free and open source.
-
-
-
-
-
-
-
-
-
-
- Recommended
-
-
Managed Hosting
-
Skip the DevOps. Use open-frame.net. We handle the servers, storage, and updates.
-
-
-
- Start on open-frame.net
-
-
-
-
-
-
-
-
- {/* Footer */}
-
-
- );
-}
\ No newline at end of file
+ return ;
+}
diff --git a/bun.lock b/bun.lock
index 4d250a1..97bc8b3 100644
--- a/bun.lock
+++ b/bun.lock
@@ -12,6 +12,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
+ "gsap": "^3.14.2",
"hls.js": "^1.6.15",
"lucide-react": "^0.563.0",
"next": "16.1.6",
@@ -1140,6 +1141,8 @@
"graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="],
+ "gsap": ["gsap@3.14.2", "", {}, "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA=="],
+
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
diff --git a/components/LandingPage.tsx b/components/LandingPage.tsx
new file mode 100644
index 0000000..8f4821e
--- /dev/null
+++ b/components/LandingPage.tsx
@@ -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(null);
+ const navbarRef = useRef(null);
+ const timelineCounterRef = useRef(null);
+ const heroAnnotationCircleRef = useRef(null);
+ const featureAnnotationCircleRef = useRef(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('.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('.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('[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 (
+
+
+
+
+
+
+
OPENFRAME
+
+
+
+
+ Features
+
+
+ Pricing
+
+
+
+
+ {isLoggedIn ? (
+ <>
+
+
+
+ Dashboard
+ Go to Dashboard
+
+
+
+
+
+
+ Workspace
+
+
+ >
+ ) : (
+ <>
+
+
+
Login
+
+
+
+
+ Get Started
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+ VIDEO FEEDBACK PROTOCOL
+
+
+ Review Video. Without the Chaos.
+
+
+ Timestamped text, voice feedback, and version control for modern creative teams.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ANNOTATION
+
Focus this subject isolation for scene 04.
+
+
+
+
+
+
+
+ 00:01:23
+
+
Re-time this beat hit by 4 frames.
+
+
+
+
+
+ Voice Note
+
+
Transition pacing reads cleaner in this cut.
+
+
+
+
+
+
+
+
+
+ Collaborative video review as precision software.
+
+
+ Guest commenting via shareable links and workspace-level permissions are available on every project.
+
+
+
+
+
+
+
+
+
The Timeline
+
+
+ 00:01:23
+
+
+
+ Comments attach to exact timestamps so every request references the exact moment on the video.
+
+
+
+
+ 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.
+
+
+
+
+ REC 00:00:12
+
+
+
+
+ {Array.from({ length: 22 }).map((_, index) => (
+
+ ))}
+
+
+
MIC-01 / SAVED TO 00:01:23
+
+
+
+
+
+
+
Version Management
+
+
+ Upload multiple cuts under one video and keep each version named and ordered.
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+ DRAW ON FRAME / SAVE WITH COMMENT
+
+
+
+
+
+
+
+
+
+
+
+ Upload. Review. Version. In one continuous loop.
+
+
+
+
+ {workflowProofItems.map((item) => (
+
+
+ {item.kind === 'upload' && (
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+ 2-4 Video Panels
+
+
Load multiple versions into one comparison view.
+
+
+
+
+ Synchronized Playback
+
+
Play, pause, and scrub all panels at once.
+
+
+
+
+ Compare Feedback Impact
+
+
Confirm that requested changes are visible across versions.
+
+
+
+
+
+
+
+
+
+
+ Built for agencies, solo editors, and internal teams.
+
+
+
+
+ {useCaseRails.map((item) => (
+
+
+
+ {item.label}
+ {item.title}
+ {item.description}
+
+ ))}
+
+
+
+
+
+
+
+
Deploy self-hosted, or run fully managed.
+
+
+
+
+
+
For the builders.
+
Open Source (Self-Hosted)
+
Free / OSS
+
+
+
+
+
+ Full codebase access
+
+
+
+ Self-hosted infrastructure control
+
+
+
+ Manual update cadence
+
+
+
+
+
+
+
+ View GitHub
+
+
+
+
+
+
+
+
+ For modern creative teams.
+
+
Hosted Cloud
+
$10 / month
+
+
+
+
+
+ Unlimited Seats (Collaborators/Guests)
+
+
+
+ Unlimited Workspaces & Projects
+
+
+
+ Unlimited YouTube video imports
+
+
+
+ 100 GB of direct video upload storage (+$5 per next 100 GB)
+
+
+
+ Instant Webhook (Telegram/Email) Setup
+
+
+
+
+
+
+ Deploy on Cloud
+
+
+
+
+
+
+
+
+
+
+
+
+
OpenFrame
+
+ Fast video review with timestamped feedback, version tracking, and guest collaboration.
+
+
+
+
+
Legal
+
+
+ Terms
+
+
+ Privacy
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/package.json b/package.json
index fa99e4c..99f5131 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
+ "gsap": "^3.14.2",
"hls.js": "^1.6.15",
"lucide-react": "^0.563.0",
"next": "16.1.6",
diff --git a/public/landing/.webp b/public/landing/.webp
new file mode 100644
index 0000000..d0f7d05
Binary files /dev/null and b/public/landing/.webp differ
diff --git a/public/landing/compare-v2.webp b/public/landing/compare-v2.webp
new file mode 100644
index 0000000..b8f3aca
Binary files /dev/null and b/public/landing/compare-v2.webp differ
diff --git a/public/landing/compare.webp b/public/landing/compare.webp
new file mode 100644
index 0000000..b8f3aca
Binary files /dev/null and b/public/landing/compare.webp differ
diff --git a/public/landing/deep-dive-dashboard.webp b/public/landing/deep-dive-dashboard.webp
new file mode 100644
index 0000000..d0f7d05
Binary files /dev/null and b/public/landing/deep-dive-dashboard.webp differ
diff --git a/public/landing/workflow-comment.webp b/public/landing/workflow-comment.webp
new file mode 100644
index 0000000..56781ad
Binary files /dev/null and b/public/landing/workflow-comment.webp differ
diff --git a/public/landing/workflow-upload.webp b/public/landing/workflow-upload.webp
new file mode 100644
index 0000000..646be69
Binary files /dev/null and b/public/landing/workflow-upload.webp differ
diff --git a/public/landing/workflow-version.webp b/public/landing/workflow-version.webp
new file mode 100644
index 0000000..9936351
Binary files /dev/null and b/public/landing/workflow-version.webp differ