Motion · Interaction
Glass pill dictation typing
On black, a glass pill pops in at 1.25× then eases to size, an accent light sweeping inside; the cursor leads, placeholder text types in as the glow dims with typing progress, ending as a neutral dark glass bar.
Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player
When to use
The entrance of a voice/AI input box; an interaction cue for "talk to it"; a quiet transitional beat between high-energy segments.
Intention
Tell a small story with "the light dims as input arrives": while idle it glows (waiting for you to speak), and the moment you start talking it hands the glow over to the text. In 1.7s only three things happen — pop in, type, light fades. Any extra motion (bounce, color change, icon animation) would break this beat's function as a rest.
Duration & energy
- Duration: About 1.7s (50f@30fps)
- Energy: Low (the quietest beat in the whole film, only the light receding)
Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)
意图
用"光随输入熄灭"讲一件很小的事:待命时它在发光(等你说话),你一开口 它就把光让给文字。1.7s 里只做三件事——弹出、打字、光退。任何额外动作 (弹跳、色变、图标动画)都会让这一拍不再是休止符。
动效核心
- 胶囊高 46px、圆角 23px、
padding: 0 8px 0 16px,定宽
PW = 文本实测宽 + 168px(≈整句宽度的 2 倍),不随打字伸缩
- 出场
scale 1.25→1,seg(0→0.22, outCubic)(≈0.36s);
opacity 在前 0.025 就跳满——先"在了"再落位
- 内嵌强调色光是一层
linear-gradient(90deg, α0 → α.35 @42% → α.95 @100%)
的左暗右亮渐层(光在胶囊内部,不是外部光晕)
- 打字:光标 t≈0.025 先行出现,字符
seg(0.06→0.73)匀速铺完 18 字符,
尾段保持;光标 0.75→0.8 撤掉
- 光熄灭
g = 1 − seg(0.08→0.76, inOutQuad),与打字进度同窗口;
g 同时驱动三层阴影收敛:内描边 0.16+0.1g、内顶光 0.03+0.04g、外泛光 26g px / 0.28g
- 右端 30px 描边圆角方块内 5 根竖条声波,基准高
[13,7,10,6,9]
(左高右低),叠 ±1.6·sin(t·18 + i·1.7) 极轻呼吸
参数表
| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 胶囊定宽 | 文本实测宽 + 168px(≈句宽 2 倍) | 定宽是关键:随字伸缩会读作 chip 而不是输入条 | | 出场缩放 | scale 1.25→1,t 0→0.22 outCubic | 1.25 倍是"从观众面前落回去"的量;1.5 倍就成了 pop 弹窗 | | opacity 时序 | 前 0.025 跳满(早于缩放落位) | 同步淡入会让它像"渐渐出现",失掉那一下轻微的实感 | | 内嵌光 | 90deg、0 → .35@42% → .95@100% | 光必须在胶囊内部;改成外 glow 就成了"待通知"而不是"待输入" | | 打字 | 18 字符,t 0.06→0.73 匀速 | 匀速 ≈ 3f/字符;加 ease 就读作"有人在犹豫" | | 光熄灭 | 1 → 0,t 0.08→0.76 inOutQuad | 与打字同窗口是全片唯一的因果:光让位给文字 | | 阴影收敛 | 内描边 0.16+0.1g / 内顶光 0.03+0.04g / 外泛光 26g px | 三层都挂在同一个 g 上,收尾自然落到"中性深色玻璃条" | | 声波竖条 | 5 根、基准 [13,7,10,6,9]、±1.6px、频率 18 | 幅度到 4px 就成了"正在录音"的活跃态,与"待命"矛盾 | | 光标 | t≈0.025 先行、0.75→0.8 撤 | 光标先于文字出现,是"输入框已就绪"的信号 |
已知坑
- 定宽靠一个
visibility:hidden的测量节点算offsetWidth,字体必须
与正式文本完全一致(400 21px + letter-spacing:.3px), 差一档就宽度不对
- 声波竖条是持续呼吸(不受 t 分段控制),所以首尾帧的高度不同——
用作 loop 素材时要把频率 18 调成 2π 的整数倍
- 打字进度
floor(seg(…)*len + 1e-6),那个 epsilon 是防止末字符在
t=0.73 整点因浮点误差不出现,别删
- ACCENT_RGB(默认
146,126,212紫)内嵌光与外泛光共用,
换项目品牌色只改这一个变量
- 占位句 "Speak or type here"(18 字符);换句要保持字符数接近,
否则匀速打字的节奏和定宽比例都要重调
参考实现
demos/interaction/glass-pill-dictation-typing/ (GlassPillDictationTyping.tsx)
Reference implementation (Remotion)
- demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.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.
- Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
- Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
- 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/glass-pill-dictation-typing.jsongit clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx
npx remotion render <CompositionId> out.mp4claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("glass-pill-dictation-typing")Prompt
Use the video-shotcraft shot recipe "Glass pill dictation typing" (Remotion composition, Apache-2.0) published on gimgs.net. Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/glass-pill-dictation-typing.json Raw TSX: https://gimgs.net/motion/glass-pill-dictation-typing/template.html Repository: https://github.com/Vincentwei1021/video-shotcraft (component: demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx; imports demos/_fixtures and assets/lib) Spec: 1920x1080, 30 fps, 2.17s 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).
// ===== demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx =====
// glass-pill-dictation-typing — Glass Pill Dictation 玻璃胶囊听写(motion-lab 定稿转原生 Remotion)
// 纯黑底上一条定宽玻璃胶囊:整条以约 1.25 倍略大弹出后缓落到位,胶囊内部自左暗到右亮
// 铺一层强调色光(ACCENT 变量,默认紫);光标先行出现,随后打字出现占位句
// 「Speak or type here」,光随打字进度渐渐熄灭,收尾成中性深色玻璃条;右端是描边圆角
// 方块里的竖条声波图标——全片最安静的一拍。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React, { useLayoutEffect, useRef, useState } from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';
export const GLASS_PILL_DICTATION_TYPING_DURATION = 50; // 1650ms @30fps
// 模板强调色:实际使用时按项目品牌色替换这一个变量(内嵌光 + 外泛光共用)
const ACCENT_RGB = '146,126,212';
const UI = '-apple-system,system-ui,"Segoe UI",sans-serif';
const PH = 46; // 胶囊高度
const ICON = 30; // 右端声波图标方块尺寸
const BASE = [13, 7, 10, 6, 9]; // 竖条基准高度(左高右低的听写图标)
const TEXT = 'Speak or type here'; // 占位句,长度贴近原片(19→18 字符),打字节奏不变
export const GlassPillDictationTyping: React.FC = () => {
const t = useT();
// 定宽:原片胶囊宽度约为整句文本宽度的 2 倍,不随词伸缩——挂载时实测一次整句宽度
const measRef = useRef<HTMLDivElement>(null);
const [textW, setTextW] = useState(180); // 兜底估算值,useLayoutEffect 实测后覆盖(先于首帧绘制)
useLayoutEffect(() => {
if (measRef.current) setTextW(measRef.current.offsetWidth);
}, []);
const PW = Math.round(textW + 168);
// 出场:整体略大(~1.25x)快速浮现,约 0.45s 内缓落到位
const s = lerp(seg(t, 0, 0.22, E.outCubic), 1.25, 1);
// 打字:caret 先行(~t0.03),字符 t0.06→0.73 匀速铺完,尾段保持
const n = Math.floor(seg(t, 0.06, 0.73) * TEXT.length + 1e-6);
const caretO = seg(t, 0.025, 0.045) * (1 - seg(t, 0.75, 0.8));
// 强调色光随打字进度渐熄;同步收掉描边亮度的一点富余
const g = 1 - seg(t, 0.08, 0.76, E.inOutQuad);
return (
<DesignStage bg="#000">
<div
style={{
position: 'absolute',
inset: 0,
background: '#000',
overflow: 'hidden',
// 暗部校色:原样片 x264 把外泛光的暗尾(1~2 级灰)压成纯 0,本渲染的
// 泛光尾巴因此比样片亮一档、拖得更远。contrast(1.008) 等价于黑端减 ~1 级
// (255*0.008/2≈1)、中间调不动,按对照帧实测校准(f1 0.9205→0.9844)。
filter: 'contrast(1.008)',
}}
>
{/* 居中走 translateX:PW 为奇数时圆心在 .5 半像素上,left/flex 会被布局
取整偏 0.5 设计像素;transform 矩阵不吸附,能与原片圆心逐像素对齐 */}
<div
style={{
position: 'absolute',
left: 0,
top: (270 - PH) / 2,
height: PH,
width: PW,
borderRadius: PH / 2,
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
padding: '0 8px 0 16px',
overflow: 'hidden',
background: '#0d0d13',
willChange: 'transform,opacity',
opacity: seg(t, 0, 0.025),
transform: `translateX(${(480 - PW) / 2}px) scale(${s})`,
boxShadow: `inset 0 0 0 1px rgba(255,255,255,${0.16 + 0.1 * g}), inset 0 14px 22px rgba(255,255,255,${0.03 + 0.04 * g}), 0 0 ${26 * g}px rgba(${ACCENT_RGB},${0.28 * g})`,
}}
>
{/* 内嵌强调色光:左暗右亮的渐层,随打字进度熄灭(原片光在胶囊内部,无外部光晕) */}
<div
style={{
position: 'absolute',
inset: 0,
pointerEvents: 'none',
opacity: g,
background: `linear-gradient(90deg,rgba(${ACCENT_RGB},0) 0%,rgba(${ACCENT_RGB},.35) 42%,rgba(${ACCENT_RGB},.95) 100%)`,
}}
/>
<div
style={{
position: 'relative',
font: `400 21px ${UI}`,
color: '#f4f4f5',
whiteSpace: 'pre',
letterSpacing: 0.3,
flex: 'none',
}}
>
{TEXT.slice(0, n)}
</div>
<div
style={{
position: 'relative',
width: 2,
height: 23,
borderRadius: 1,
background: '#eaeaec',
marginLeft: 2,
flex: 'none',
opacity: caretO,
}}
/>
<div
style={{
position: 'relative',
marginLeft: 'auto',
width: ICON,
height: ICON,
borderRadius: 9,
boxSizing: 'border-box',
border: '1.5px solid rgba(255,255,255,.5)',
background: 'rgba(255,255,255,.05)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 'none',
}}
>
{/* 声波竖条轻微呼吸(原片几乎静止,仅微动) */}
<div style={{ display: 'flex', alignItems: 'center', gap: 2.2, height: '100%' }}>
{BASE.map((h, i) => (
<div
key={i}
style={{
width: 1.5,
borderRadius: 1.5,
background: '#ececee',
height: h + 1.6 * Math.sin(t * 18 + i * 1.7),
}}
/>
))}
</div>
</div>
</div>
{/* 隐藏测量条:与正文同字体同字距,量一次整句宽度以定胶囊定宽 */}
<div
ref={measRef}
style={{
position: 'absolute',
visibility: 'hidden',
whiteSpace: 'pre',
font: `400 21px ${UI}`,
letterSpacing: 0.3,
top: -999,
}}
>
{TEXT}
</div>
</div>
</DesignStage>
);
};
- Read the recipePrompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
- Get the codetool: git
git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install - Register + rendertool: remotion
npx remotion render <CompositionId> out.mp4 # component: demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx - Or re-implementPort the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
| param | type | default | description |
|---|---|---|---|
| — | |||
More in this category

Live voice waveform
A live waveform in a recording pill — 64 thin bars rise and fall with "speech," spiking tall mid-speech and shrinking to a dotted line on pauses, scrolling right to left; a full speak-pause-speak-submit performance.

Type, filter, drill in
Typing a search on a real UI, the grid converges to a single card on its own, then a click drills through into the detail page.

Theme switch: sweep and ripple
Two theme-switch moves — theme-sweep, a diagonal wipe (the skin changes wherever the boundary passes), and palette-ripple, a combo move (a ⌘K panel collapses to a point, a ripple spreads from it to re-skin).
Segmented thumb hero
A segmented control's thumb shift becomes the star of a close-up — a cursor clicks it and a new icon springs in.