summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/package.json2
-rw-r--r--apps/web/src/app/contributors/page.tsx57
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx60
-rw-r--r--apps/web/src/components/icons.tsx50
-rw-r--r--apps/web/src/constants/site.ts22
5 files changed, 175 insertions, 16 deletions
diff --git a/apps/web/package.json b/apps/web/package.json
index f5cd7da4..9531b6c7 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -43,7 +43,7 @@
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
"motion": "^12.18.1",
- "next": "^15.4.3",
+ "next": "^15.4.5",
"next-themes": "^0.4.4",
"pg": "^8.16.2",
"radix-ui": "^1.4.2",
diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx
index 6a524866..29151bd8 100644
--- a/apps/web/src/app/contributors/page.tsx
+++ b/apps/web/src/app/contributors/page.tsx
@@ -5,8 +5,14 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
-import { GithubIcon } from "@/components/icons";
+import {
+ GithubIcon,
+ MarbleIcon,
+ VercelIcon,
+ DataBuddyIcon,
+} from "@/components/icons";
import { Badge } from "@/components/ui/badge";
+import { EXTERNAL_TOOLS } from "@/constants/site";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
@@ -39,7 +45,7 @@ async function getContributors(): Promise<Contributor[]> {
"User-Agent": "OpenCut-Web-App",
},
next: { revalidate: 600 }, // 10 minutes
- }
+ } as RequestInit
);
if (!response.ok) {
@@ -236,6 +242,53 @@ export default async function ContributorsPage() {
</div>
)}
+ <div className="mt-6 mb-20">
+ <div className="text-center mb-12">
+ <h2 className="text-2xl font-semibold mb-2">External Tools</h2>
+ <p className="text-muted-foreground">
+ Tools we use to build OpenCut
+ </p>
+ </div>
+
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
+ {EXTERNAL_TOOLS.map((tool, index) => {
+ const IconComponent = {
+ MarbleIcon,
+ VercelIcon,
+ DataBuddyIcon,
+ }[tool.icon];
+
+ return (
+ <Link
+ key={tool.name}
+ href={tool.url}
+ target="_blank"
+ className="group block"
+ style={{
+ animationDelay: `${index * 100}ms`,
+ }}
+ >
+ <Card className="h-full bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl">
+ <CardContent className="p-6 text-center h-full flex flex-col">
+ <div className="mb-4">
+ <div className="w-12 h-12 mx-auto rounded-full bg-muted/50 flex items-center justify-center group-hover:bg-muted/70 transition-colors">
+ <IconComponent className="h-6 w-6" size={24} />
+ </div>
+ </div>
+ <h3 className="font-semibold text-lg mb-2 group-hover:text-foreground/80 transition-colors">
+ {tool.name}
+ </h3>
+ <p className="text-sm text-muted-foreground flex-1">
+ {tool.description}
+ </p>
+ </CardContent>
+ </Card>
+ </Link>
+ );
+ })}
+ </div>
+ </div>
+
<div className="mt-32 text-center">
<div className="max-w-2xl mx-auto">
<h2 className="text-3xl font-bold mb-4">Join the community</h2>
diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 4cc35bb5..39c6795f 100644
--- a/apps/web/src/components/editor/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
@@ -826,6 +826,18 @@ function PreviewToolbar({
setCanvasSizeToOriginal(aspectRatio);
};
+ const totalDuration = getTotalDuration();
+
+ const skipBackward = () => {
+ const newTime = Math.max(0, currentTime - 1);
+ setCurrentTime(newTime);
+ };
+
+ const skipForward = () => {
+ const newTime = Math.min(totalDuration, currentTime + 1);
+ setCurrentTime(newTime);
+ };
+
if (isExpanded) {
return (
<FullscreenToolbar
@@ -871,19 +883,41 @@ function PreviewToolbar({
</span>
</p>
</div>
- <Button
- variant="text"
- size="icon"
- onClick={toggle}
- disabled={!hasAnyElements}
- className="h-auto p-0"
- >
- {isPlaying ? (
- <Pause className="h-3 w-3" />
- ) : (
- <Play className="h-3 w-3" />
- )}
- </Button>
+ <div className="flex items-center gap-1">
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipBackward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip backward 1s"
+ >
+ <SkipBack className="h-3 w-3" />
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={toggle}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ >
+ {isPlaying ? (
+ <Pause className="h-3 w-3" />
+ ) : (
+ <Play className="h-3 w-3" />
+ )}
+ </Button>
+ <Button
+ variant="text"
+ size="icon"
+ onClick={skipForward}
+ disabled={!hasAnyElements}
+ className="h-auto p-0 text-white hover:text-white/80"
+ title="Skip forward 1s"
+ >
+ <SkipForward className="h-3 w-3" />
+ </Button>
+ </div>
<div className="flex items-center gap-3">
<BackgroundSettings />
<DropdownMenu>
diff --git a/apps/web/src/components/icons.tsx b/apps/web/src/components/icons.tsx
index 6099c052..43f953e8 100644
--- a/apps/web/src/components/icons.tsx
+++ b/apps/web/src/components/icons.tsx
@@ -113,3 +113,53 @@ export function BackgroundIcon({ className }: { className?: string }) {
</svg>
);
}
+
+export function MarbleIcon({
+ className = "",
+ size = 32,
+}: {
+ className?: string;
+ size?: number;
+}) {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width={size}
+ height={size}
+ className={className}
+ fill="none"
+ viewBox="0 0 256 256"
+ >
+ <path fill="#202027" d="M0 0h256v256H0z" />
+ <path
+ fill="#fff"
+ d="M116.032 94.016q1.408.256 6.272 9.856 4.856 9.472 11.904 24.576l4.096 8.576a42.4 42.4 0 0 0 4.992-6.784q2.304-3.96 5.76-10.624 3.96-7.56 5.504-9.728 6.144-9.344 13.312-19.84.896-1.408 3.2-1.92a4.8 4.8 0 0 1 1.28-.128q3.072 0 6.784 2.56 3.84 2.56 4.992 5.248a4.8 4.8 0 0 1 .256 1.92q0 1.28-.128 1.92l-.64 2.944q-.256 1.664-.512 3.968a3200 3200 0 0 0-5.888 46.848 56 56 0 0 0-.512 8.064l-.256 4.096a32 32 0 0 0-.128 2.944q0 1.672-.384 2.304-.384.64-1.408.64-.768 0-2.304-.384-4.096-.896-6.272-3.2-2.176-2.432-2.432-6.656-.128-2.176-.128-6.784 0-4.736.256-14.464l.128-7.04.256-10.112q-1.152 1.024-5.888 8.064a328 328 0 0 0-9.088 14.464q-4.48 7.56-5.888 11.648-1.28 3.2-2.56 4.736-1.152 1.536-2.816 1.536-1.536 0-3.968-1.408-6.912-4.224-8.448-11.648a336 336 0 0 0-6.016-23.68 40 40 0 0 0-2.56-6.272q-1.792-3.968-2.304-4.992l-1.664-3.328q-18.68 24.832-25.344 45.568-1.28 4.352-2.432 6.272-1.152 1.792-2.688 1.792-2.432 0-6.912-4.352Q72 158.144 72 154.304q0-2.04.768-4.096a108 108 0 0 1 16-30.08l3.968-5.248a432 432 0 0 0 12.16-16.64q3.072-4.48 8.192-4.48.896 0 2.944.256"
+ />
+ </svg>
+ );
+}
+
+export function DataBuddyIcon({
+ className = "",
+ size = 32,
+}: {
+ className?: string;
+ size?: number;
+}) {
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width={size}
+ height={size}
+ className={className}
+ viewBox="0 0 8 8"
+ shapeRendering="crispEdges"
+ >
+ <path d="M0 0h8v8H0z" />
+ <path
+ fill="#fff"
+ d="M1 1h1v6H1zm1 0h4v1H2zm4 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zM2 6h4v1H2zm1-3h1v1H3zm1 1h1v1H4z"
+ />
+ </svg>
+ );
+}
diff --git a/apps/web/src/constants/site.ts b/apps/web/src/constants/site.ts
index f47f6470..00bd649f 100644
--- a/apps/web/src/constants/site.ts
+++ b/apps/web/src/constants/site.ts
@@ -9,3 +9,25 @@ export const SITE_INFO = {
twitterImage: "/open-graph/default.jpg",
favicon: "/favicon.ico",
};
+
+export const EXTERNAL_TOOLS = [
+ {
+ name: "Marble",
+ description:
+ "Modern headless CMS for content management and the blog for OpenCut",
+ url: "https://marblecms.com?utm_source=opencut",
+ icon: "MarbleIcon" as const,
+ },
+ {
+ name: "Vercel",
+ description: "Platform where we deploy and host OpenCut",
+ url: "https://vercel.com?utm_source=opencut",
+ icon: "VercelIcon" as const,
+ },
+ {
+ name: "Databuddy",
+ description: "GDPR compliant analytics and user insights for OpenCut",
+ url: "https://databuddy.cc?utm_source=opencut",
+ icon: "DataBuddyIcon" as const,
+ },
+];