From 7ff96a65158415b4cdeea2e27115a806153a73ff Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Fri, 8 May 2026 16:39:03 +0800 Subject: [PATCH] feat(videos): add ToolGrid and BeforeAfter components --- apps/videos/src/components/BeforeAfter.tsx | 65 ++++++++++++++++++++++ apps/videos/src/components/ToolGrid.tsx | 62 +++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 apps/videos/src/components/BeforeAfter.tsx create mode 100644 apps/videos/src/components/ToolGrid.tsx diff --git a/apps/videos/src/components/BeforeAfter.tsx b/apps/videos/src/components/BeforeAfter.tsx new file mode 100644 index 00000000..88b0de62 --- /dev/null +++ b/apps/videos/src/components/BeforeAfter.tsx @@ -0,0 +1,65 @@ +import type React from "react"; +import { interpolate, useCurrentFrame } from "remotion"; +import { COLOR } from "@/lib/colors"; +import { EASE } from "@/lib/motion"; + +export const BeforeAfter: React.FC<{ + before: React.ReactNode; + after: React.ReactNode; + scanStartFrame: number; + scanDuration: number; + width: number; + height: number; + style?: React.CSSProperties; +}> = ({ before, after, scanStartFrame, scanDuration, width, height, style }) => { + const frame = useCurrentFrame(); + + const scanProgress = interpolate( + frame, + [scanStartFrame, scanStartFrame + scanDuration], + [0, 100], + { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.smooth }, + ); + + const scanX = (scanProgress / 100) * width; + + return ( +
+
{before}
+ +
+ {after} +
+ + {scanProgress > 0 && scanProgress < 100 && ( +
+ )} +
+ ); +}; diff --git a/apps/videos/src/components/ToolGrid.tsx b/apps/videos/src/components/ToolGrid.tsx new file mode 100644 index 00000000..e26a08f6 --- /dev/null +++ b/apps/videos/src/components/ToolGrid.tsx @@ -0,0 +1,62 @@ +import type React from "react"; +import { spring, useCurrentFrame, useVideoConfig } from "remotion"; +import { ToolPill } from "@/components/ToolPill"; +import { CATEGORY_ORDER } from "@/lib/colors"; +import { SPRING, TIMING } from "@/lib/motion"; +import { getToolsByCategory, TOOLS } from "@/lib/tools"; + +export const ToolGrid: React.FC<{ + startFrame: number; + cellWidth?: number; + cellHeight?: number; + gap?: number; + style?: React.CSSProperties; +}> = ({ startFrame, cellWidth = 140, cellHeight = 32, gap = 6, style }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + let globalIndex = 0; + + return ( +
+ {CATEGORY_ORDER.map((cat) => { + const tools = getToolsByCategory(cat); + return ( +
+ {tools.map((tool) => { + const i = globalIndex++; + const enterDelay = startFrame + i * TIMING.staggerFrames; + const s = spring({ + frame: frame - enterDelay, + fps, + config: SPRING.settle, + }); + + return ( +
+ +
+ ); + })} +
+ ); + })} +
+ ); +};