diff --git a/src/remotion/ShortsVideo.tsx b/src/remotion/ShortsVideo.tsx
index 9cd7368..53f4421 100644
--- a/src/remotion/ShortsVideo.tsx
+++ b/src/remotion/ShortsVideo.tsx
@@ -13,6 +13,10 @@ import {
CompareScene,
BulletScene,
BangScene,
+ QuoteScene,
+ PodiumScene,
+ GridListScene,
+ ScoreboardScene,
MiniKaraoke,
} from "./parts";
import { TweetCard, ThreadCard, NewsCard } from "./cards";
@@ -147,7 +151,7 @@ export type Segment = {
label?: string;
sentences?: Sentence[];
/** optional explicit scene override */
- scene?: "title" | "stat" | "caption" | "cta" | "tweet" | "thread" | "news" | "terminal" | "compare" | "fix" | "bullets" | "bang";
+ scene?: "title" | "stat" | "caption" | "cta" | "tweet" | "thread" | "news" | "terminal" | "compare" | "fix" | "bullets" | "bang" | "quote" | "podium" | "gridlist" | "scoreboard";
stat?: { value: string; label: string };
/** source content (tweet/thread/news) — rendered as a visual card scene */
source?: {
@@ -211,6 +215,12 @@ function pickScene(seg: Segment, index: number, seed = 0): Segment["scene"] | "c
if (seg.stat) return "stat";
const stat = extractStat(seg.text);
if (stat) return "stat";
+ // creative scene variety (round 2): quote/podium/grid/scoreboard rotation
+ const j = (index + seed) % 8;
+ if (j === 3) return "quote";
+ if (j === 5) return "podium";
+ if (j === 6) return "gridlist";
+ if (j === 7) return "scoreboard";
if (i % 3 === 2) return "title";
return "caption";
}
@@ -284,6 +294,58 @@ const Scene: React.FC<{
);
}
+ if (scene === "quote") {
+ return (
+
+
+
+ {label ? : null}
+
+
+
+
+
+ );
+ }
+ if (scene === "podium") {
+ return (
+
+
+
+ {label ? : null}
+
+
+
+
+
+ );
+ }
+ if (scene === "gridlist") {
+ return (
+
+
+
+ {label ? : null}
+
+
+
+
+
+ );
+ }
+ if (scene === "scoreboard") {
+ return (
+
+
+
+ {label ? : null}
+
+
+
+
+
+ );
+ }
if (scene === "caption" || scene === "title" || scene === "cta") {
// regular scenes get an animated asset above the card (variety!)
return (
diff --git a/src/remotion/parts.tsx b/src/remotion/parts.tsx
index 2421188..894929a 100644
--- a/src/remotion/parts.tsx
+++ b/src/remotion/parts.tsx
@@ -762,4 +762,155 @@ export const MiniKaraoke: React.FC<{
))}
);
+};
+
+/* ═══════════════════════════════════════════════════════════════════════
+ SCENE ROUND 2 — 4 more creative scene types (mono flat).
+ ═══════════════════════════════════════════════════════════════════════ */
+
+/** Quote scene — oversized quotation mark + emphasized excerpt + attribution. */
+export const QuoteScene: React.FC<{ text: string; size?: number }> = ({ text, size = 50 }) => {
+ const frame = useCurrentFrame();
+ const { fps } = useVideoConfig();
+ const p = spring({ frame, fps, config: { damping: 16, stiffness: 70 } });
+ const words = text.split(" ");
+ const quote = words.length > 8 ? words.slice(0, -3).join(" ") : text;
+ const byline = words.length > 8 ? words.slice(-3).join(" ") : "";
+ return (
+
+
+ “
+
+
+ {quote}
+
+ {byline && (
+
+ — {byline}
+
+ )}
+
+ );
+};
+
+/** Podium scene — 1-2-3 columns with the middle one elevated (ranking). */
+export const PodiumScene: React.FC<{ text: string; size?: number }> = ({ text, size = 36 }) => {
+ const frame = useCurrentFrame();
+ const { fps } = useVideoConfig();
+ const p = spring({ frame, fps, config: { damping: 16, stiffness: 80 } });
+ const words = text.split(" ");
+ const picks = words.length >= 3 ? [words[0], words[1], words[2]] : ["TOP", "3", "PICKS"];
+ const heights = [200, 280, 150];
+ return (
+
+ {picks.map((w, i) => (
+
+
+ {w}
+
+
+ {i + 1}
+
+
+ ))}
+
+ );
+};
+
+/** Grid list — 2×2 keyword tiles popping in with check corners. */
+export const GridListScene: React.FC<{ text: string; size?: number }> = ({ text, size = 34 }) => {
+ const frame = useCurrentFrame();
+ const { fps } = useVideoConfig();
+ const words = text.split(" ");
+ const tiles = words.length >= 4 ? words.slice(0, 4) : ["AGENT", text.slice(0, 12).toUpperCase(), "TOOLS", "MEMORY"];
+ return (
+
+ {tiles.map((w, i) => {
+ const p = spring({ frame: Math.max(0, frame - i * 6), fps, config: { damping: 16, stiffness: 100 } });
+ const r = i % 2 === 0 ? 14 : 20;
+ return (
+
+ {w}
+
+ );
+ })}
+
+ );
+};
+
+/** Scoreboard — Q&A panel (question highlighted, answer slides in). */
+export const ScoreboardScene: React.FC<{ text: string; size?: number }> = ({ text, size = 38 }) => {
+ const frame = useCurrentFrame();
+ const { fps } = useVideoConfig();
+ const p = spring({ frame, fps, config: { damping: 16, stiffness: 75 } });
+ const words = text.split(" ");
+ const q = words.slice(0, Math.ceil(words.length / 2)).join(" ");
+ const a = words.slice(Math.ceil(words.length / 2)).join(" ");
+ return (
+
+
+ Q
+ {q}
+
+
+ A
+ {a}
+
+
+ );
};
\ No newline at end of file