| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- import React from "react";
- import {
- useCurrentFrame,
- useVideoConfig,
- AbsoluteFill,
- interpolate,
- Easing,
- } from "remotion";
- import type { RemotionScene } from "../Root";
- import { THEMES } from "../base/theme/colors";
- import { Background } from "../base/components/background";
- import { SubtitleBar } from "../base/components/subtitle-bar";
- import { AnimatedText } from "../base/components/animated-text";
- import { Watermark } from "../base/components/watermark";
- import { QuoteBlock } from "./components/quote-block";
- interface OpinionSceneProps {
- scene: RemotionScene;
- sceneIndex: number;
- totalScenes: number;
- totalFrames: number;
- title: string;
- channelName?: string;
- }
- const OpinionScene: React.FC<OpinionSceneProps> = ({
- scene,
- sceneIndex,
- totalScenes,
- totalFrames,
- title,
- channelName,
- }) => {
- const frame = useCurrentFrame();
- const colors = THEMES.opinion;
- const visualText = scene.displayText ?? scene.narration;
- const fadeOpacity = interpolate(
- frame,
- [0, 10, scene.endFrame - scene.startFrame - 10, scene.endFrame - scene.startFrame],
- [0, 1, 1, 0],
- { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
- );
- return (
- <AbsoluteFill style={{ opacity: fadeOpacity }}>
- <Background
- colors={colors}
- sceneIndex={sceneIndex}
- totalScenes={totalScenes}
- />
- {/* Opinion badge */}
- <div
- style={{
- position: "absolute",
- top: 40,
- left: 40,
- display: "flex",
- alignItems: "center",
- gap: 12,
- }}
- >
- <div
- style={{
- width: 8,
- height: 8,
- borderRadius: 4,
- backgroundColor: colors.primary,
- }}
- />
- <div
- style={{
- fontSize: 18,
- color: "#9ca3af",
- fontFamily: "Noto Sans SC",
- letterSpacing: 1,
- }}
- >
- 观点 · {title}
- </div>
- </div>
- {/* Main content */}
- <div
- style={{
- position: "absolute",
- top: 120,
- left: 80,
- right: 80,
- bottom: 160,
- display: "flex",
- flexDirection: "column",
- justifyContent: "center",
- gap: 40,
- overflow: "hidden",
- }}
- >
- {sceneIndex === 0 ? (
- // Opening: quote style
- <QuoteBlock
- quote={visualText}
- color={colors.primaryLight}
- />
- ) : (
- <>
- <AnimatedText
- text={visualText}
- fontSize={40}
- color="white"
- fontWeight={500}
- delay={5}
- />
- {scene.keyframes
- .filter((kf) => kf.type === "text" || kf.type === "highlight")
- .map((kf, i) => (
- <QuoteBlock
- key={i}
- quote={kf.content}
- color={colors.primaryLight}
- />
- ))}
- </>
- )}
- </div>
- {scene.wordTimestamps.length > 0 && (
- <SubtitleBar wordTimestamps={scene.wordTimestamps} />
- )}
- <Watermark text={channelName} />
- </AbsoluteFill>
- );
- };
- export default OpinionScene;
|