import React from "react";
import {
AbsoluteFill,
Audio,
interpolate,
Sequence,
spring,
staticFile,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import {audioFileFor, script, Sentence} from "./data/script";
import {
GAP_FRAMES,
durationForSentence,
hasAudioForSentence,
} from "./data/timing";
const Title: React.FC<Readonly<{progress: number}>> = ({progress}) => {
const opacity = interpolate(progress, [0, 1], [0, 1]);
const translateY = interpolate(progress, [0, 1], [-30, 0]);
return (
<div
style={{
fontFamily:
'"IPAexGothic", "IPAPGothic", "M PLUS Rounded 1c", "Hiragino Maru Gothic ProN", sans-serif',
fontSize: 54,
fontWeight: 700,
color: "#1f2a44",
letterSpacing: 1,
textAlign: "center",
marginTop: 40,
opacity,
transform: `translateY(${translateY}px)`,
textShadow: "0 6px 18px rgba(31, 42, 68, 0.2)",
}}
>
ネコミミはなぜかわいい?
</div>
);
};
const Subtitle: React.FC<Readonly<{text: string; progress: number}>> = ({
text,
progress,
}) => {
const opacity = interpolate(progress, [0, 1], [0, 1]);
const translateY = interpolate(progress, [0, 1], [16, 0]);
return (
<div
style={{
fontFamily:
'"IPAexGothic", "IPAPGothic", "M PLUS Rounded 1c", "Hiragino Maru Gothic ProN", sans-serif',
fontSize: 36,
fontWeight: 700,
color: "#1a1a1a",
lineHeight: 1.4,
padding: "18px 28px",
backgroundColor: "rgba(255, 255, 255, 0.85)",
borderRadius: 18,
border: "2px solid rgba(31, 42, 68, 0.15)",
boxShadow: "0 10px 30px rgba(31, 42, 68, 0.15)",
opacity,
transform: `translateY(${translateY}px)`,
}}
>
{text}
</div>
);
};
const Zundamon: React.FC<Readonly<{bounce: number}>> = ({bounce}) => {
return (
<div
style={{
width: 320,
height: 320,
borderRadius: "50%",
background:
"radial-gradient(circle at 30% 30%, #c9f6a5, #79d36f 70%)",
border: "6px solid rgba(31, 42, 68, 0.1)",
boxShadow: "0 18px 40px rgba(31, 42, 68, 0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
transform: `translateY(${bounce}px)`,
}}
>
<div
style={{
position: "absolute",
top: -60,
left: 40,
width: 90,
height: 90,
background: "#79d36f",
borderRadius: "10% 60% 20% 60%",
transform: "rotate(-18deg)",
border: "6px solid rgba(31, 42, 68, 0.1)",
}}
/>
<div
style={{
position: "absolute",
top: -60,
right: 40,
width: 90,
height: 90,
background: "#79d36f",
borderRadius: "60% 10% 60% 20%",
transform: "rotate(18deg)",
border: "6px solid rgba(31, 42, 68, 0.1)",
}}
/>
<div
style={{
display: "flex",
gap: 36,
}}
>
<div
style={{
width: 36,
height: 46,
borderRadius: "50%",
background: "#1f2a44",
}}
/>
<div
style={{
width: 36,
height: 46,
borderRadius: "50%",
background: "#1f2a44",
}}
/>
</div>
<div
style={{
position: "absolute",
bottom: 68,
width: 90,
height: 38,
borderRadius: "0 0 80px 80px",
borderBottom: "8px solid #1f2a44",
}}
/>
</div>
);
};
const SentenceSegment: React.FC<Readonly<{sentence: Sentence}>> = ({sentence}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const subtitleProgress = spring({
frame,
fps,
config: {damping: 20, mass: 0.7},
});
return (
<>
<div
style={{
position: "absolute",
bottom: 40,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<Subtitle text={sentence.text} progress={subtitleProgress} />
</div>
{hasAudioForSentence(sentence) ? (
<Audio src={staticFile(audioFileFor(sentence.id))} />
) : null}
</>
);
};
export const YukkuriComposition: React.FC = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const titleProgress = spring({
frame,
fps,
config: {damping: 18, mass: 0.6},
});
const bounce = interpolate(
Math.sin((frame / fps) * Math.PI * 2),
[-1, 1],
[-10, 10]
);
let cursor = 0;
const sequences = script.map((sentence, index) => {
const durationInFrames = durationForSentence(sentence, fps);
const from = cursor;
cursor += durationInFrames;
if (index < script.length - 1) {
cursor += GAP_FRAMES;
}
return (
<Sequence key={sentence.id} from={from} durationInFrames={durationInFrames}>
<SentenceSegment sentence={sentence} />
</Sequence>
);
});
return (
<AbsoluteFill
style={{
background:
"radial-gradient(circle at top, #ffe8c7 0%, #ffd3b4 45%, #ffb6b6 100%)",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<div
style={{
position: "absolute",
inset: 0,
backgroundImage:
"radial-gradient(circle at 20% 20%, rgba(255,255,255,0.6) 0, rgba(255,255,255,0) 40%), radial-gradient(circle at 80% 30%, rgba(255,255,255,0.5) 0, rgba(255,255,255,0) 45%), radial-gradient(circle at 30% 80%, rgba(255,255,255,0.4) 0, rgba(255,255,255,0) 50%)",
opacity: 0.8,
}}
/>
<Title progress={titleProgress} />
<div
style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
paddingBottom: 40,
}}
>
<Zundamon bounce={bounce} />
</div>
{sequences}
</AbsoluteFill>
);
};