Add .gitignore to exclude all node packages and lock files
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
spring,
|
||||
} from "remotion";
|
||||
import { Scene as SceneType } from "../lib/types";
|
||||
|
||||
interface SceneProps {
|
||||
scene: SceneType;
|
||||
}
|
||||
|
||||
export const SceneComponent: React.FC<SceneProps> = ({ scene }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const totalFrames = scene.durationInSeconds * fps;
|
||||
|
||||
// Entrance animation (first 20% of scene)
|
||||
const entranceDuration = Math.floor(totalFrames * 0.2);
|
||||
// Exit animation (last 20% of scene)
|
||||
const exitStart = totalFrames - Math.floor(totalFrames * 0.2);
|
||||
|
||||
// Calculate entrance opacity and transform
|
||||
const getEntranceStyles = (): React.CSSProperties => {
|
||||
const progress = Math.min(frame / entranceDuration, 1);
|
||||
const springProgress = spring({
|
||||
frame: Math.min(frame, entranceDuration),
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100, mass: 0.8 },
|
||||
});
|
||||
|
||||
switch (scene.animation.entrance) {
|
||||
case "fadeIn":
|
||||
return { opacity: interpolate(frame, [0, entranceDuration], [0, 1], { extrapolateRight: "clamp" }) };
|
||||
case "slideUp":
|
||||
return {
|
||||
opacity: interpolate(frame, [0, entranceDuration * 0.5], [0, 1], { extrapolateRight: "clamp" }),
|
||||
transform: `translateY(${interpolate(springProgress, [0, 1], [80, 0])}px)`,
|
||||
};
|
||||
case "slideLeft":
|
||||
return {
|
||||
opacity: interpolate(frame, [0, entranceDuration * 0.5], [0, 1], { extrapolateRight: "clamp" }),
|
||||
transform: `translateX(${interpolate(springProgress, [0, 1], [100, 0])}px)`,
|
||||
};
|
||||
case "slideRight":
|
||||
return {
|
||||
opacity: interpolate(frame, [0, entranceDuration * 0.5], [0, 1], { extrapolateRight: "clamp" }),
|
||||
transform: `translateX(${interpolate(springProgress, [0, 1], [-100, 0])}px)`,
|
||||
};
|
||||
case "zoomIn":
|
||||
return {
|
||||
opacity: interpolate(frame, [0, entranceDuration * 0.5], [0, 1], { extrapolateRight: "clamp" }),
|
||||
transform: `scale(${interpolate(springProgress, [0, 1], [0.5, 1])})`,
|
||||
};
|
||||
case "typewriter":
|
||||
return { opacity: 1 };
|
||||
case "none":
|
||||
default:
|
||||
return { opacity: 1 };
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate exit styles
|
||||
const getExitStyles = (): React.CSSProperties => {
|
||||
if (frame < exitStart) return {};
|
||||
|
||||
const exitProgress = (frame - exitStart) / (totalFrames - exitStart);
|
||||
|
||||
switch (scene.animation.exit) {
|
||||
case "fadeOut":
|
||||
return { opacity: interpolate(exitProgress, [0, 1], [1, 0]) };
|
||||
case "slideDown":
|
||||
return {
|
||||
opacity: interpolate(exitProgress, [0.5, 1], [1, 0], { extrapolateLeft: "clamp" }),
|
||||
transform: `translateY(${interpolate(exitProgress, [0, 1], [0, 80])}px)`,
|
||||
};
|
||||
case "slideLeft":
|
||||
return {
|
||||
opacity: interpolate(exitProgress, [0.5, 1], [1, 0], { extrapolateLeft: "clamp" }),
|
||||
transform: `translateX(${interpolate(exitProgress, [0, 1], [0, -100])}px)`,
|
||||
};
|
||||
case "slideRight":
|
||||
return {
|
||||
opacity: interpolate(exitProgress, [0.5, 1], [1, 0], { extrapolateLeft: "clamp" }),
|
||||
transform: `translateX(${interpolate(exitProgress, [0, 1], [0, 100])}px)`,
|
||||
};
|
||||
case "zoomOut":
|
||||
return {
|
||||
opacity: interpolate(exitProgress, [0.5, 1], [1, 0], { extrapolateLeft: "clamp" }),
|
||||
transform: `scale(${interpolate(exitProgress, [0, 1], [1, 1.5])})`,
|
||||
};
|
||||
case "none":
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Typewriter effect for text
|
||||
const getDisplayText = (text: string): string => {
|
||||
if (scene.animation.entrance !== "typewriter") return text;
|
||||
const charsToShow = Math.floor(
|
||||
interpolate(frame, [0, entranceDuration * 1.5], [0, text.length], {
|
||||
extrapolateRight: "clamp",
|
||||
})
|
||||
);
|
||||
return text.slice(0, charsToShow);
|
||||
};
|
||||
|
||||
// Background
|
||||
const backgroundStyle: React.CSSProperties = scene.backgroundGradient
|
||||
? {
|
||||
background: `linear-gradient(${scene.backgroundGradient.direction}, ${scene.backgroundGradient.from}, ${scene.backgroundGradient.to})`,
|
||||
}
|
||||
: {
|
||||
backgroundColor: scene.style.backgroundColor,
|
||||
};
|
||||
|
||||
const entranceStyles = getEntranceStyles();
|
||||
const exitStyles = getExitStyles();
|
||||
|
||||
// Merge entrance and exit
|
||||
const combinedStyles: React.CSSProperties = {
|
||||
...entranceStyles,
|
||||
...(frame >= exitStart ? exitStyles : {}),
|
||||
};
|
||||
|
||||
// Subtle background animation
|
||||
const bgScale = interpolate(frame, [0, totalFrames], [1, 1.05], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
...backgroundStyle,
|
||||
transform: `scale(${bgScale})`,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Decorative elements */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "-20%",
|
||||
right: "-10%",
|
||||
width: "40%",
|
||||
height: "40%",
|
||||
borderRadius: "50%",
|
||||
background: "rgba(255,255,255,0.05)",
|
||||
filter: "blur(60px)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "-15%",
|
||||
left: "-10%",
|
||||
width: "35%",
|
||||
height: "35%",
|
||||
borderRadius: "50%",
|
||||
background: "rgba(255,255,255,0.03)",
|
||||
filter: "blur(40px)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: scene.style.textAlign === "left" ? "flex-start" : scene.style.textAlign === "right" ? "flex-end" : "center",
|
||||
justifyContent: "center",
|
||||
padding: "80px",
|
||||
maxWidth: "85%",
|
||||
textAlign: scene.style.textAlign,
|
||||
...combinedStyles,
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
{scene.icon && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: scene.style.fontSize * 1.2,
|
||||
marginBottom: "20px",
|
||||
filter: "drop-shadow(0 4px 8px rgba(0,0,0,0.3))",
|
||||
}}
|
||||
>
|
||||
{scene.icon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Title (small label above main text) */}
|
||||
{scene.title && scene.title !== scene.text && (
|
||||
<div
|
||||
style={{
|
||||
color: scene.style.textColor,
|
||||
fontSize: Math.max(scene.style.fontSize * 0.35, 18),
|
||||
fontFamily: scene.style.fontFamily,
|
||||
fontWeight: 600,
|
||||
letterSpacing: "3px",
|
||||
textTransform: "uppercase",
|
||||
marginBottom: "16px",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
{scene.title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main text */}
|
||||
<div
|
||||
style={{
|
||||
color: scene.style.textColor,
|
||||
fontSize: scene.style.fontSize,
|
||||
fontFamily: scene.style.fontFamily,
|
||||
fontWeight: 700,
|
||||
lineHeight: 1.2,
|
||||
textShadow: "0 2px 20px rgba(0,0,0,0.3)",
|
||||
letterSpacing: "-0.5px",
|
||||
}}
|
||||
>
|
||||
{getDisplayText(scene.text)}
|
||||
{scene.animation.entrance === "typewriter" && frame < entranceDuration * 1.5 && (
|
||||
<span
|
||||
style={{
|
||||
opacity: Math.sin(frame * 0.3) > 0 ? 1 : 0,
|
||||
marginLeft: "2px",
|
||||
}}
|
||||
>
|
||||
|
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Subtitle */}
|
||||
{scene.subtitle && (
|
||||
<div
|
||||
style={{
|
||||
color: scene.style.textColor,
|
||||
fontSize: Math.max(scene.style.fontSize * 0.45, 20),
|
||||
fontFamily: scene.style.fontFamily,
|
||||
fontWeight: 400,
|
||||
marginTop: "20px",
|
||||
opacity: 0.8,
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
{getDisplayText(scene.subtitle)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user