Motion · Ritmo

Sakuga timing shift

On-3s cuts to on-1s — an element moves in choppy 3-frame steps, then snaps to buttery frame-by-frame speed at the climax.

1920×108016:930 fps5.06s · 152 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#rhythm#ui-entrance

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

Emphatic movement for a single element (a card entrance, a metric sprinting to the finish); sections that need a "handmade feel → climax burst" contrast

Intention

The library already has speed-ramp for continuous speed changes and hit-counter for a localized hitch. This card is different — it changes the frame rate itself, not the speed: during the choppy section, the driving frame is q=floor(f/3)*3, so the element only moves once every 3 frames (a 10fps flip-book feel); at the switch point it starts driving off the raw f frame by frame for a buttery sprint. The chunky threes and the smooth ones cut together in the same shot — this is the "shooting on 3s / on 1s" performance technique from Japanese animation. The choppy section isn't cheap, it's handmade-feeling, and the switch point itself is the climax marker.

Duration & energy

  • Duration: ~5s
  • Energy: Medium-high

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

意图

库内时间轴操纵已有 speed-ramp 连续变速与 hit-counter 的局部顿帧。 本卡是另一种——改的不是速度是帧率本身: 顿挫段驱动帧 q=floor(f/3)*3,元素每 3 帧才动一步(10fps 手翻书), 切换帧起改用原始 f 逐帧驱动,丝滑冲刺。一拍三的钝与一拍一的滑 同屏对切,日式作画的拍数演出(shooting on 3s / on 1s)。顿挫段 不是低级感是手工感,切换点即高潮标记。

动效核心

  • 顿挫段:q = Math.floor(f/3)*3 当驱动帧,位置线性 X 120→1380,

每步 ≈79px;姿势 sin(q) rotate ±5°,一步一个样

  • f48 切换:改原始 f 连续驱动,Easing.out(poly(4)) 高初速冲刺

折返中央 + 过冲 36px + 3f scaleX 0.9 回弹急停

  • 冲刺段运动拉伸由位置差分速度驱动(scaleX 峰值 ≈1.35 /

scaleY 0.88),残影 2 层条件挂载——速度低即摘除,非 opacity 0

  • 角标 "on 3s"/"on 1s" 随段切换,切换瞬间 pop 1.35×;角标

line-boil 用 h() 伪随机、f=108 后冻结

  • f108–150 真静止 42f,全画面无任何动画

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 拍数 | 顿挫段 floor(f/3)(一拍三) | /2 顿感不足、/4 以上读作播放器卡死 | | 每步位移 | ≈79px(总程 ÷ 步数倒推) | <30px 读作卡顿掉帧不是顿挫——命门 | | 步间姿势 | sin(q) rotate ±5° | 无姿势变化的纯位移顿挫像 bug 不像手翻书 | | 切换帧 | f48(顿挫:冲刺 ≈ 3:2) | 切换必须一帧完成,任何补间都毁掉"换拍"感 | | 冲刺缓动 | Easing.out(poly(4)) + 过冲 36px + 3f 回弹 | 初速要猛,与顿挫段拉满反差 | | 收尾 | 真静止 ≥42f,角标沸腾同步冻结 | 沸腾残留毁真静止(feTurbulence 摘罩判例同族) |

已知坑

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

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

  • 同一镜头单一时间操纵者判例:与 speed-ramp / hit-counter 顿帧

互斥,一个镜头只能有一个人动时间轴

  • 顿挫段每步位移 ≥30px 才读得出"顿"——微步顿挫读作渲染掉帧,

这是本卡命门;步幅不够就缩短总时长换大步幅

  • 切换点必须有标注(角标 "on 3s"→"on 1s" 或一声打点,

sound-design §4.5)——无标注观众只觉得"变快了",读不出"换拍"

参考实现

demos/rhythm/sakuga-timing-shift/ (SakugaTimingShift.tsx)

Reference implementation (Remotion)

  • demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.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/sakuga-timing-shift.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("sakuga-timing-shift")

Prompt

Use the video-shotcraft shot recipe "Sakuga timing shift" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/sakuga-timing-shift.json
Raw TSX: https://gimgs.net/motion/sakuga-timing-shift/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.06s

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

5,354 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx =====
// sakuga-timing-shift —— 一拍三转一拍一(作画打拍切换)
// 0–48f:驱动帧 q = floor(f/3)*3,卡从左侧顿挫横移到右侧(10fps 手翻书感),
// 每步 ≈74px + rotate 摆动 = 一拍三的钝感;48f 切换点后改用原始 f 连续驱动,
// 48–75f 丝滑冲刺折返中央(out-poly(4) 高初速 + 运动拉伸 scaleX + 残影),
// 过冲 36px 后 3f 回弹落位。左上角标 "on 3s"/"on 1s" 随段切换并带 line-boil
// (boil 在 f=108 后冻结)。收尾真静止 ≥40f。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card, TitleBlock } from '../../_fixtures/Fixtures';

const W = 1920;
const CARD_W = 420;
const CARD_H = 260;
const CARD_Y = (1080 - CARD_H) / 2; // 410

const X_LEFT = 120;
const X_RIGHT = 1380; // 卡左缘,右侧停点
const X_CENTER = (W - CARD_W) / 2; // 750,中央落位
const OVERSHOOT = 36;

const SWITCH = 48; // 打拍切换帧
const ARRIVE = 70; // 冲刺到过冲点
const SETTLE = 75; // 回弹落位完成

// 段一:一拍三。位置函数线性,但只在 q = floor(f/3)*3 上取值。
const pos1 = (t: number): number =>
  interpolate(t, [0, SWITCH], [X_LEFT, X_RIGHT], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

// 段二:一拍一。out-poly(4) 高初速冲刺 → 过冲 → 3f 回弹。
const pos2 = (t: number): number =>
  interpolate(t, [SWITCH, ARRIVE, SETTLE], [X_RIGHT, X_CENTER - OVERSHOOT, X_CENTER], {
    easing: Easing.out(Easing.poly(4)),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

export const SakugaTimingShift: React.FC = () => {
  const f = useCurrentFrame();
  const onThrees = f < SWITCH;

  // ---- 卡片位置 ----
  const q = Math.floor(f / 3) * 3; // 一拍三驱动帧
  const x = onThrees ? pos1(q) : pos2(f);

  // 顿挫段 rotate 摆动(随 q 冻结,一步一个姿势)
  const rot = onThrees
    ? Math.sin(q * 0.7) * 5
    : interpolate(f, [SWITCH, SWITCH + 8], [Math.sin(SWITCH * 0.7) * 5, 0], {
        extrapolateLeft: 'clamp',
        extrapolateRight: 'clamp',
      });

  // 冲刺段速度(位置差分,帧时间解耦)→ 运动拉伸
  const v = onThrees ? 0 : Math.abs(pos2(f) - pos2(f - 1));
  const sFac = Math.min(v / 55, 1);
  const stretchX = 1 + 0.35 * sFac; // 峰值 ≈1.35,横向拉丝
  const stretchY = 1 - 0.12 * sFac;

  // 落位急停回弹:72–78f scaleX 压扁再回 1
  const sqX = interpolate(f, [SETTLE - 3, SETTLE, SETTLE + 3], [1, 0.9, 1], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const sqY = interpolate(f, [SETTLE - 3, SETTLE, SETTLE + 3], [1, 1.07, 1], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  // 冲刺残影:只在段二速度高时挂载(条件挂载,不留 opacity 0 的壳)
  const ghosts =
    !onThrees && f > SWITCH + 1 && f < ARRIVE + 2 && sFac > 0.15
      ? [
          { xg: pos2(f - 2), op: 0.28 * sFac },
          { xg: pos2(f - 4), op: 0.13 * sFac },
        ]
      : [];

  // ---- 角标 "on 3s" / "on 1s",line-boil,f=108 后冻结 ----
  const h = (n: number) => {
    const s = Math.sin(n * 127.3) * 43758.5453;
    return s - Math.floor(s);
  };
  const qb = Math.min(Math.floor(f / 4) * 4, 108); // boil 驱动帧,108 后冻结
  const bx = (h(qb + 1) - 0.5) * 7;
  const by = (h(qb + 2) - 0.5) * 7;
  const brot = (h(qb + 3) - 0.5) * 3;
  // 切换瞬间角标弹一下
  const pop = interpolate(f, [SWITCH, SWITCH + 3, SWITCH + 9], [1, 1.35, 1], {
    easing: Easing.out(Easing.cubic),
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  const titleOp = interpolate(f, [0, 10], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>
      <div style={{ position: 'absolute', top: 90, width: '100%', textAlign: 'center', opacity: titleOp }}>
        <TitleBlock text="SAKUGA TIMING SHIFT" size={64} />
      </div>

      {/* 轨道基线 + 中央落位虚线槽 */}
      <div
        style={{
          position: 'absolute',
          left: 100,
          right: 100,
          top: CARD_Y + CARD_H + 24,
          height: 3,
          background: G.line,
          borderRadius: 2,
        }}
      />
      <div
        style={{
          position: 'absolute',
          left: X_CENTER,
          top: CARD_Y,
          width: CARD_W,
          height: CARD_H,
          border: `3px dashed ${G.bar}`,
          borderRadius: 14,
          boxSizing: 'border-box',
        }}
      />

      {/* 冲刺残影 */}
      {ghosts.map((g, i) => (
        <div
          key={`ghost-${i}`}
          style={{ position: 'absolute', left: 0, top: CARD_Y, opacity: g.op, transform: `translateX(${g.xg}px)` }}
        >
          <Card w={CARD_W} h={CARD_H} seed={4} />
        </div>
      ))}

      {/* 主卡 */}
      <div
        style={{
          position: 'absolute',
          left: 0,
          top: CARD_Y,
          transform: `translateX(${x}px) rotate(${rot}deg) scaleX(${stretchX * sqX}) scaleY(${stretchY * sqY})`,
          transformOrigin: '50% 50%',
        }}
      >
        <Card w={CARD_W} h={CARD_H} seed={4} />
      </div>

      {/* 角标:on 3s / on 1s */}
      <div
        style={{
          position: 'absolute',
          left: 120,
          top: 160,
          transform: `translate(${bx}px, ${by}px) rotate(${brot}deg) scale(${pop})`,
          transformOrigin: '0% 50%',
          fontFamily: 'Courier New, monospace',
          fontWeight: 700,
          fontSize: 84,
          color: G.ink,
          borderBottom: `6px solid ${G.ink}`,
          paddingBottom: 6,
        }}
      >
        {onThrees ? 'on 3s' : 'on 1s'}
      </div>
    </div>
  );
};


  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/sakuga-timing-shift/SakugaTimingShift.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