Motion · Light & emphasis

FUI/HUD moves

Two FUI/HUD styles: line-unfold-panel, a single line unfolding into a panel (line-to-plane CRT grammar), and reticle-lock-on, a viewfinder flying in to lock onto a target.

1920×108016:930 fps4.72s · 142 framesremotionLicense: Apache-2.0#shotcraft#remotion#effects#ui-entrance

Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player

Variants

line-unfold-panelline-unfold-panel · Line-to-plane panel unfold
scaleX 0→1 (out poly4, a sharp 5f snap) followed by scaleY 3px→full height (out cubic, 9f), content fades in one beat early; exit mirrors the sequence in reverse.
Panel entrance/exit in dark segments; power-on/off ritual feel
reticle-lock-onreticle-lock-on · Reticle lock-on
Four L-corners are four mirrored copies of the same rectangle pair; the fly-in (10f out cubic) and the shrink (2.2×→0.94×→1 overshoot rebound) are decoupled; on the lock-on frame the target brightens slightly and a label pops in with back easing.
Target call-out; timed to hit a sound effect

When to use

Use A for panel entrances/exits in dark, tech-flavored segments; use B for any "look here" target call-out (replaces circling/arrow annotation without freezing the frame).

Intention

Two motifs from sci-fi fictional interfaces (Jarvis/Territory) that can be made neutral. A is a panel's ritual power-on/off: a 1px thin line snaps out very fast → stretches vertically into a full panel → content fades in ahead of the beat, exiting in reverse as the line compresses back to a point and dies (old-CRT power-off); a grayscale thin line already works, no tech-blue needed. B is a capture-in-motion: four L-shaped corner marks rush in from off-frame, overshoot and rebound, "clicking" onto the four corners of a target element plus a popped label — division of labor with freeze-annotate: that card freezes the frame to annotate, this one calls things out while things keep flowing.

Duration & energy

  • Duration: A: 3–4s (including exit) / B: 2–3s
  • Energy: A: Medium / B: Medium-high (the lock-on frame is the hit point)

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

意图

科幻虚构界面(Jarvis/Territory)的两个可中性化母题。A 是面板的 仪式感开关机:1px 细线极快抽出→纵向撑开成面板→内容淡入,退场反向 压线缩点熄灭(老 CRT 关机);灰阶细线即成立,不需要科技蓝。 B 是运动中的捕获:四个 L 形角标从画外冲入、超调回弹、"咔"地咬合 到目标元素四角+弹标签——与 freeze-annotate 分工:那卡冻结画面标注, 本卡在流动中点名。

两式选型

| 式 | 做法 | 适用 | |----|------|------| | A line-unfold-panel | scaleX 0→1(out poly4 急抽 5f)接 scaleY 3px→满高(out cubic 9f),内容提前一拍淡入;退场镜像反序 | 暗场面板入退场;开关机仪式感 | | B reticle-lock-on | 四 L 角=同一对矩形四份镜像,飞入(10f out cubic)与收缩(2.2×→0.94×→1 超调回弹)解耦;咬合帧目标微亮+标签 back 弹出 | 目标点名;踩音效重拍 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 两段节奏 | 抽线 5f 急 vs 撑面 9f 缓 | 节奏差是手法本体,等长就平了 | | A 线体 | 白色发光条 3px 级;线/点阶段与面板阶段条件切换 | 熄灭后必须条件卸载才有真静止 | | B 飞入行程 | 画外 ≥1000px 冲入 | 行程短读作"角标出现"不是"扑过来" | | B 超调 | 收缩过头到 0.94× 再回弹 1 | 无超调没有"咔"的咬合感 | | B 咬合帧 | 目标白 overlay 快闪 0.55→停 0.28 + 标签同帧弹出 | 微亮与标签错帧就散了 |

已知坑

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

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

  • Remotion 无 Easing.quart——用 Easing.poly(4)(demo 实测报

"easing is not a function" 判例)

  • B 一支片 ≤2 次:连用读作军事模板;两次锁定目标必须不同
  • A 与 glow-flyline 同属暗场词汇,相邻段落别背靠背堆光效(Q4 同源)
  • 声音:A 抽线一声短 whoosh、撑开一声轻 pop;B 咬合帧一声"咔"

(机械 click 而非游戏 UI 音,S1 边界内)

参考实现

demos/effects/fui-hud-moves/ (LineUnfoldPanel.tsx / ReticleLockOn.tsx)

Reference implementation (Remotion)

  • demos/effects/fui-hud-moves/LineUnfoldPanel.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/LineUnfoldPanel.tsx
  • demos/effects/fui-hud-moves/ReticleLockOn.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/ReticleLockOn.tsx

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

Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.

  1. Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
  2. Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
  3. Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/fui-hud-moves.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/fui-hud-moves/LineUnfoldPanel.tsx, demos/effects/fui-hud-moves/ReticleLockOn.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("fui-hud-moves")

Prompt

Use the video-shotcraft shot recipe "FUI/HUD moves" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/fui-hud-moves.json
Raw TSX: https://gimgs.net/motion/fui-hud-moves/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/effects/fui-hud-moves/LineUnfoldPanel.tsx, demos/effects/fui-hud-moves/ReticleLockOn.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.72s

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

8,347 chars · Apache-2.0Open raw template ↗
// ===== demos/effects/fui-hud-moves/LineUnfoldPanel.tsx =====
// line-unfold-panel —— 一线展面(Jarvis/FUI 母题)
// 暗底。入场两拍:3px 细线从中点向两侧极快抽出(5f)→ 定宽后纵向
// 撑开成 Card 面板(9f,out 缓动)→ 内容延迟淡入。
// 静置展示后反向退场:压扁成线(7f)→ 线缩成点 → 熄灭,像老 CRT 关机。
// f0–12 空场静置;入场 f12–34;持面板至 f78;退场 f78–98;末静止 ≥42f(140f)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, Card } from '../../_fixtures/Fixtures';

const PANEL_W = 760;
const PANEL_H = 460;
const CX = 960;
const CY = 540;

// —— 入场时间表 ——
const T0 = 12; // 点亮起点
const LINE_END = T0 + 5; // 线抽出完成 f17
const UNFOLD_END = LINE_END + 9; // 面板撑开完成 f26
const CONTENT_END = UNFOLD_END + 8; // 内容淡入完成 f34

// —— 退场时间表 ——
const OUT0 = 78; // 开始压扁
const COLLAPSE_END = OUT0 + 7; // 压成线 f85
const SHRINK_END = COLLAPSE_END + 6; // 线缩成点 f91
const OFF = SHRINK_END + 4; // 点熄灭 f95

const clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const };

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

  // 入场:scaleX(线抽出)快进快停,入场后保持 1
  const inSX = interpolate(frame, [T0, LINE_END], [0.004, 1], {
    easing: Easing.out(Easing.poly(4)),
    ...clamp,
  });
  // 入场:scaleY(纵向撑开),线阶段压在 3px
  const inSY = interpolate(frame, [LINE_END, UNFOLD_END], [3 / PANEL_H, 1], {
    easing: Easing.out(Easing.cubic),
    ...clamp,
  });
  // 内容淡入(面板撑开过半才开始)
  const contentOp = interpolate(frame, [UNFOLD_END - 3, CONTENT_END], [0, 1], {
    easing: Easing.out(Easing.quad),
    ...clamp,
  });

  // 退场:先压 Y 回线,再缩 X 回点
  const outSY = interpolate(frame, [OUT0, COLLAPSE_END], [1, 3 / PANEL_H], {
    easing: Easing.in(Easing.cubic),
    ...clamp,
  });
  const outSX = interpolate(frame, [COLLAPSE_END, SHRINK_END], [1, 0.004], {
    easing: Easing.in(Easing.poly(4)),
    ...clamp,
  });
  // 内容在压扁前先撤
  const contentOutOp = interpolate(frame, [OUT0 - 4, OUT0 + 2], [1, 0], clamp);

  const sx = frame < OUT0 ? inSX : outSX;
  const sy = frame < OUT0 ? inSY : outSY;

  // 末点熄灭:opacity 快落。f >= OFF 后整个元素条件卸载 → 真静止
  const dotOp = interpolate(frame, [SHRINK_END, OFF], [1, 0], {
    easing: Easing.in(Easing.quad),
    ...clamp,
  });

  const alive = frame >= T0 && frame < OFF;
  // 面板阶段(sy 足够大)显示卡片内容;线/点阶段显示发光条
  const isPanel = sy > 0.15;

  return (
    <div style={{ width: 1920, height: 1080, background: '#1c1c1b', overflow: 'hidden', position: 'relative' }}>
      <div
        style={{
          position: 'absolute',
          top: 120,
          width: '100%',
          textAlign: 'center',
          fontFamily: 'Helvetica, Arial, sans-serif',
          fontWeight: 800,
          fontSize: 52,
          color: G.mid,
          letterSpacing: 2,
        }}
      >
        LINE UNFOLD PANEL
      </div>
      {alive && (
        <div
          style={{
            position: 'absolute',
            left: CX - PANEL_W / 2,
            top: CY - PANEL_H / 2,
            width: PANEL_W,
            height: PANEL_H,
            transform: `scaleX(${sx}) scaleY(${sy})`,
            transformOrigin: '50% 50%',
            opacity: dotOp,
          }}
        >
          {isPanel ? (
            <>
              <Card w={PANEL_W} h={PANEL_H} seed={3} style={{ border: `2px solid ${G.mid}`, boxShadow: '0 0 40px rgba(255,255,255,0.18)' }} />
              {/* 内容层单独控 opacity:盖一层暗板模拟"内容未亮" */}
              <div
                style={{
                  position: 'absolute',
                  inset: 2,
                  borderRadius: 12,
                  background: G.card,
                  opacity: 1 - Math.min(contentOp, contentOutOp),
                }}
              />
            </>
          ) : (
            // 线/点阶段:白色发光条填满整个盒(被 scale 压成线)
            <div style={{ width: '100%', height: '100%', background: '#ffffff', boxShadow: '0 0 60px rgba(255,255,255,0.9)', borderRadius: 2 }} />
          )}
        </div>
      )}
    </div>
  );
};


// ===== demos/effects/fui-hud-moves/ReticleLockOn.tsx =====
// reticle-lock-on —— 准星咬合(钢铁侠 HUD / 安德的游戏)
// FakeDashboard 静置。四个 L 形角标组成的取景框从画外飞入(大框),
// 超调回弹后收缩贴紧目标卡片四角"咔"地咬合定格;咬合帧卡片微亮 +
// 旁弹一行小标签。画面不冻结,是运动中的捕获。
// f0–14 面板静置;f14–24 飞入;f24–46 收缩(含过冲回弹);f46 咬合;
// 标签 f46–56 弹出;f56 后真静止 ≥84f(140f 总长)。
import React from 'react';
import { useCurrentFrame, interpolate, Easing } from 'remotion';
import { G, FakeDashboard } from '../../_fixtures/Fixtures';

// 目标:variant A 网格第 2 张卡(第一行中间)。
// 布局推导:侧栏 220 + padding 36,网格 3 列 gap 28,
// 内容区宽 1700-72=1628 → 卡宽 (1628-56)/3 ≈ 524,卡高 (1080-72-72-28)/2 ≈ 454...
// 实测近似:目标框取第一行中间卡的外接矩形(含少量呼吸边距)。
const CARD_X = 220 + 36 + 524 + 28; // ≈ 808
const CARD_Y = 72 + 36; // 108
const CARD_W = 524;
const CARD_H = 425;
const PAD = 14; // 咬合后角标与卡的呼吸距

const TCX = CARD_X + CARD_W / 2;
const TCY = CARD_Y + CARD_H / 2;

const FLY_IN = 14; // 飞入开始
const FLY_END = 24; // 飞入结束(大框就位)
const LOCK_END = 46; // 咬合帧
const LABEL_END = 56;

const ARM = 56; // L 臂长
const THICK = 10; // L 粗

const clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const };

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

  // 飞入:整框从右下画外冲进来,同时是个放大 2.2× 的大框
  const flyT = interpolate(frame, [FLY_IN, FLY_END], [0, 1], {
    easing: Easing.out(Easing.cubic),
    ...clamp,
  });
  const flyDX = (1 - flyT) * 1100;
  const flyDY = (1 - flyT) * 620;

  // 收缩咬合:半宽/半高从 2.2× 收到贴紧,带超调(收过头 6% 再弹回)
  const shrink = interpolate(
    frame,
    [FLY_END, LOCK_END - 8, LOCK_END - 3, LOCK_END],
    [2.2, 1.06, 0.94, 1],
    { easing: Easing.inOut(Easing.cubic), ...clamp },
  );
  const hw = (CARD_W / 2 + PAD) * shrink;
  const hh = (CARD_H / 2 + PAD) * shrink;

  // 咬合帧:卡片微亮(一层白色 overlay 快闪后停在 0.28)
  const glow = interpolate(frame, [LOCK_END, LOCK_END + 3, LOCK_END + 10], [0, 0.55, 0.28], clamp);

  // 标签:咬合后从角标右上弹出(scaleX 展开 + 淡入)
  const labelT = interpolate(frame, [LOCK_END + 2, LABEL_END], [0, 1], {
    easing: Easing.out(Easing.back(1.8)),
    ...clamp,
  });

  const showReticle = frame >= FLY_IN;
  const locked = frame >= LOCK_END;

  // 四个 L 角:位置 = 中心 ± (hw, hh),各自镜像
  const corners = [
    { x: TCX - hw, y: TCY - hh, sx: 1, sy: 1 },
    { x: TCX + hw, y: TCY - hh, sx: -1, sy: 1 },
    { x: TCX - hw, y: TCY + hh, sx: 1, sy: -1 },
    { x: TCX + hw, y: TCY + hh, sx: -1, sy: -1 },
  ];

  return (
    <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>
      <FakeDashboard variant="A" />
      {/* 咬合微亮层:条件渲染,锁定前不存在 */}
      {locked && (
        <div
          style={{
            position: 'absolute',
            left: CARD_X,
            top: CARD_Y,
            width: CARD_W,
            height: CARD_H,
            borderRadius: 14,
            background: '#ffffff',
            opacity: glow,
            pointerEvents: 'none',
          }}
        />
      )}
      {showReticle && (
        <div style={{ position: 'absolute', left: 0, top: 0, transform: `translate(${flyDX}px, ${flyDY}px)` }}>
          {corners.map((c, i) => (
            <div
              key={i}
              style={{
                position: 'absolute',
                left: c.x,
                top: c.y,
                transform: `scale(${c.sx}, ${c.sy})`,
                transformOrigin: '0 0',
              }}
            >
              {/* L 形角标:横臂 + 竖臂 */}
              <div style={{ position: 'absolute', left: 0, top: 0, width: ARM, height: THICK, background: G.ink }} />
              <div style={{ position: 'absolute', left: 0, top: 0, width: THICK, height: ARM, background: G.ink }} />
            </div>
          ))}
          {/* 小标签:右上角标外侧弹出 */}
          {frame >= LOCK_END + 2 && (
            <div
              style={{
                position: 'absolute',
                left: TCX + hw + 18,
                top: TCY - hh - 6,
                transform: `scale(${labelT})`,
                transformOrigin: '0 50%',
                background: G.ink,
                color: '#fff',
                fontFamily: 'Helvetica, Arial, sans-serif',
                fontWeight: 700,
                fontSize: 26,
                padding: '10px 18px',
                borderRadius: 8,
                whiteSpace: 'nowrap',
                opacity: Math.min(1, labelT * 1.5),
              }}
            >
              TARGET · CARD_02
            </div>
          )}
        </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/effects/fui-hud-moves/LineUnfoldPanel.tsx, demos/effects/fui-hud-moves/ReticleLockOn.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
—

More in this category