feat(film): screen frame (with placeholder fallback) + ken burns
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";
|
||||||
|
|
||||||
|
// Slow zoom/pan over its children across the whole sequence the component lives in.
|
||||||
|
export const KenBurns: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
from?: number;
|
||||||
|
to?: number;
|
||||||
|
durationInFrames: number;
|
||||||
|
}> = ({ children, from = 1.0, to = 1.08, durationInFrames }) => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const scale = interpolate(frame, [0, durationInFrames], [from, to], {
|
||||||
|
extrapolateRight: "clamp",
|
||||||
|
});
|
||||||
|
return <div style={{ width: "100%", height: "100%", transform: `scale(${scale})` }}>{children}</div>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { AbsoluteFill, staticFile } from "remotion";
|
||||||
|
import { theme } from "../theme";
|
||||||
|
|
||||||
|
export const ScreenFrame: React.FC<{
|
||||||
|
captureId: string;
|
||||||
|
scale?: number;
|
||||||
|
}> = ({ captureId, scale = 0.86 }) => {
|
||||||
|
const [errored, setErrored] = useState(false);
|
||||||
|
const src = staticFile(`captures/${captureId}.png`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill style={{ alignItems: "center", justifyContent: "center", backgroundColor: theme.bg }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${scale * 100}%`,
|
||||||
|
aspectRatio: "16 / 9",
|
||||||
|
borderRadius: 16,
|
||||||
|
overflow: "hidden",
|
||||||
|
border: `1px solid ${theme.border}`,
|
||||||
|
boxShadow: "0 40px 120px rgba(0,0,0,0.55)",
|
||||||
|
backgroundColor: "#0c0c0e",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ height: 34, display: "flex", alignItems: "center", gap: 8, padding: "0 14px", background: "rgba(255,255,255,0.04)", borderBottom: `1px solid ${theme.border}` }}>
|
||||||
|
<span style={{ width: 11, height: 11, borderRadius: 999, background: "#ef4444" }} />
|
||||||
|
<span style={{ width: 11, height: 11, borderRadius: 999, background: "#f59e0b" }} />
|
||||||
|
<span style={{ width: 11, height: 11, borderRadius: 999, background: "#10b981" }} />
|
||||||
|
<span style={{ marginLeft: 14, color: theme.textDim, fontFamily: theme.mono, fontSize: 13 }}>app.lynkeduppro.com</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ position: "absolute", top: 34, left: 0, right: 0, bottom: 0 }}>
|
||||||
|
{errored ? (
|
||||||
|
<div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: theme.textDim, fontFamily: theme.mono, fontSize: 22, background: "#0c0c0e" }}>
|
||||||
|
[capture missing: {captureId}]
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
onError={() => setErrored(true)}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: "top center", display: "block" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user