// ===== demos/effects/light-play-moves/HalationBloom.tsx =====
// halation-bloom —— 高光晕染
// 深灰底大白字 "10x" crash-zoom 急停入场(2.4→1,7f in-quad + 2f 回弹)。
// 撞停帧起:底层复制文字(blur+提亮的白色晕层)猛涨一圈(scale 1→1.3,6f out-cubic 扩散),
// 再 20f 线性回落到 0.35 驻留柔晕(扩散/消散解耦判例),随后 15f 缓收到 0.22 稳态。
// 深底白字:白底上提亮不可见判例。收尾真静止 ≥40f。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
const BG = '#2a2a28';
const WHITE = '#f7f7f5';
const MID = '#8f8f8d';
// —— 时间轴(30fps)——
const ZOOM_START = 8; // 入场起始
const IMPACT = 15; // 撞停帧(7f 急速缩入)
const REBOUND_END = 17; // 2f 回弹
const POP_END = IMPACT + 6; // 晕层猛涨 6f → f21
const FALL_END = POP_END + 20; // 20f 线性回落 → f41
const SETTLE_END = FALL_END + 15; // 15f 缓收 → f56,此后全静止
// 总时长 145f → 静止 89f ≥ 40f
const TextBlock: React.FC<{ color: string }> = ({ color }) => (
10x
);
export const HalationBloom: React.FC = () => {
const frame = useCurrentFrame();
// —— crash-zoom 入场:scale 2.4 → 0.94(7f in-quad 加速撞停)→ 1(2f 回弹)——
const zoomIn = interpolate(frame, [ZOOM_START, IMPACT], [2.4, 0.94], {
easing: Easing.in(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const rebound = interpolate(frame, [IMPACT, REBOUND_END], [0.94, 1], {
easing: Easing.out(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const textScale = frame < IMPACT ? zoomIn : rebound;
const textOpacity = interpolate(frame, [ZOOM_START, ZOOM_START + 3], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// —— 晕层:撞停帧起条件挂载 ——
// 扩散(scale):out-cubic,6f 猛涨 1 → 1.3
const bloomScale = interpolate(frame, [IMPACT, POP_END], [1, 1.3], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 消散(opacity):与扩散解耦——猛涨段保持全亮,随后 20f 线性回落到 0.35
const bloomFall = interpolate(frame, [POP_END, FALL_END], [1, 0.35], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 驻留晕 15f 缓收到 0.22 稳态
const bloomSettle = interpolate(frame, [FALL_END, SETTLE_END], [0.35, 0.22], {
easing: Easing.out(Easing.quad),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const bloomOpacity = frame < FALL_END ? bloomFall : bloomSettle;
return (
{/* 小标签,全程静态 */}
HALATION BLOOM
{/* 晕层:文字复制底层,blur + 提亮,撞停帧起条件挂载 */}
{frame >= IMPACT && (
)}
{/* 本体文字:crash-zoom 急停 */}
);
};
// ===== demos/effects/light-play-moves/SheenSweepRetry.tsx =====
// sheen-sweep-retry —— 单点扫光(高标准重试)
// 深墨大卡居中,一道 45° 高光带在 40–68f 从左外扫到右外,仅此一次。
// 约束:单点(只扫主角卡)、圆角裁剪(overflow hidden)、扫前扫后完全静止。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G } from '../../_fixtures/Fixtures';
const CARD_W = 760;
const CARD_H = 420;
const SHEEN_W = CARD_W * 1.6; // 1216
export const SheenSweepRetry: React.FC = () => {
const frame = useCurrentFrame();
// 扫光:40–68f,从卡左外(-SHEEN_W)扫到卡右外(CARD_W),inOut(cubic),只一次
const sweepActive = frame >= 40 && frame <= 68;
const x = interpolate(frame, [40, 68], [-SHEEN_W, CARD_W], {
easing: Easing.inOut(Easing.cubic),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
PRO
{/* 高光带:条件挂载,扫完即摘罩,收尾真静止 */}
{sweepActive && (
)}
);
};
// ===== demos/effects/light-play-moves/SpotlightSweepReveal.tsx =====
import React from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';
// spotlight-sweep-reveal: 聚光灯摆动扫字
// 暗场 #2a2a28,标题两行常驻 opacity 0.07;亮版同文本用 radial-gradient mask,
// 光斑 x 按 sin 摆动扫两个来回(周期 ~55f),f110 后全字提亮定格,
// 光斑/光锥线性消散并在 f>=125 条件卸载,125–160 真静止 35f。
const BG = '#2a2a28';
const INKW = '#f5f5f3';
const CX = 960;
const CY = 540;
const AMP = 560; // 摆动幅度
const PERIOD = 55; // 单个来回帧数
const SWEEP_END = 110; // 两个来回结束
const REVEAL_END = 125; // 提亮完成,之后真静止
const textStyle: React.CSSProperties = {
fontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
fontSize: 150,
fontWeight: 800,
lineHeight: 1.15,
letterSpacing: '0.04em',
color: INKW,
textAlign: 'center',
whiteSpace: 'pre',
};
const TitleText: React.FC = () => (
{'SPOTLIGHT\nON'}
);
export const SpotlightSweepReveal: React.FC = () => {
const f = useCurrentFrame();
// 光斑 x:sin 摆动,两个来回后停在中心
const sweepF = Math.min(f, SWEEP_END);
const x = CX + AMP * Math.sin((2 * Math.PI * sweepF) / PERIOD);
// 全字提亮(扩散感 out-cubic)
const brighten = interpolate(f, [SWEEP_END, REVEAL_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.out(Easing.cubic),
});
// 光斑/光锥消散(线性)
const fadeOut = interpolate(f, [SWEEP_END, REVEAL_END], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const sweepAlive = f < REVEAL_END; // 条件卸载,保证末段真静止
const maskGrad = `radial-gradient(circle 380px at ${x}px ${CY}px, rgba(0,0,0,1) 0%, rgba(0,0,0,0.95) 45%, rgba(0,0,0,0) 100%)`;
return (
{/* 暗版文字常驻 */}
{sweepAlive && (
<>
{/* 光锥:从顶部收拢到光斑 */}
{/* 落在暗墙上的柔光斑 */}
{/* 亮版文字,按帧移动的 radial mask */}
>
)}
{/* 全亮定格层 */}
);
};