mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(videos): add FeaturePill, ProgressBar, NumberPunch components
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import type React from "react";
|
||||
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLOR } from "@/lib/colors";
|
||||
import { FONT } from "@/lib/fonts";
|
||||
import { SPRING } from "@/lib/motion";
|
||||
|
||||
export const FeaturePill: React.FC<{
|
||||
label: string;
|
||||
enterFrame: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
fromDirection?: "left" | "right" | "top" | "bottom";
|
||||
}> = ({ label, enterFrame, targetX, targetY, fromDirection = "left" }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const progress = spring({
|
||||
frame: frame - enterFrame,
|
||||
fps,
|
||||
config: SPRING.popIn,
|
||||
});
|
||||
|
||||
const offscreen = {
|
||||
left: { x: -300, y: targetY },
|
||||
right: { x: 1400, y: targetY },
|
||||
top: { x: targetX, y: -100 },
|
||||
bottom: { x: targetX, y: 1200 },
|
||||
}[fromDirection];
|
||||
|
||||
const x = interpolate(progress, [0, 1], [offscreen.x, targetX]);
|
||||
const y = interpolate(progress, [0, 1], [offscreen.y, targetY]);
|
||||
const scale = interpolate(progress, [0, 1], [0.8, 1]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: x,
|
||||
top: y,
|
||||
transform: `scale(${scale})`,
|
||||
padding: "8px 18px",
|
||||
borderRadius: 8,
|
||||
backgroundColor: `${COLOR.accent}26`,
|
||||
border: `1px solid ${COLOR.accent}40`,
|
||||
color: "white",
|
||||
fontFamily: FONT.body,
|
||||
fontWeight: 600,
|
||||
fontSize: 16,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import type React from "react";
|
||||
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
||||
import { COLOR } from "@/lib/colors";
|
||||
import { TEXT } from "@/lib/fonts";
|
||||
import { SPRING } from "@/lib/motion";
|
||||
|
||||
export const NumberPunch: React.FC<{
|
||||
number: string;
|
||||
descriptor: string;
|
||||
enterFrame: number;
|
||||
numberSize?: number;
|
||||
shakeIntensity?: number;
|
||||
}> = ({ number, descriptor, enterFrame, numberSize = 120, shakeIntensity = 2 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const elapsed = frame - enterFrame;
|
||||
const scaleSpring = spring({ frame: elapsed, fps, config: SPRING.settle });
|
||||
const scale = interpolate(scaleSpring, [0, 1], [1.5, 1]);
|
||||
|
||||
const shakeDecay = interpolate(elapsed, [0, 12], [1, 0], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const shakeX = Math.sin(elapsed * 3) * shakeIntensity * shakeDecay;
|
||||
const shakeY = Math.cos(elapsed * 4) * shakeIntensity * shakeDecay;
|
||||
|
||||
const descOpacity = interpolate(elapsed, [8, 18], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
const numberOpacity = interpolate(elapsed, [0, 4], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "center",
|
||||
gap: 20,
|
||||
transform: `translate(${shakeX}px, ${shakeY}px)`,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
...TEXT.counter,
|
||||
fontSize: numberSize,
|
||||
transform: `scale(${scale})`,
|
||||
opacity: numberOpacity,
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{number}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: TEXT.heroSub.fontFamily,
|
||||
fontWeight: 500,
|
||||
fontSize: 36,
|
||||
color: COLOR.accent,
|
||||
opacity: descOpacity,
|
||||
}}
|
||||
>
|
||||
{descriptor}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import type React from "react";
|
||||
import { interpolate, useCurrentFrame } from "remotion";
|
||||
import { EASE } from "@/lib/motion";
|
||||
|
||||
export const ProgressBar: React.FC<{
|
||||
startFrame: number;
|
||||
duration: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
color?: string;
|
||||
bgColor?: string;
|
||||
style?: React.CSSProperties;
|
||||
}> = ({
|
||||
startFrame,
|
||||
duration,
|
||||
width = 200,
|
||||
height = 6,
|
||||
color = "#3b82f6",
|
||||
bgColor = "rgba(255,255,255,0.1)",
|
||||
style,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const progress = interpolate(frame, [startFrame, startFrame + duration], [0, 100], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: EASE.smooth,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
borderRadius: height / 2,
|
||||
backgroundColor: bgColor,
|
||||
overflow: "hidden",
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
height: "100%",
|
||||
borderRadius: height / 2,
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user