mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(videos): add ToolGrid and BeforeAfter components
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width,
|
||||
height,
|
||||
overflow: "hidden",
|
||||
borderRadius: 8,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div style={{ position: "absolute", inset: 0 }}>{before}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
clipPath: `inset(0 ${100 - scanProgress}% 0 0)`,
|
||||
}}
|
||||
>
|
||||
{after}
|
||||
</div>
|
||||
|
||||
{scanProgress > 0 && scanProgress < 100 && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: scanX,
|
||||
top: 0,
|
||||
width: 2,
|
||||
height: "100%",
|
||||
backgroundColor: COLOR.accent,
|
||||
boxShadow: `0 0 20px ${COLOR.accent}80`,
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: gap * 2,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{CATEGORY_ORDER.map((cat) => {
|
||||
const tools = getToolsByCategory(cat);
|
||||
return (
|
||||
<div key={cat} style={{ display: "flex", flexDirection: "column", gap }}>
|
||||
{tools.map((tool) => {
|
||||
const i = globalIndex++;
|
||||
const enterDelay = startFrame + i * TIMING.staggerFrames;
|
||||
const s = spring({
|
||||
frame: frame - enterDelay,
|
||||
fps,
|
||||
config: SPRING.settle,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tool.name}
|
||||
style={{
|
||||
opacity: s,
|
||||
transform: `translateX(${(1 - s) * 100}px) scale(${0.8 + s * 0.2})`,
|
||||
}}
|
||||
>
|
||||
<ToolPill
|
||||
name={tool.name}
|
||||
category={tool.category}
|
||||
style={{ width: cellWidth, fontSize: 11, padding: "3px 8px" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user