Motion · Ritmo

Beat Step Theme Cycle

A three-channel metronome — a dark-scene adjective list shifts up one line per beat, a fixed center pill "catches" the next word and recolors, and the whole background flips color on the same beat.

1920×108016:930 fps3.71s · 111 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#rhythm#typography

Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador

When to use

"Same product, many moods/multi-theme showcase" segments (modern/playful/expressive-style adjective bursts); the densest-rhythm segment in the film; syncing to a music beat.

Intention

Switching theme color once is a feature demo; switching every 0.6s, three times in a row, with "selected line + pill color + whole background" all jumping on the same frame — that's a metronome. It builds the densest rhythm in the film from the cheapest possible variable swap. Two things are critical: first, all three channels must lock to the same beat point — if any one channel lags half a beat, "jumping together" degrades into three independent animations; second, the boundary between jump and slide — the jump only occupies the first 6f of the beat, the remaining 12f is fully still; the beat must "jump," not "slide," or a uniformly scrolling list is just a list.

Duration & energy

  • Duration: Lead-in 30f + 18f per beat × beat count + closing hold; 3 beats ≈ 3.5s (demo 110f).
  • Energy: High (three channels jump together every 0.6s — density-driven high energy).

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

换一次主题色是功能演示;0.6 秒换一次、连换三次、每次"选中行+胶囊色 +全场底色"三件事同帧齐跳——就成了节拍器。用最便宜的变量切换做出 全片最高的节奏密度。命门有二:一是三通道必须锁同一拍点,任何 一通道慢半拍,"齐跳"退化成三个各自为政的动画;二是跳与滑的分界 ——跳变只占拍头 6f,其余 12f 完全静置,拍点要"跳"不要"滑", 匀速滚动的列表就只是列表。

动效核心

  • 胶囊固定在视口中央行不动,列表 translateY 逐拍步进——视觉上

读作"胶囊跳到下一行接住新词",实际动的是世界不是胶囊

  • 拍长 18f(0.6s @30fps ≈ 100BPM),跳变窗仅拍头 6f:

snap(t) = 1-(1-x)^3.2(陡 ease-out),其余帧静置

  • 三通道同源驱动:同一个 tInBeat 同时喂列表位移、胶囊/底色

rgb 逐通道 cross-mix、胶囊 squash——一个时钟,不许各挂各的曲线

  • 主题表逐拍换对(demo:灰白/深棕→绿→紫→红/深藏青),胶囊色与

底色是配好的深浅对,不是独立随机色

  • 胶囊落位 squash:scale 1.12→0.97→1 随拍头 6f 走完
  • 选中词反白、非选中词 34% 白;上下各 300px 底色渐变羽化收视口

——羽化色必须跟 bgColor 实时同步,写死颜色换场时穿帮

  • 开拍前 30f 静置铺垫——先让观众看清"这是个列表",再开始打拍

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 拍长 | 18f(BEAT_LEN,改它即改 BPM 对音轨) | <12f 观众来不及读词只见闪色;>26f 拍感散成逐个展示 | | 跳变窗 | 拍头 6f,ease-out 指数 3.2 | 窗 >10f 或缓曲线,"跳"变"滑",节拍器塌成滚动列表 | | 拍数 | 3–4 拍 | 5+ 拍观众适应了节奏开始走神;1–2 拍立不起"节拍器"人设 | | squash | 1.12→0.97→1 | 去掉它胶囊像贴图换色;>1.2 弹跳抢了换色的戏 | | 底色跨度 | 相邻主题底色须同明度不同色相(都深色) | 明度跳变(深→浅)会读作闪屏而非换肤 | | 铺垫/收尾 | 前 30f 静置;末拍后 hold ≥30f | 没铺垫第一拍读作 bug;末拍即切没有"停在最终色"的确认 |

已知坑

  • demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验

  • 与 theme-switch-moves 分工:那卡是单次换肤的仪式(扫场/涟漪,换一次

讲一次故事),本卡是连续多次换肤当节拍用——讲"深色模式功能"用那卡, 讲"多气质/多主题体量"用本卡

  • 与 cel-flash-stomp 同为高密度拍点段,同片二选一或隔远——都是

"每 0.6s 一记"的手法,紧邻观众会拍疲劳

  • 声音强依赖:拍点对齐鼓点(换 BEAT_LEN 对 BPM),无声版三通道

齐跳读作界面抽风

  • 词表长度 ≥ 拍数+2——列表底部要留未选中的词垫着,最后一拍就停在

列表末行会露出"下面没了"

  • 反白判定跟 beat 整数走、颜色跟 tInBeat 小数走(demo 即如此)——

两者都跟小数会出现半白半灰的中间态词

参考实现

demos/rhythm/beat-step-list-theme-cycle/ (BeatStepListThemeCycle.tsx) 原片出处:bear-app 22.3–24.6s

Reference implementation (Remotion)

  • demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx

The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.

Tudo nesta página também é legível por máquina. Aponte seu agente para o JSON ou adicione o servidor MCP do gimgs e peça para buscar templates de motion.

  1. Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
  2. Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
  3. Registre o componente como <Composition> em um projeto Remotion e renderize com `npx remotion render`, ou reimplemente a receita no seu próprio engine.
curl -s https://gimgs.net/motion/beat-step-list-theme-cycle.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("beat-step-list-theme-cycle")

Prompt

Use the video-shotcraft shot recipe "Beat Step Theme Cycle" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/beat-step-list-theme-cycle.json
Raw TSX: https://gimgs.net/motion/beat-step-list-theme-cycle/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 3.71s

1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls).
2. Adapt it for: <describe what you want>.
3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).

JSON · Markdown · llms.txt

3,881 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx =====
// beat-step-list-theme-cycle(bear 22.3–24.6s)
// 深色场形容词列表逐拍步进:每拍列表上移一行 + 选中胶囊跳到下一行并换色
// (绿→紫→红),整场底色同步换(深棕→深紫→深藏青)。
// 一拍之内"行、色、场"三通道同步跳变,~0.6s 一拍。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame } from 'remotion';

const FONT = 'Helvetica, Arial, sans-serif';
const ROW_H = 150;
const WORDS = ['modern', 'playful', 'expressive', 'seamless', 'intuitive'];

// 拍点:~0.6s 一拍 = 18 帧
const BEAT_LEN = 18;
const FIRST_BEAT = 30; // 前 1s 静置铺垫
const N_BEATS = 3;

// 每拍的(胶囊色 / 场底色)——起始态 + 三拍
const THEMES = [
  { pill: '#d8d8d4', bg: '#241a12', ink: '#2a2018' }, // modern:灰白胶囊 / 深棕场
  { pill: '#4fae62', bg: '#1e2416', ink: '#173015' }, // playful:绿
  { pill: '#8e6fd8', bg: '#221a33', ink: '#2a2144' }, // expressive:紫
  { pill: '#d64d55', bg: '#141c2e', ink: '#1a2440' }, // seamless:红 / 深藏青
];

// 拍内跳变:5 帧内完成,ease-out + 微过冲
const snap = (t: number) => interpolate(t, [0, 1], [0, 1], {
  easing: (x) => 1 - Math.pow(1 - x, 3.2),
  extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
});

export const BeatStepListThemeCycle: React.FC = () => {
  const frame = useCurrentFrame();

  // 当前拍序号与拍内进度(跳变只占拍头 6 帧)
  const raw = (frame - FIRST_BEAT) / BEAT_LEN;
  const beat = Math.min(N_BEATS, Math.max(0, Math.floor(raw) + 1)); // 已触发的拍数
  const beatStartFrame = FIRST_BEAT + (beat - 1) * BEAT_LEN;
  const tInBeat = beat === 0 ? 1 : snap((frame - beatStartFrame) / 6);

  // 连续步进量:整数拍 + 拍头 6 帧内的插值
  const step = beat === 0 ? 0 : (beat - 1) + tInBeat;

  // 三通道 —— 1) 列表上移一行
  const listY = -step * ROW_H;

  // 2) 胶囊换色(拍头硬跳,带一点 cross-fade)
  const themePrev = THEMES[Math.max(0, beat - 1)];
  const themeNow = THEMES[beat];
  const mixT = beat === 0 ? 1 : tInBeat;
  const mix = (a: string, b: string, t: number) => {
    const pa = [1, 3, 5].map((i) => parseInt(a.slice(i, i + 2), 16));
    const pb = [1, 3, 5].map((i) => parseInt(b.slice(i, i + 2), 16));
    return `rgb(${pa.map((v, i) => Math.round(v + (pb[i] - v) * t)).join(',')})`;
  };
  const pillColor = mix(themePrev.pill, themeNow.pill, mixT);
  const bgColor = mix(themePrev.bg, themeNow.bg, mixT);

  // 胶囊每拍落位时 squash 弹一下
  const pop = beat === 0 ? 1 : interpolate(tInBeat, [0, 0.6, 1], [1.12, 0.97, 1]);

  // 选中行文字反白判定:胶囊固定在视口中央行,选中词 = WORDS[beat]
  const selectedIdx = beat;

  return (
    <AbsoluteFill style={{ background: bgColor, fontFamily: FONT, justifyContent: 'center' }}>
      {/* 中央固定胶囊(列表在其下滚动,视觉上"胶囊跳到下一行") */}
      <div style={{
        position: 'absolute', left: '50%', top: 540 - ROW_H / 2 + 10,
        width: 900, height: ROW_H - 20, transform: `translateX(-50%) scale(${pop})`,
        background: pillColor, borderRadius: 999,
        boxShadow: '0 14px 40px rgba(0,0,0,0.35)',
      }} />
      {/* 词列表 */}
      <div style={{
        position: 'absolute', left: 0, right: 0, top: 540 - ROW_H / 2,
        transform: `translateY(${listY}px)`,
      }}>
        {WORDS.map((w, i) => {
          const isSel = i === selectedIdx;
          return (
            <div key={w} style={{
              height: ROW_H, display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>
              <span style={{
                fontSize: 92, fontWeight: 800, letterSpacing: -1.5,
                color: isSel ? (beat === 0 ? '#2a2018' : '#ffffff') : 'rgba(255,255,255,0.34)',
                position: 'relative', zIndex: 2,
              }}>{w}</span>
            </div>
          );
        })}
      </div>
      {/* 视口上下羽化 */}
      <div style={{ position: 'absolute', left: 0, right: 0, top: 0, height: 300, background: `linear-gradient(${bgColor}, transparent)`, zIndex: 3 }} />
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 300, background: `linear-gradient(transparent, ${bgColor})`, zIndex: 3 }} />
    </AbsoluteFill>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Mais nesta categoria