Remotion LabRemotion Lab
返回模板庫

LINE 整合階段介紹卡

展示 LINE Messaging API 整合所需的四個核心配置項目,以動態勾選清單呈現:Provider、Messaging API、Secret / Token / User ID、Webhook Verify,每個項目依序亮起並打勾。

checklistLINEphase動畫清單
提示詞(可直接修改內容)
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",
  warning: "#FFB547",
  danger: "#FF6B6B",
  dimmed: "rgba(255, 255, 255, 0.6)",
  cardBg: "rgba(255, 255, 255, 0.05)",
  border: "rgba(0, 212, 170, 0.3)",
};
const fonts = { main: "'Inter', 'Noto Sans TC', sans-serif" };

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

export const LINE-PHASE-INTRO-DURATION-FRAMES = 180;

const CHECKLIST-ITEMS = [
  { label: "Provider", startFrame: 12 },
  { label: "Messaging API", startFrame: 30 },
  { label: "Secret / Token / User ID", startFrame: 48 },
  { label: "Webhook Verify", startFrame: 66 },
];

const CheckMark: React.FC<{ progress: number; size?: number }> = ({
  progress,
  size = 24,
}) => {
  const checkDashOffset = interpolate(progress, [0, 1], [30, 0]);
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
      <circle
        cx="12"
        cy="12"
        r="10"
        stroke={colors.accent}
        strokeWidth={2}
        opacity={0.3 + 0.7 * progress}
      />
      <polyline
        points="7,12 10.5,15.5 17,9"
        fill="none"
        stroke={colors.accent}
        strokeWidth={2.5}
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeDasharray={30}
        strokeDashoffset={checkDashOffset}
      />
    </svg>
  );
};

const EmptyCircle: React.FC<{ size?: number }> = ({ size = 24 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
    <circle
      cx="12"
      cy="12"
      r="10"
      stroke="rgba(255,255,255,0.25)"
      strokeWidth={2}
    />
  </svg>
);

const ChecklistRow: React.FC<{
  label: string;
  startFrame: number;
  frame: number;
  fps: number;
}> = ({ label, startFrame, frame, fps }) => {
  const progress = interpolate(frame, [startFrame, startFrame + 12], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const isActive = frame >= startFrame;
  const textOpacity = interpolate(progress, [0, 1], [0.4, 1]);
  const bgOpacity = interpolate(progress, [0, 1], [0, 0.1]);

  const popCurve = isActive
    ? spring({
        frame: frame - startFrame,
        fps,
        config: { damping: 10, mass: 0.4, stiffness: 200 },
      })
    : 0;
  const scaleY = 1 + 0.02 * interpolate(popCurve, [0, 0.5, 1], [0, 1, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const borderOpacity = interpolate(progress, [0, 1], [0.05, 0.2]);

  return (
    <div
      style={{
        display: "flex",
        alignItems: "center",
        gap: 16,
        padding: "14px 24px",
        borderRadius: 14,
        backgroundColor: `rgba(255, 255, 255, ${bgOpacity})`,
        border: `1.5px solid rgba(0, 212, 170, ${borderOpacity})`,
        transform: `scaleY(${scaleY})`,
        minWidth: 420,
      }}
    >
      <div style={{ flexShrink: 0 }}>
        {isActive ? <CheckMark progress={progress} /> : <EmptyCircle />}
      </div>
      <div
        style={{
          fontSize: 32,
          fontWeight: 600,
          color: `rgba(255, 255, 255, ${textOpacity})`,
          fontFamily: fonts.main,
          whiteSpace: "nowrap",
        }}
      >
        {label}
      </div>
    </div>
  );
};

const LineLogoIcon: React.FC<{ size: number }> = ({ size }) => (
  <svg width={size} height={size} viewBox="0 0 48 48" fill="none">
    <path
      d="M24 4C12 4 2 12.5 2 23c0 5.5 2.5 10.5 6.5 14L6 44l10-5c2.5.7 5.2 1 8 1 12 0 22-8.5 22-19S36 4 24 4z"
      stroke="#00B900"
      strokeWidth={2.5}
      strokeLinecap="round"
      strokeLinejoin="round"
    />
  </svg>
);

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

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

  const leftOpacity = interpolate(frame, [0, 8], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const leftY = interpolate(frame, [0, 8], [10, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const leftScale = interpolate(frame, [0, 12], [0.98, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const rightOpacity = interpolate(frame, [0, 12], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const rightX = interpolate(frame, [0, 12], [12, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <AbsoluteFill style={{ backgroundColor: "#0B0B0F" }}>
      <Sequence from={0} durationInFrames={15}>
        <Audio src={AUDIO.whooshIn} volume={0.25} />
      </Sequence>
      <Sequence from={8} durationInFrames={20}>
        <Audio src={AUDIO.thumpSoft} volume={0.25} />
      </Sequence>
      <Sequence from={12} durationInFrames={12}>
        <Audio src={AUDIO.tick} volume={0.15} />
      </Sequence>
      <Sequence from={30} durationInFrames={12}>
        <Audio src={AUDIO.tick} volume={0.15} />
      </Sequence>
      <Sequence from={48} durationInFrames={12}>
        <Audio src={AUDIO.tick} volume={0.15} />
      </Sequence>
      <Sequence from={66} durationInFrames={12}>
        <Audio src={AUDIO.tick} volume={0.15} />
      </Sequence>

      <AbsoluteFill
        style={{
          background:
            "radial-gradient(circle at 35% 45%, rgba(255,255,255,0.07), rgba(0,0,0,0) 60%)",
          opacity: glowOpacity,
        }}
      />

      <AbsoluteFill
        style={{
          display: "flex",
          flexDirection: "row",
          alignItems: "center",
          justifyContent: "center",
          padding: "80px 120px",
          gap: 100,
        }}
      >
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            alignItems: "flex-start",
            gap: 16,
            opacity: leftOpacity,
            transform: `translateY(${leftY}px) scale(${leftScale})`,
            flex: "0 0 auto",
          }}
        >
          <div
            style={{
              fontSize: 22,
              fontWeight: 600,
              color: colors.dimmed,
              letterSpacing: 4,
              textTransform: "uppercase",
              fontFamily: fonts.main,
            }}
          >
            PHASE 2
          </div>

          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: 20,
            }}
          >
            <div
              style={{
                fontSize: 96,
                fontWeight: 800,
                color: colors.text,
                fontFamily: fonts.main,
                lineHeight: 1,
              }}
            >
              LINE
            </div>
            <LineLogoIcon size={64} />
          </div>
        </div>

        <div
          style={{
            display: "flex",
            flexDirection: "column",
            gap: 14,
            opacity: rightOpacity,
            transform: `translateX(${rightX}px)`,
          }}
        >
          {CHECKLIST-ITEMS.map((item, idx) => (
            <ChecklistRow
              key={idx}
              label={item.label}
              startFrame={item.startFrame}
              frame={frame}
              fps={fps}
            />
          ))}
        </div>
      </AbsoluteFill>
    </AbsoluteFill>
  );
};

登入後查看完整程式碼