Remotion LabRemotion Lab
返回模板庫

MCP 章節卡片

以大字標題呈現「PHASE 3 / MCP」章節過場,中央 MCP 插頭圖示以描邊動畫展開,兩側伸出連接線後顯示 GitHub 與 Vercel 圖示,最終漸出。

chapter-cardMCPtransition過場
提示詞(可直接修改內容)
import React from "react";
import {
  AbsoluteFill,
  useCurrentFrame,
  useVideoConfig,
  Sequence,
  interpolate,
  spring,
  Audio,
  staticFile,
} from "remotion";

const colors = {
  background: "#0A0E14",
  text: "#FFFFFF",
  accent: "#00D4AA",
  accentSecondary: "#4DA3FF",
  dimmed: "rgba(255, 255, 255, 0.6)",
};
const fonts = { main: "'Inter', 'Noto Sans TC', sans-serif" };

const AUDIO = {
  whooshIn: staticFile("audio/connection/woosh.wav"),
  thump: staticFile("audio/connection/soft-impact.wav"),
};

export const MCP-CHAPTER-CARD-DURATION-FRAMES = 75;

const McpPlugIcon: React.FC<{ size: number; drawProgress: number }> = ({
  size,
  drawProgress,
}) => {
  const dashOffset = interpolate(drawProgress, [0, 1], [300, 0]);
  return (
    <svg width={size} height={size} viewBox="0 0 64 64" fill="none">
      <rect
        x="20"
        y="16"
        width="24"
        height="32"
        rx="6"
        stroke={colors.accent}
        strokeWidth={2.5}
        strokeDasharray={300}
        strokeDashoffset={dashOffset}
      />
      <line
        x1="28"
        y1="16"
        x2="28"
        y2="8"
        stroke={colors.accent}
        strokeWidth={2.5}
        strokeLinecap="round"
        strokeDasharray={30}
        strokeDashoffset={interpolate(drawProgress, [0.3, 0.7], [30, 0], {
          extrapolateLeft: "clamp",
          extrapolateRight: "clamp",
        })}
      />
      <line
        x1="36"
        y1="16"
        x2="36"
        y2="8"
        stroke={colors.accent}
        strokeWidth={2.5}
        strokeLinecap="round"
        strokeDasharray={30}
        strokeDashoffset={interpolate(drawProgress, [0.3, 0.7], [30, 0], {
          extrapolateLeft: "clamp",
          extrapolateRight: "clamp",
        })}
      />
      <circle
        cx="32"
        cy="32"
        r="4"
        fill={colors.accent}
        opacity={interpolate(drawProgress, [0.6, 1], [0, 1], {
          extrapolateLeft: "clamp",
          extrapolateRight: "clamp",
        })}
      />
    </svg>
  );
};

const GitHubIcon: React.FC<{ size: number; opacity: number }> = ({
  size,
  opacity,
}) => (
  <svg
    width={size}
    height={size}
    viewBox="0 0 48 48"
    fill="none"
    style={{ opacity }}
  >
    <circle cx="24" cy="24" r="18" stroke={colors.accentSecondary} strokeWidth={2} />
    <path
      d="M20 36c-6 2-6-3-8-3m16 6v-5.3a4.6 4.6 0 00-1.3-3.6c4.4-.5 9-2.2 9-9.8a7.6 7.6 0 00-2-5.3 7.1 7.1 0 00-.2-5.2s-1.6-.5-5.3 2a18.4 18.4 0 00-9.8 0c-3.7-2.5-5.3-2-5.3-2a7.1 7.1 0 00-.2 5.2 7.6 7.6 0 00-2 5.3c0 7.6 4.6 9.3 9 9.8a4.6 4.6 0 00-1.3 3.6V36"
      stroke={colors.accentSecondary}
      strokeWidth={1.8}
      strokeLinecap="round"
      strokeLinejoin="round"
    />
  </svg>
);

const VercelIcon: React.FC<{ size: number; opacity: number }> = ({
  size,
  opacity,
}) => (
  <svg
    width={size}
    height={size}
    viewBox="0 0 48 48"
    fill="none"
    style={{ opacity }}
  >
    <circle cx="24" cy="24" r="18" stroke={colors.accentSecondary} strokeWidth={2} />
    <polygon
      points="24,12 38,36 10,36"
      fill="none"
      stroke={colors.accentSecondary}
      strokeWidth={2}
      strokeLinejoin="round"
    />
  </svg>
);

const ForkLines: React.FC<{ progress: number }> = ({ progress }) => {
  const lineLen = interpolate(progress, [0, 1], [0, 180]);
  const opacity = interpolate(progress, [0, 0.1], [0, 0.7], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <svg
      width={500}
      height={60}
      viewBox="0 0 500 60"
      fill="none"
      style={{ opacity }}
    >
      <line
        x1="250"
        y1="30"
        x2={250 - lineLen}
        y2="30"
        stroke={colors.accent}
        strokeWidth={2}
        strokeLinecap="round"
      />
      <line
        x1="250"
        y1="30"
        x2={250 + lineLen}
        y2="30"
        stroke={colors.accent}
        strokeWidth={2}
        strokeLinecap="round"
      />
      <circle cx="250" cy="30" r="4" fill={colors.accent} />
    </svg>
  );
};

export const Scene09-McpChapterCard: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const labelOpacity = interpolate(frame, [0, 8], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const titleSpring = spring({
    frame,
    fps,
    config: { damping: 14, mass: 0.6, stiffness: 160 },
  });
  const titleScale = interpolate(titleSpring, [0, 1], [0.95, 1]);
  const titleOpacity = interpolate(frame, [0, 8], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const iconDrawProgress = interpolate(frame, [4, 22], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const forkProgress = interpolate(frame, [15, 35], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const sideIconOpacity = interpolate(frame, [25, 35], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const fadeOut = interpolate(frame, [60, 75], [1, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <AbsoluteFill style={{ backgroundColor: colors.background }}>
      <Sequence from={0} durationInFrames={20}>
        <Audio src={AUDIO.whooshIn} volume={0.3} />
      </Sequence>
      <Sequence from={12} durationInFrames={20}>
        <Audio src={AUDIO.thump} volume={0.25} />
      </Sequence>

      <AbsoluteFill
        style={{
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          justifyContent: "center",
          opacity: fadeOut,
        }}
      >
        <div
          style={{
            fontSize: 24,
            fontWeight: 600,
            color: colors.dimmed,
            letterSpacing: 6,
            textTransform: "uppercase",
            opacity: labelOpacity,
            marginBottom: 24,
            fontFamily: fonts.main,
          }}
        >
          PHASE 3
        </div>

        <div
          style={{
            fontSize: 120,
            fontWeight: 800,
            color: colors.text,
            opacity: titleOpacity,
            transform: `scale(${titleScale})`,
            fontFamily: fonts.main,
            letterSpacing: 8,
            marginBottom: 40,
          }}
        >
          MCP
        </div>

        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 0,
            position: "relative",
          }}
        >
          <GitHubIcon size={56} opacity={sideIconOpacity} />

          <div style={{ width: 500, display: "flex", justifyContent: "center" }}>
            <ForkLines progress={forkProgress} />
          </div>

          <VercelIcon size={56} opacity={sideIconOpacity} />
        </div>

        <div
          style={{
            position: "absolute",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, 10px)",
          }}
        >
          <McpPlugIcon size={80} drawProgress={iconDrawProgress} />
        </div>
      </AbsoluteFill>
    </AbsoluteFill>
  );
};

登入後查看完整程式碼