Remotion LabRemotion Lab
返回模板庫

社群數據牆

6 個社群平台統計數據卡片排成 3×2 網格,各含平台圖示、主要指標大數字動態計數、次要指標列與上升趨勢徽章,卡片依序以縮放彈性動畫登場。

社群商務橫式
提示詞(可直接修改內容)
import {
  AbsoluteFill,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";
import React from "react";

const STATS = [
  {
    platform: "YouTube",
    icon: "▶",
    color: "#ff0000",
    bg: "#1a0000",
    main: 28400,
    mainLabel: "訂閱者",
    metrics: ["12萬 次觀看", "4.8% 互動率"],
  },
  {
    platform: "Instagram",
    icon: "◎",
    color: "#e1306c",
    bg: "#1a0010",
    main: 15200,
    mainLabel: "追蹤者",
    metrics: ["8.2% 觸及率", "342 平均按讚"],
  },
  {
    platform: "X (Twitter)",
    icon: "✕",
    color: "#1d9bf0",
    bg: "#001a2a",
    main: 9840,
    mainLabel: "追蹤者",
    metrics: ["2.1% 互動率", "187 平均轉推"],
  },
  {
    platform: "TikTok",
    icon: "♪",
    color: "#69c9d0",
    bg: "#001a1a",
    main: 42100,
    mainLabel: "粉絲",
    metrics: ["18.4萬 觀看", "12.3% 互動率"],
  },
  {
    platform: "LinkedIn",
    icon: "in",
    color: "#0a66c2",
    bg: "#001020",
    main: 6320,
    mainLabel: "連結",
    metrics: ["3.4萬 曝光", "5.2% 點擊率"],
  },
  {
    platform: "GitHub",
    icon: "⑂",
    color: "#6e40c9",
    bg: "#0d001a",
    main: 2840,
    mainLabel: "Stars",
    metrics: ["142 Forks", "28 貢獻者"],
  },
];

const COLS = 3;
const ROWS = 2;
const CARD-W = 560;
const CARD-H = 240;
const COL-GAP = 30;
const ROW-GAP = 28;
const GRID-W = COLS * CARD-W + (COLS - 1) * COL-GAP;
const GRID-H = ROWS * CARD-H + (ROWS - 1) * ROW-GAP;
const GRID-LEFT = (1920 - GRID-W) / 2;
const GRID-TOP = 80 + (1080 - 80 - GRID-H) / 2;

const GRID-POSITIONS = STATS.map((_, i) => ({
  col: i % COLS,
  row: Math.floor(i / COLS),
}));

function formatCount(n: number): string {
  if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, "") + "萬";
  if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "K";
  return String(n);
}

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

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

  const cardProgressList = STATS.map((_, i) => {
    const startFrame = i * 12 + 5;
    return spring({
      frame: frame - startFrame,
      fps,
      config: { damping: 20, stiffness: 150 },
      durationInFrames: 18,
    });
  });

  const countProgressList = STATS.map((_, i) => {
    const start = i * 12 + 5 + 15;
    return interpolate(frame, [start, start + 60], [0, 1], {
      extrapolateLeft: "clamp",
      extrapolateRight: "clamp",
    });
  });

  return (
    <AbsoluteFill
      style={{
        background: "#080808",
        fontFamily: "sans-serif",
      }}
    >
      <div
        style={{
          position: "absolute",
          top: 28,
          left: 0,
          right: 0,
          display: "flex",
          justifyContent: "center",
          opacity: headerOpacity,
        }}
      >
        <span
          style={{
            fontSize: 26,
            fontWeight: 800,
            color: "rgba(255,255,255,0.18)",
            letterSpacing: 4,
            textTransform: "uppercase",
          }}
        >
          社群媒體數據總覽
        </span>
      </div>

      {STATS.map((stat, i) => {
        const progress = cardProgressList[i];
        const countProgress = countProgressList[i];
        const { col, row } = GRID-POSITIONS[i];

        const scale = interpolate(progress, [0, 1], [0.88, 1]);
        const opacity = interpolate(progress, [0, 0.4], [0, 1], {
          extrapolateRight: "clamp",
        });

        const cardLeft = GRID-LEFT + col * (CARD-W + COL-GAP);
        const cardTop = GRID-TOP + row * (CARD-H + ROW-GAP);
        const currentMain = Math.round(countProgress * stat.main);

        return (
          <div
            key={i}
            style={{
              position: "absolute",
              left: cardLeft,
              top: cardTop,
              width: CARD-W,
              height: CARD-H,
              background: stat.bg,
              borderRadius: 16,
              border: `1px solid ${stat.color}33`,
              boxSizing: "border-box",
              padding: "22px 24px",
              transform: `scale(${scale})`,
              opacity,
              boxShadow: `0 4px 40px ${stat.color}22`,
              overflow: "hidden",
            }}
          >
            <div
              style={{
                position: "absolute",
                top: -40,
                right: -40,
                width: 140,
                height: 140,
                borderRadius: "50%",
                background: `radial-gradient(circle, ${stat.color}30 0%, transparent 70%)`,
                pointerEvents: "none",
              }}
            />

            <div
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "space-between",
                marginBottom: 14,
              }}
            >
              <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                <div
                  style={{
                    width: 44,
                    height: 44,
                    borderRadius: 10,
                    background: `${stat.color}22`,
                    border: `1px solid ${stat.color}55`,
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    fontSize: 20,
                    fontWeight: 900,
                    color: stat.color,
                    flexShrink: 0,
                  }}
                >
                  {stat.icon}
                </div>
                <span
                  style={{
                    fontSize: 18,
                    fontWeight: 700,
                    color: "rgba(255,255,255,0.85)",
                  }}
                >
                  {stat.platform}
                </span>
              </div>
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 4,
                  background: "#0d2e1a",
                  border: "1px solid #22c55e44",
                  borderRadius: 20,
                  padding: "4px 12px",
                }}
              >
                <span style={{ fontSize: 13, color: "#22c55e" }}>↑</span>
                <span style={{ fontSize: 12, color: "#22c55e", fontWeight: 600 }}>
                  成長中
                </span>
              </div>
            </div>

            <div style={{ marginBottom: 12 }}>
              <div
                style={{
                  fontSize: 44,
                  fontWeight: 900,
                  color: stat.color,
                  lineHeight: 1,
                  marginBottom: 4,
                  letterSpacing: -1,
                }}
              >
                {formatCount(currentMain)}
              </div>
              <div
                style={{
                  fontSize: 14,
                  color: "rgba(255,255,255,0.45)",
                  fontWeight: 500,
                  letterSpacing: 1,
                  textTransform: "uppercase",
                }}
              >
                {stat.mainLabel}
              </div>
            </div>

            <div
              style={{
                display: "flex",
                gap: 12,
                borderTop: `1px solid ${stat.color}22`,
                paddingTop: 12,
              }}
            >
              {stat.metrics.map((m, mi) => (
                <div
                  key={mi}
                  style={{
                    fontSize: 13,
                    color: "rgba(255,255,255,0.5)",
                    background: "rgba(255,255,255,0.04)",
                    borderRadius: 6,
                    padding: "4px 10px",
                    fontWeight: 500,
                  }}
                >
                  {m}
                </div>
              ))}
            </div>
          </div>
        );
      })}
    </AbsoluteFill>
  );
};

登入後查看完整程式碼