AI 看圖不理解語意的視覺解說
以字元逐一顯示的標題動畫開場,再以機器人與圖像圖示搭配流動光點連線,呈現「AI 看到的是像素不是語意」的概念,並以散落的問號強化困惑感。
教學概念解說SVG問號逐字動畫
提示詞
我有一個用 Remotion 寫的場景元件(檔案:NotUnderstandScene.tsx),請幫我做以下調整: 1. 修改頂部說明文字(AnimatedTitle 中的 text 字串) 2. 調整機器人與圖像圖示的出現時間(ROBOT_DELAY、IMAGE_DELAY,目前為第 60、90 幀) 3. 修改底部副標題文字(目前為「它看到的是像素,不是語意」) 4. 調整散落問號的數量與位置(scatterPositions 陣列) 請保留逐字動畫與流光連線效果,只修改我指定的部分,然後把完整的修改後程式碼給我。
import {
AbsoluteFill,
Audio,
Sequence,
interpolate,
spring,
staticFile,
useCurrentFrame,
useVideoConfig,
} from "remotion";
const colors = {
background: "#0B0F17",
text: "#FFFFFF",
accent: "#4DA3FF",
dimmed: "rgba(255, 255, 255, 0.6)",
cardBg: "rgba(255, 255, 255, 0.05)",
border: "rgba(77, 163, 255, 0.3)",
};
const SFX = {
woosh: staticFile("audio/connection/woosh.wav"),
softImpact: staticFile("audio/connection/soft-impact.wav"),
softClick: staticFile("audio/connection/soft-click.wav"),
tinyPop: staticFile("audio/connection/tiny-pop.mp3"),
microRiser: staticFile("audio/connection/micro-riser.mp3"),
ding: staticFile("audio/connection/ding.mp3"),
};
export const NOT_UNDERSTAND_DURATION_FRAMES = 240;
const EX = {
extrapolateRight: "clamp" as const,
extrapolateLeft: "clamp" as const,
};
const RobotIcon: React.FC<{ size: number; glitch: number }> = ({ size, glitch }) => (
<svg width={size} height={size} viewBox="0 0 120 120" fill="none">
<line x1="60" y1="8" x2="60" y2="28" stroke={colors.accent} strokeWidth="3" strokeLinecap="round" />
<circle cx="60" cy="6" r="4" fill={colors.accent} />
<rect x="28" y="28" width="64" height="48" rx="12" stroke={colors.accent} strokeWidth="3" transform={`translate(${glitch}, 0)`} />
<circle cx="44" cy="52" r="7" fill={colors.accent} opacity="0.8" transform={`translate(${glitch * 1.5}, 0)`} />
<circle cx="76" cy="52" r="7" fill={colors.accent} opacity="0.8" transform={`translate(${glitch * 1.5}, 0)`} />
<path d="M44 66 Q52 72 60 66 Q68 60 76 66" stroke={colors.accent} strokeWidth="2.5" strokeLinecap="round" fill="none" opacity="0.6" />
<rect x="34" y="80" width="52" height="30" rx="8" stroke={colors.accent} strokeWidth="3" />
<line x1="48" y1="88" x2="72" y2="88" stroke={colors.accent} strokeWidth="2" opacity="0.4" />
<line x1="48" y1="96" x2="66" y2="96" stroke={colors.accent} strokeWidth="2" opacity="0.3" />
</svg>
);
const ImageIcon: React.FC<{ size: number }> = ({ size }) => (
<svg width={size} height={size} viewBox="0 0 120 120" fill="none">
<rect x="12" y="20" width="96" height="80" rx="10" stroke="#A78BFA" strokeWidth="3" />
<path d="M22 84 L42 52 L56 68 L68 54 L98 84 Z" fill="#A78BFA" opacity="0.2" />
<path d="M22 84 L42 52 L56 68 L68 54 L98 84" stroke="#A78BFA" strokeWidth="2" fill="none" opacity="0.5" />
<circle cx="82" cy="40" r="10" stroke="#A78BFA" strokeWidth="2.5" />
<circle cx="82" cy="40" r="4" fill="#A78BFA" opacity="0.4" />
<line x1="32" y1="84" x2="32" y2="74" stroke="#A78BFA" strokeWidth="2" opacity="0.4" />
<circle cx="32" cy="72" r="5" fill="#A78BFA" opacity="0.2" />
</svg>
);
const QuestionIcon: React.FC<{ size: number; pulse: number }> = ({ size, pulse }) => (
<svg width={size} height={size} viewBox="0 0 120 120" fill="none">
<circle cx="60" cy="60" r="48" stroke="#F59E0B" strokeWidth="3" opacity={0.3 + pulse * 0.2} />
<text x="60" y="78" textAnchor="middle" fontSize="72" fontWeight="800" fontFamily="'Inter', sans-serif" fill="#F59E0B" opacity={0.9}>?</text>
</svg>
);
const FloatingQ: React.FC<{ x: number; y: number; size: number; opacity: number; rotation: number }> = ({ x, y, size, opacity, rotation }) => (
<text x={x} y={y} textAnchor="middle" fontSize={size} fontWeight="700" fontFamily="'Inter', sans-serif" fill="#F59E0B" opacity={opacity} transform={`rotate(${rotation}, ${x}, ${y})`}>?</text>
);
const AnimatedTitle: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const text = "因為 Claude Code 並沒有真的理解這張圖上面的所有元素";
const prefix = "因為 ";
const claudeCode = "Claude Code";
const containerOp = interpolate(frame, [0, 15], [0, 1], EX);
const containerY = interpolate(frame, [0, 20], [20, 0], EX);
const revealCount = interpolate(frame, [5, 50], [0, text.length], EX);
const highlightOp = interpolate(frame, [55, 70], [0, 1], EX);
const underlineProgress = interpolate(frame, [60, 90], [0, 1], EX);
const chars = text.split("");
return (
<div style={{ position: "absolute", top: 160, width: "100%", display: "flex", flexDirection: "column", alignItems: "center", opacity: containerOp, transform: `translateY(${containerY}px)` }}>
<div style={{ fontSize: 52, fontWeight: 700, lineHeight: 1.6, textAlign: "center", maxWidth: 1400 }}>
{chars.map((char, i) => {
const isClaudeCode = i >= prefix.length && i < prefix.length + claudeCode.length;
const visible = i < revealCount;
return (
<span key={i} style={{ opacity: visible ? 1 : 0, color: isClaudeCode ? colors.accent : colors.text, textShadow: isClaudeCode && highlightOp > 0 ? `0 0 ${20 * highlightOp}px ${colors.accent}60` : "none", fontWeight: isClaudeCode ? 800 : 700, transition: "none" }}>
{char}
</span>
);
})}
</div>
<svg width={800} height={6} viewBox="0 0 800 6" style={{ marginTop: 16 }}>
<defs>
<linearGradient id="titleUnderline" x1="0" y1="0" x2="800" y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor={colors.accent} stopOpacity="0" />
<stop offset="30%" stopColor={colors.accent} />
<stop offset="70%" stopColor="#A78BFA" />
<stop offset="100%" stopColor="#A78BFA" stopOpacity="0" />
</linearGradient>
</defs>
<line x1={400 - 400 * underlineProgress} y1={3} x2={400 + 400 * underlineProgress} y2={3} stroke="url(#titleUnderline)" strokeWidth={2} strokeLinecap="round" />
</svg>
</div>
);
};
export const NotUnderstandScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const ROBOT_DELAY = 60;
const IMAGE_DELAY = 90;
const LINE_DELAY = 110;
const QUESTION_DELAY = 145;
const SCATTER_Q_DELAY = 170;
const robotLocal = frame - ROBOT_DELAY;
const robotOp = interpolate(robotLocal, [0, 20], [0, 1], EX);
const robotScale = spring({ frame: Math.max(0, robotLocal), fps, config: { damping: 12, stiffness: 80 } });
const robotY = interpolate(robotLocal, [0, 25], [40, 0], EX);
const glitchPhase = robotLocal - 40;
const glitch = glitchPhase > 0 && glitchPhase < 15 ? Math.sin(glitchPhase * 4) * interpolate(glitchPhase, [0, 15], [4, 0], EX) : 0;
const robotFloat = robotLocal > 30 ? Math.sin(robotLocal * 0.06) * 4 : 0;
const imageLocal = frame - IMAGE_DELAY;
const imageOp = interpolate(imageLocal, [0, 20], [0, 1], EX);
const imageScale = spring({ frame: Math.max(0, imageLocal), fps, config: { damping: 10, stiffness: 70 } });
const imageY = interpolate(imageLocal, [0, 25], [40, 0], EX);
const imageFloat = imageLocal > 30 ? Math.sin((imageLocal + 20) * 0.055) * 5 : 0;
const lineLocal = frame - LINE_DELAY;
const lineProgress = interpolate(lineLocal, [0, 25], [0, 1], EX);
const qLocal = frame - QUESTION_DELAY;
const qOp = interpolate(qLocal, [0, 15], [0, 1], EX);
const qScale = spring({ frame: Math.max(0, qLocal), fps, config: { damping: 8, stiffness: 60 } });
const qPulse = qLocal > 25 ? Math.sin(qLocal * 0.1) : 0;
const scatterLocal = frame - SCATTER_Q_DELAY;
const scatterPositions = [
{ x: 320, y: 480, size: 28, delay: 0, speed: 0.07, amp: 15, rot: -15 },
{ x: 1600, y: 520, size: 32, delay: 8, speed: 0.06, amp: 18, rot: 12 },
{ x: 480, y: 750, size: 22, delay: 15, speed: 0.08, amp: 12, rot: -8 },
{ x: 1440, y: 380, size: 26, delay: 5, speed: 0.065, amp: 16, rot: 20 },
{ x: 260, y: 350, size: 20, delay: 12, speed: 0.075, amp: 10, rot: -22 },
{ x: 1660, y: 720, size: 24, delay: 18, speed: 0.055, amp: 14, rot: 8 },
];
const particles = Array.from({ length: 10 }, (_, i) => {
const baseX = (i * 197) % 1920;
const baseY = (i * 263) % 1080;
const speed = 0.25 + (i % 3) * 0.12;
const y = baseY + Math.sin((frame + i * 35) * speed * 0.03) * 25;
const x = baseX + Math.cos((frame + i * 45) * speed * 0.02) * 18;
const size = 1.5 + (i % 3);
const opacity = interpolate(Math.sin((frame + i * 40) * 0.035), [-1, 1], [0.03, 0.12]);
return { x, y, size, opacity };
});
const robotGlow = robotLocal > 30 ? 0.12 + Math.sin(robotLocal * 0.08) * 0.06 : 0;
const imageGlow = imageLocal > 30 ? 0.12 + Math.sin((imageLocal + 15) * 0.08) * 0.06 : 0;
const qGlow = qLocal > 30 ? 0.15 + Math.sin((qLocal + 30) * 0.08) * 0.08 : 0;
const CENTER_Y = 480;
const ICON_GAP = 500;
const LEFT_X = 960 - ICON_GAP / 2;
const RIGHT_X = 960 + ICON_GAP / 2;
const MID_X = 960;
return (
<AbsoluteFill style={{ backgroundColor: colors.background, fontFamily: "'Noto Sans TC', 'Inter', sans-serif", overflow: "hidden" }}>
<svg width={1920} height={1080} style={{ position: "absolute", top: 0, left: 0 }}>
{particles.map((p, i) => <circle key={i} cx={p.x} cy={p.y} r={p.size} fill={colors.accent} opacity={p.opacity} />)}
</svg>
<AnimatedTitle frame={frame} fps={fps} />
<div style={{ position: "absolute", left: LEFT_X - 96, top: CENTER_Y - 96, opacity: robotOp, transform: `scale(${robotScale}) translateY(${robotY + robotFloat}px)` }}>
<div style={{ position: "absolute", inset: -50, borderRadius: "50%", background: `radial-gradient(circle, ${colors.accent} 0%, transparent 70%)`, opacity: robotGlow }} />
<RobotIcon size={192} glitch={glitch} />
<div style={{ textAlign: "center", marginTop: 12, fontSize: 24, fontWeight: 600, color: colors.accent, opacity: interpolate(robotLocal, [20, 35], [0, 0.8], EX) }}>AI</div>
</div>
<div style={{ position: "absolute", left: RIGHT_X - 96, top: CENTER_Y - 96, opacity: imageOp, transform: `scale(${imageScale}) translateY(${imageY + imageFloat}px)` }}>
<div style={{ position: "absolute", inset: -50, borderRadius: "50%", background: `radial-gradient(circle, #A78BFA 0%, transparent 70%)`, opacity: imageGlow }} />
<ImageIcon size={192} />
<div style={{ textAlign: "center", marginTop: 12, fontSize: 24, fontWeight: 600, color: "#A78BFA", opacity: interpolate(imageLocal, [20, 35], [0, 0.8], EX) }}>圖像</div>
</div>
{lineProgress > 0 && (() => {
const lineX1 = LEFT_X + 110;
const lineX2End = RIGHT_X - 110;
const lineX2 = lineX1 + (lineX2End - lineX1) * lineProgress;
const lineLen = lineX2 - lineX1;
const dots = [0, 0.33, 0.66].map((offset) => {
const t = ((lineLocal * 0.025 + offset) % 1);
return { x: lineX1 + t * lineLen, opacity: lineProgress > 0.3 ? 0.9 * Math.sin(t * Math.PI) : 0 };
});
return (
<svg width={1920} height={1080} style={{ position: "absolute", top: 0, left: 0, pointerEvents: "none" }}>
<defs>
<linearGradient id="arrowGrad" x1={lineX1} y1="0" x2={lineX2End} y2="0" gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor={colors.accent} />
<stop offset="100%" stopColor="#A78BFA" />
</linearGradient>
<radialGradient id="dotGlow">
<stop offset="0%" stopColor="#FFFFFF" />
<stop offset="40%" stopColor={colors.accent} />
<stop offset="100%" stopColor={colors.accent} stopOpacity="0" />
</radialGradient>
</defs>
<line x1={lineX1} y1={CENTER_Y} x2={lineX2} y2={CENTER_Y} stroke="url(#arrowGrad)" strokeWidth={2.5} opacity={0.8} />
{lineProgress > 0.9 && <path d={`M${lineX2 - 2},${CENTER_Y} L${lineX2 + 14},${CENTER_Y} M${lineX2 + 8},${CENTER_Y - 7} L${lineX2 + 14},${CENTER_Y} L${lineX2 + 8},${CENTER_Y + 7}`} stroke="#A78BFA" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none" />}
{dots.map((dot, i) => (
<g key={i}>
<circle cx={dot.x} cy={CENTER_Y} r={12} fill="url(#dotGlow)" opacity={dot.opacity * 0.5} />
<circle cx={dot.x} cy={CENTER_Y} r={3.5} fill="#FFFFFF" opacity={dot.opacity} />
</g>
))}
</svg>
);
})()}
{qOp > 0 && (
<div style={{ position: "absolute", left: MID_X - 60, top: CENTER_Y - 120, width: 120, display: "flex", flexDirection: "column", alignItems: "center", opacity: qOp, transform: `scale(${qScale})` }}>
<div style={{ position: "absolute", inset: -30, borderRadius: "50%", background: `radial-gradient(circle, #F59E0B 0%, transparent 70%)`, opacity: qGlow }} />
<QuestionIcon size={120} pulse={qPulse} />
</div>
)}
{scatterLocal > 0 && (
<svg width={1920} height={1080} style={{ position: "absolute", top: 0, left: 0, pointerEvents: "none" }}>
{scatterPositions.map((q, i) => {
const local = scatterLocal - q.delay;
if (local < 0) return null;
const op = interpolate(local, [0, 15], [0, 0.25], EX);
const floatY = local > 15 ? Math.sin(local * q.speed) * q.amp : 0;
return <FloatingQ key={i} x={q.x} y={q.y + floatY} size={q.size} opacity={op} rotation={q.rot + Math.sin(local * 0.04) * 5} />;
})}
</svg>
)}
<div style={{ position: "absolute", bottom: 80, width: "100%", textAlign: "center", fontSize: 28, fontWeight: 400, color: colors.dimmed, opacity: interpolate(frame, [180, 200], [0, 0.6], EX) }}>
它看到的是像素,不是語意
</div>
<Sequence from={5}><Audio src={SFX.softClick} volume={0.2} /></Sequence>
<Sequence from={ROBOT_DELAY}><Audio src={SFX.woosh} volume={0.25} /></Sequence>
<Sequence from={ROBOT_DELAY + 12}><Audio src={SFX.softImpact} volume={0.18} /></Sequence>
<Sequence from={IMAGE_DELAY}><Audio src={SFX.woosh} volume={0.2} /></Sequence>
<Sequence from={IMAGE_DELAY + 12}><Audio src={SFX.softImpact} volume={0.15} /></Sequence>
<Sequence from={LINE_DELAY}><Audio src={SFX.microRiser} volume={0.2} /></Sequence>
<Sequence from={QUESTION_DELAY}><Audio src={SFX.ding} volume={0.3} /></Sequence>
<Sequence from={SCATTER_Q_DELAY}><Audio src={SFX.tinyPop} volume={0.15} /></Sequence>
<Sequence from={SCATTER_Q_DELAY + 15}><Audio src={SFX.tinyPop} volume={0.12} /></Sequence>
</AbsoluteFill>
);
};登入後查看完整程式碼