import React from "react";
import {
useCurrentFrame,
useVideoConfig,
AbsoluteFill,
Img,
staticFile,
interpolate,
} from "remotion";
import type { RemotionScene } from "../Root";
import type { RenderSegmentExtension } from "@pipeline/shared";
import { GITHUB_DAILY_PICK_PALETTE } from "../base/theme/colors";
import { SubtitleBar } from "../base/components/subtitle-bar";
import { Watermark } from "../base/components/watermark";
import { formatCount } from "@pipeline/shared";
import { ColorDot } from "../github-trending/index";
interface GithubDailyPickSceneProps {
scene: RemotionScene;
sceneIndex: number;
totalScenes: number;
totalFrames: number;
title: string;
channelName?: string;
}
/** Dark-stage tag chip — the deep-dive board keeps the dark look, so it can't
* reuse github-trending's (now light) Tag. Dark glass chip, light accent. */
const Tag: React.FC<{ accent: string; children: React.ReactNode; size?: "default" | "large" }> = ({ accent, children, size = "default" }) => (
{children}
);
/**
* github-daily-pick content scene — one ACT chapter of the day's six-act
* recommendation (钩子/痛点/亮相/亮点/上手/总结). Unlike the board templates (headline = repo fullName), here the scene
* title is the act ("快速上手"), the repo identity moves to a
* compact sub-line, and the body is the scene's keyframe cards plus the repo's
* social preview. A top-right progress badge (03 / 07) reinforces the
* deep-dive chapter structure.
*/
const GithubDailyPickScene: React.FC = ({
scene,
sceneIndex,
totalScenes,
channelName,
}) => {
const frame = useCurrentFrame();
const { width, height, durationInFrames } = useVideoConfig();
const isPortrait = height > width;
const palette = GITHUB_DAILY_PICK_PALETTE[sceneIndex % GITHUB_DAILY_PICK_PALETTE.length];
const github: RenderSegmentExtension | undefined =
scene.extension?.type === "github-daily-pick" ? scene.extension : undefined;
const sceneDur = durationInFrames;
const fadeOpacity = interpolate(
frame,
[0, 8, sceneDur - 8, sceneDur],
[0, 1, 1, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
const socialPreview = github?.images?.socialPreview;
const starHistory = github?.images?.starHistory;
const pad = isPortrait ? 48 : 80;
const footerReserved = isPortrait ? 500 : 140;
const repo = github?.repo;
const language = repo?.language || "";
const languageColor = repo?.languageColor || palette.accent;
const starsLabel = repo?.stars != null ? `${formatCount(repo.stars)} stars` : "";
const license = repo?.license || "";
const fullName = repo?.fullName || "";
if (!github) {
return (
{scene.title || scene.captionOrigin}
{(scene.caption?.length ?? 0) > 0 && }
);
}
// The scene's cards, ordered as produced. Card kinds are free-form strings
// (功能简介 / 快速上手 / 对比 / 安装 ...); shown as titled sections.
const cards = (scene.cardList ?? []).filter((c) => (c.desc ?? "").trim());
const badge = `${String(sceneIndex + 1).padStart(2, "0")} / ${String(totalScenes).padStart(2, "0")}`;
const renderHeader = (fontSize: number, tagSize: "default" | "large", dotSize: number) => (
{/* Aspect title — the chapter headline (from displayText) */}
{scene.title || fullName}
{/* Repo identity — compact sub-line under the act title */}
{fullName}
{language && (
{language}
)}
{starsLabel && {starsLabel}}
{license && {license}}
);
const renderBadge = (scale: number) => (
{String(sceneIndex + 1).padStart(2, "0")}
/ {String(totalScenes).padStart(2, "0")}
);
const cardsRow = (size: "default" | "large") => (
{cards.slice(0, size === "large" ? 2 : 3).map((card, i) => (
))}
);
return (
{/* Subtle grid overlay (matches other templates' aesthetic) */}
{/* Top accent line */}
{isPortrait ? (
{renderHeader(84, "large", 16)}
{renderBadge(0.9)}
{socialPreview && (
)}
{cardsRow("large")}
) : (
<>
{renderHeader(64, "default", 14)}
{renderBadge(1)}
{socialPreview && (
{starHistory && (
)}
)}
{cardsRow("default")}
>
)}
{(scene.caption?.length ?? 0) > 0 && (
)}
);
};
const ImageFrame: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
);
const AspectSection: React.FC<{
title: string;
accent: string;
body: string;
size?: "default" | "large";
}> = ({ title, accent, body, size = "default" }) => {
const isLarge = size === "large";
const cleanBody = (body ?? "").replace(/[。,、,.;;::\s]+$/, "");
return (
);
};
export default GithubDailyPickScene;