mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(videos): redesign all scenes with real app screenshots and proper scaling
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type React from "react";
|
||||
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
|
||||
import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame } from "remotion";
|
||||
import { GradientBlob } from "@/components/GradientBlob";
|
||||
|
||||
const BLOBS = [
|
||||
@@ -41,6 +41,9 @@ const BLOBS = [
|
||||
},
|
||||
];
|
||||
|
||||
const SCREENSHOTS = ["dashboard.png", "resize-tool.png", "editor.png"];
|
||||
const CYCLE_FRAMES = 30;
|
||||
|
||||
export const AmbientOpenScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const fadeIn = interpolate(frame, [0, 45], [0, 1], {
|
||||
@@ -48,11 +51,65 @@ export const AmbientOpenScene: React.FC = () => {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
const currentIdx = Math.floor(frame / CYCLE_FRAMES) % SCREENSHOTS.length;
|
||||
const nextIdx = (currentIdx + 1) % SCREENSHOTS.length;
|
||||
const cycleProgress = (frame % CYCLE_FRAMES) / CYCLE_FRAMES;
|
||||
|
||||
const currentOpacity = interpolate(cycleProgress, [0, 0.8, 1], [0.6, 0.6, 0], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const nextOpacity = interpolate(cycleProgress, [0.7, 1], [0, 0.6], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
const scale = interpolate(frame % CYCLE_FRAMES, [0, CYCLE_FRAMES], [1.05, 1.0], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
const rotation = (currentIdx % 2 === 0 ? 1 : -1) * 3.5;
|
||||
|
||||
return (
|
||||
<AbsoluteFill style={{ opacity: fadeIn }}>
|
||||
{BLOBS.map((blob, i) => (
|
||||
<GradientBlob key={`ambient-${i}`} config={blob} duration={600} />
|
||||
))}
|
||||
|
||||
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
width: 700,
|
||||
height: 450,
|
||||
position: "relative",
|
||||
transform: `rotate(${rotation}deg) scale(${scale})`,
|
||||
borderRadius: 12,
|
||||
overflow: "hidden",
|
||||
boxShadow: "0 20px 60px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile(`screenshots/${SCREENSHOTS[currentIdx]}`)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
opacity: currentOpacity,
|
||||
}}
|
||||
/>
|
||||
<Img
|
||||
src={staticFile(`screenshots/${SCREENSHOTS[nextIdx]}`)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
opacity: nextOpacity,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type React from "react";
|
||||
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
|
||||
import { NumberPunch } from "@/components/NumberPunch";
|
||||
import { COLOR } from "@/lib/colors";
|
||||
|
||||
const PUNCHES = [
|
||||
{ number: "49", descriptor: "tools", frame: 0, size: 120, shake: 2 },
|
||||
{ number: "15", descriptor: "AI models", frame: 37, size: 120, shake: 2 },
|
||||
{ number: "55+", descriptor: "formats", frame: 74, size: 120, shake: 2 },
|
||||
{ number: "1", descriptor: "container", frame: 111, size: 144, shake: 3 },
|
||||
{ number: "49", descriptor: "tools", frame: 0 },
|
||||
{ number: "15", descriptor: "AI models", frame: 37 },
|
||||
{ number: "55+", descriptor: "formats", frame: 74 },
|
||||
{ number: "1", descriptor: "container", frame: 111 },
|
||||
];
|
||||
|
||||
export const NumberPunchScene: React.FC = () => {
|
||||
@@ -16,27 +15,33 @@ export const NumberPunchScene: React.FC = () => {
|
||||
return (
|
||||
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{PUNCHES.map((p, i) => {
|
||||
const visible = frame >= p.frame && frame < p.frame + 37;
|
||||
const flashStart = p.frame - 2;
|
||||
const flashOpacity =
|
||||
i > 0 && frame >= flashStart && frame < flashStart + 2
|
||||
? interpolate(frame, [flashStart, flashStart + 2], [1, 0])
|
||||
: 0;
|
||||
const segmentEnd = i < PUNCHES.length - 1 ? PUNCHES[i + 1].frame : p.frame + 37;
|
||||
const fadeInOpacity = interpolate(frame, [p.frame, p.frame + 4], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const fadeOutOpacity = interpolate(frame, [segmentEnd - 6, segmentEnd], [1, 0], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const isLastPunch = i === PUNCHES.length - 1;
|
||||
const visible = frame >= p.frame && frame < segmentEnd;
|
||||
const opacity = isLastPunch ? fadeInOpacity : Math.min(fadeInOpacity, fadeOutOpacity);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<AbsoluteFill key={p.number} style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
{i > 0 && (
|
||||
<AbsoluteFill style={{ backgroundColor: COLOR.dark, opacity: flashOpacity }} />
|
||||
)}
|
||||
{visible && (
|
||||
<NumberPunch
|
||||
number={p.number}
|
||||
descriptor={p.descriptor}
|
||||
enterFrame={p.frame}
|
||||
numberSize={p.size}
|
||||
shakeIntensity={p.shake}
|
||||
/>
|
||||
)}
|
||||
<AbsoluteFill
|
||||
key={p.number}
|
||||
style={{ justifyContent: "center", alignItems: "center", opacity }}
|
||||
>
|
||||
<NumberPunch
|
||||
number={p.number}
|
||||
descriptor={p.descriptor}
|
||||
enterFrame={p.frame}
|
||||
numberSize={200}
|
||||
shakeIntensity={2}
|
||||
/>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type React from "react";
|
||||
import { AbsoluteFill } from "remotion";
|
||||
import { AbsoluteFill, Img, interpolate, staticFile, useCurrentFrame } from "remotion";
|
||||
import { RotatingTaglines } from "@/components/RotatingTaglines";
|
||||
|
||||
const TAGLINES = [
|
||||
@@ -12,13 +12,33 @@ const TAGLINES = [
|
||||
];
|
||||
|
||||
export const TaglineCascadeScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const totalFrames = 150;
|
||||
|
||||
const bgScale = interpolate(frame, [0, totalFrames], [1.0, 1.1], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
<Img
|
||||
src={staticFile("screenshots/dashboard.png")}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
opacity: 0.15,
|
||||
transform: `scale(${bgScale})`,
|
||||
}}
|
||||
/>
|
||||
</AbsoluteFill>
|
||||
|
||||
<RotatingTaglines
|
||||
lines={TAGLINES}
|
||||
startFrame={0}
|
||||
framesPerLine={25}
|
||||
fontSize={40}
|
||||
fontSize={56}
|
||||
color="white"
|
||||
/>
|
||||
</AbsoluteFill>
|
||||
|
||||
Reference in New Issue
Block a user