Motion · Tipografia

Paper title card

A sentence letterpress-stamps onto paper word by word, one word marked in italic accent color, closed off by a short dash.

960×54016:930 fps1.83s · 55 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#typography#transition#rhythm

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

A chapter transition / value-prop title card; a lead-in card before an important feature appears; a breathing beat for the whole film

Intention

Give the viewer a sentence's worth of breathing room between two product sequences: state clearly "what's next, and why it matters." The letterpress-stamp texture puts the title card in the same world as paper-and-ink-styled product visuals.

Duration & energy

  • Duration: 1.7–1.8s (50–55f)
  • Energy: Low (a breathing beat separating two high-energy shots)

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

意图

在两段产品画面之间给观众一句话的喘息:说清"接下来看什么、它值什么"。letterpress 压印质感让字卡与纸墨风格的产品画面同属一个世界。

动效核心

  • 单句文案逐词入场:scale 从大压到 1 + blur→0 + opacity(letterpress 压印配方)
  • 每句恰好一个重点词:italic + 强调色(模板片为琥珀)
  • 强调色短下划线 scaleX 0→1 收束
  • 尾部整卡淡出交棒;可挂 mono 小字副行 + DigitRoll 数字

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 逐词入场 | 第 i 词 delay = 4 + i·4、9f,bezier(0.2,0.75,0.3,1);scale 1.28→1 + blur 7px→0 + opacity | scale 起点 1.28 是"压印"读感的甜点——对照暗场版 TitleCard 用 40px 上移+blur 10,同配方明暗两版 | | 重点词 | italic + 强调色 oklch(52% 0.115 65),一句恰好一个 accent | 两个 accent 等于没有 accent;重点词选功能名/收益词(C2) | | 下划线 | 16→34f scaleX 0→1,220×6px 强调色 | 下划线是收束信号,出现即预告本卡快结束 | | 时长 | 全片 4 张字卡均 50–55f(≈1.8s) | 字卡时长定式化——观众读一句话的时间,长了拖节奏短了读不完 | | 版式 | 字号 116/serif;纸底 oklch(97.5% 0.008 82) + 中心暖光 radial;尾部 8f 淡出 | 纸底+暖光与产品画面同色系,字卡才不像广告插播 | | 副行 | mono 小字 + DigitRoll(如 "5 of 31 fetched today");digit-roll 参数见 list-stack-press 卡计数器行 | 副行给具体数字最有说服力;数字滚动必须在本卡淡出前落定(教训)——排节拍从本卡淡出帧倒推 |

声音

字卡出场统一钉 swoosh-quick(模板片四张字卡钉在 f220/565/725/885 同音)——同类元素同音是词汇表纪律(S2);字卡本身无落地重音,能量留给前后镜头。

已知坑

  • 文案要具体:带产品功能名+具体收益,抽象隐喻词被用户逐字改掉(C2——"one board"→"one place to go"、加 "Paper Radar");文案在画面锁定后按最终镜头重写一遍(C1)
  • 重要功能出场前要有引导字卡(C2 判例:文档镜头前应加一句引导,如 "Every project, linked to your weekly report")——字卡不是装饰,是章节路标

参考实现

demos/typography/paper-title-card/PaperTitleCard.tsx(原 template/src/aifl/PaperTitleCard.tsx,参数化:words/sub/subDigits 可换文案)

Reference implementation (Remotion)

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

Prompt

Use the video-shotcraft shot recipe "Paper title card" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/paper-title-card.json
Raw TSX: https://gimgs.net/motion/paper-title-card/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/typography/paper-title-card/PaperTitleCard.tsx; imports demos/_fixtures and assets/lib)
Spec: 960x540, 30 fps, 1.83s

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,573 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/typography/paper-title-card/PaperTitleCard.tsx =====
// paper-title-card —— 一句话逐词 letterpress 压印上纸字卡
// 章节转场/价值主张呼吸位:单句文案逐词压印入场(scale 大→1 + blur→0),
// 每句恰一个强调色斜体重点词,强调色下划线 scaleX 收束,尾部整卡淡出。
// 全片多张字卡统一 50–55f;纸底+中心暖光与纸墨风格产品画面同色系。
// 参数化:words/sub/subDigits 可换成目标产品文案;subDigits 走 DigitRoll
// 数字滚动(必须在本卡淡出前落定)。渲染本 demo 前把 text 复制进项目即可。
import React from 'react';
import { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';

export const PAPER_TITLE_CARD_DURATION = 55; // ≈1.8s @30fps

const SERIF = 'ui-serif, Georgia, "Times New Roman", serif';
const MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';

// 每句恰好一个 accent:功能名/收益词(C2)
const WORDS: { text: string; accent?: boolean }[] = [
  { text: 'All' },
  { text: 'your' },
  { text: 'team’s' },
  { text: 'research,' },
  { text: 'one', accent: true },
  { text: 'place' },
  { text: 'to' },
  { text: 'go.' },
];
const SUB = 'of 31 fetched today';
const SUB_DIGITS = '5';

// 数字滚动列(odometer 风格,复制自 template DigitRoll,数字带双份拼接保证
// 任何目标位都有滚感;tabular-nums 防横向抖)
const DIGITS = '0123456789';
const DigitColumn: React.FC<{ ch: string; delay: number; lineH: number; color: string }> = ({ ch, delay, lineH, color }) => {
  const frame = useCurrentFrame();
  if (ch < '0' || ch > '9') {
    return <span style={{ fontSize: 26, lineHeight: `${lineH}px`, color }}>{ch}</span>;
  }
  const target = DIGITS.indexOf(ch);
  const t = interpolate(frame, [delay, delay + 22], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.25, 0.8, 0.25, 1),
  });
  const offset = (10 + target) * t * lineH;
  return (
    <span style={{ display: 'inline-block', height: lineH }}>
      <span style={{ display: 'block', transform: `translateY(${-offset}px)` }}>
        {(DIGITS + DIGITS).split('').map((d, j) => (
          <span
            key={j}
            style={{
              display: 'block',
              fontSize: 26,
              lineHeight: `${lineH}px`,
              color,
              fontVariantNumeric: 'tabular-nums',
            }}
          >
            {d}
          </span>
        ))}
      </span>
    </span>
  );
};

const DigitRoll: React.FC<{ value: string; delay: number; color: string }> = ({ value, delay, color }) => {
  const lineH = 26 * 1.15;
  return (
    <span style={{ display: 'inline-flex', overflow: 'hidden', height: lineH, verticalAlign: 'bottom' }}>
      {value.split('').map((c, i) => (
        <DigitColumn key={i} ch={c} delay={delay + i * 4} lineH={lineH} color={color} />
      ))}
    </span>
  );
};

export const PaperTitleCard: React.FC = () => {
  const frame = useCurrentFrame();
  const duration = PAPER_TITLE_CARD_DURATION;
  const fadeOut = interpolate(frame, [duration - 8, duration], [1, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const underline = interpolate(frame, [16, 34], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.3, 0, 0.2, 1),
  });
  const subT = interpolate(frame, [10, 22], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  return (
    <AbsoluteFill
      style={{
        backgroundColor: 'oklch(97.5% 0.008 82)',
        justifyContent: 'center',
        alignItems: 'center',
        opacity: fadeOut,
        backgroundImage:
          'radial-gradient(1100px 750px at 50% 42%, oklch(99.3% 0.014 88 / 0.85), transparent 65%)',
      }}
    >
      <div style={{ textAlign: 'center', maxWidth: 1500 }}>
        <div
          style={{
            fontFamily: SERIF,
            fontSize: 116,
            fontWeight: 600,
            lineHeight: 1.14,
            color: 'oklch(18% 0.006 82)',
            letterSpacing: '-0.012em',
            display: 'flex',
            flexWrap: 'wrap',
            justifyContent: 'center',
            columnGap: '0.26em',
          }}
        >
          {WORDS.map((w, i) => {
            const delay = 4 + i * 4;
            const t = interpolate(frame, [delay, delay + 9], [0, 1], {
              extrapolateLeft: 'clamp',
              extrapolateRight: 'clamp',
              easing: Easing.bezier(0.2, 0.75, 0.3, 1),
            });
            return (
              <span
                key={i}
                style={{
                  opacity: t,
                  transform: `scale(${1.28 - 0.28 * t})`,
                  filter: `blur(${(1 - t) * 7}px)`,
                  display: 'inline-block',
                  fontStyle: w.accent ? 'italic' : 'normal',
                  color: w.accent ? 'oklch(52% 0.115 65)' : undefined,
                }}
              >
                {w.text}
              </span>
            );
          })}
        </div>
        <div
          style={{
            height: 6,
            width: 220,
            margin: '38px auto 0',
            borderRadius: 3,
            background: 'oklch(52% 0.115 65)',
            transform: `scaleX(${underline})`,
          }}
        />
        <div
          style={{
            fontFamily: MONO,
            fontSize: 26,
            letterSpacing: '0.12em',
            color: 'oklch(50% 0.006 82)',
            marginTop: 34,
            opacity: subT,
            textTransform: 'uppercase',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'baseline',
            gap: '0.5em',
          }}
        >
          <DigitRoll value={SUB_DIGITS} delay={12} color="oklch(52% 0.115 65)" />
          <span>{SUB}</span>
        </div>
      </div>
    </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/typography/paper-title-card/PaperTitleCard.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