| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- import React from "react";
- import {
- useCurrentFrame,
- useVideoConfig,
- Sequence,
- 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 { LowerThird } from "../base/components/lower-third";
- import { Watermark } from "../base/components/watermark";
- import { HeadlineCard } from "./components/headline-card";
- import { NewsTicker } from "./components/news-ticker";
- interface NewsSceneProps {
- scene: RemotionScene;
- sceneIndex: number;
- totalScenes: number;
- totalFrames: number;
- title: string;
- }
- const NewsScene: React.FC<NewsSceneProps> = ({
- scene,
- sceneIndex,
- totalScenes,
- totalFrames,
- title,
- }) => {
- const frame = useCurrentFrame();
- const { fps } = useVideoConfig();
- const colors = THEMES.news;
- // Scene-level fade
- const fadeOpacity = interpolate(
- frame,
- [0, 8, scene.endFrame - scene.startFrame - 8, scene.endFrame - scene.startFrame],
- [0, 1, 1, 0],
- { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
- );
- return (
- <AbsoluteFill style={{ opacity: fadeOpacity }}>
- <Background
- colors={colors}
- sceneIndex={sceneIndex}
- totalScenes={totalScenes}
- />
- {/* Breaking news badge */}
- {sceneIndex === 0 && (
- <div
- style={{
- position: "absolute",
- top: 40,
- left: 40,
- display: "flex",
- alignItems: "center",
- gap: 16,
- }}
- >
- <div
- style={{
- backgroundColor: "#dc2626",
- color: "white",
- padding: "8px 20px",
- fontSize: 20,
- fontWeight: 800,
- fontFamily: "sans-serif",
- borderRadius: 4,
- letterSpacing: 2,
- }}
- >
- BREAKING
- </div>
- <div
- style={{
- fontSize: 20,
- color: "#94a3b8",
- fontFamily: "sans-serif",
- }}
- >
- {title}
- </div>
- </div>
- )}
- {/* Main content area */}
- <div
- style={{
- position: "absolute",
- top: 120,
- left: 80,
- right: 80,
- bottom: 160,
- display: "flex",
- flexDirection: "column",
- justifyContent: "center",
- overflow: "hidden",
- }}
- >
- {sceneIndex === 0 ? (
- // Intro scene: big headline
- <div style={{ overflow: "hidden" }}>
- <div
- style={{
- fontSize: scene.narration.length > 60 ? 40 : 64,
- fontWeight: 800,
- color: "white",
- fontFamily: "sans-serif",
- lineHeight: 1.2,
- marginBottom: 24,
- maxHeight: 260,
- overflow: "hidden",
- display: "-webkit-box",
- WebkitLineClamp: 4,
- WebkitBoxOrient: "vertical",
- }}
- >
- {scene.narration}
- </div>
- {scene.keyframes.map((kf, i) => (
- <HeadlineCard
- key={i}
- headline={kf.content}
- index={i}
- />
- ))}
- </div>
- ) : (
- // Other scenes: cards layout
- <div style={{ overflow: "hidden" }}>
- <div
- style={{
- fontSize: scene.narration.length > 60 ? 30 : 42,
- fontWeight: 700,
- color: "white",
- fontFamily: "sans-serif",
- marginBottom: 32,
- lineHeight: 1.3,
- maxHeight: 200,
- overflow: "hidden",
- display: "-webkit-box",
- WebkitLineClamp: 4,
- WebkitBoxOrient: "vertical",
- }}
- >
- {scene.narration}
- </div>
- <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
- {scene.keyframes
- .filter((kf) => kf.type === "text" || kf.type === "highlight")
- .map((kf, i) => (
- <HeadlineCard
- key={i}
- headline={kf.content}
- index={i}
- />
- ))}
- </div>
- </div>
- )}
- </div>
- {/* Subtitle bar */}
- {scene.wordTimestamps.length > 0 && (
- <SubtitleBar wordTimestamps={scene.wordTimestamps} />
- )}
- {/* News ticker */}
- <NewsTicker
- headlines={scene.keyframes.map((kf) => kf.content)}
- />
- <Watermark />
- </AbsoluteFill>
- );
- };
- export default NewsScene;
|