{
  "slug": "gradient-word-sweep",
  "title": "Gradient word charge sweep",
  "tagline": "A gradient of colored light sweeps fast left to right to \"charge\" a keyword in a black-scene tagline — brightest at the wavefront, decaying behind it, then settling into a steady lightning-breathing glow.",
  "description": "A gradient of colored light sweeps fast left to right to \"charge\" a keyword in a black-scene tagline — brightest at the wavefront, decaying behind it, then se",
  "category": "typography",
  "tags": [
    "shotcraft",
    "remotion",
    "typography",
    "effects"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 3.71,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/gradient-word-sweep/poster-db49db4a6982.jpg",
    "video": "https://gimgs.net/motion/media/gradient-word-sweep/video-b4a108cd3e2f.mp4"
  },
  "variants": [
    {
      "key": "gradient-word-sweep",
      "label": "gradient-word-sweep · Gradient word charge sweep",
      "description": "A gradient of colored light sweeps fast left to right to \"charge\" a keyword in a black-scene tagline — brightest at the wavefront, decaying behind it; once filled, fine violet-red lightning links between characters and the whole word settles into a steady breathing glow.",
      "use": "",
      "video": "https://gimgs.net/motion/media/gradient-word-sweep/video-b4a108cd3e2f.mp4",
      "poster": "https://gimgs.net/motion/media/gradient-word-sweep/poster-db49db4a6982.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/gradient-word-sweep.html",
  "json_url": "https://gimgs.net/motion/gradient-word-sweep.json",
  "template_url": "https://gimgs.net/motion/gradient-word-sweep/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/gradient-word-sweep.md",
  "updated_at": "2026-09-24T07:39:59.920Z",
  "prompt": "## When to use\n\nCharging a single verb/selling-point word in a tagline (Supercharged/faster/AI…); the typography moment of a high-energy peak segment; the slogan frame of a black-scene brand film.\n\n## Intention\n\nWithin an all-white sentence, only the keyword gets \"electrified\": a gradient of colored light sweeps fast left to right across the characters, like an energy injection — brightest the instant it passes (wavefront trail), then decaying to a steady state; once filled, fine lightning arcs jump between characters and the word keeps a breathing glow. Three crux points: **fast** (sweep-charge 15–20f; slower reads as a progress bar), **brightest at the wavefront** (the character that just lit up is strongest — without this gradient it's just a static clipped gradient), and **restrained glow** (After-Effects-style layered bloom, but don't let it smear into a blob; the lightning should be thin and sparse, a garnish not a flood).\n\n## Duration & energy\n\n- Duration: Sweep-charge ~15–20f (must be fast) + steady lightning-breathing 1–2s; whole segment 2.5–3.5s\n- Energy: High (a single-word peak; everything before and after should yield to it)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n整句白字里只有关键词被\"通电\"：渐变彩光从左到右快速扫过字符，\n像能量注入——扫过的瞬间最亮（波前尾迹），随后衰减到稳态；\n填满后字符之间跳起勾连的细闪电，词保持泛光呼吸。三个命门：\n**快**（扫充 15–20f，慢了读作进度条）、**波前最亮**（刚点亮的\n字符效果最强，没有这个梯度就是静态渐变裁切）、**辉光克制**\n（AE 式多层泛光但别糊成一团，闪电细而稀疏才是点缀）。\n\n## 动效核心\n- 渐变填充：关键词做双层文字（白/渐变彩），彩层用从左到右推进的\n  mask 揭开；斜切 mask 边缘略糊（~8% 词宽）\n- 波前尾迹增亮：跟随 mask 前沿一条 ~34% 词宽的增亮带，波前处\n  辉光最强、向后衰减；填充结束后 ~10f 淡出到稳态\n- 辉光：4 层泛光（环境大柔光+中晕+近核+亮核），稳态 opacity\n  0.55–0.72 档（实测：0.75–0.95 档被裁\"泛光感有点强\"）\n- 闪电：填满后字符间勾连细闪电，紫红系三层描边（外晕 6/中 2.4/\n  亮核 1.4），mulberry32 种子随机跳位闪烁，稀疏零星（不是持续电弧）\n- 禁：填充过程不带跟随移动的光点/星光（实测判例：跟随白色光点读作廉价装饰，删）\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 扫充时长 | 15–20f | 原片\"速度很快\"；>28f 读作进度条 |\n| 波前增亮带 | ~34% 词宽，波前最亮向后衰减 | 无梯度＝静态渐变；带太宽＝整词一起亮没有\"注入\"感 |\n| 稳态泛光 | 4 层 opacity 0.55/0.55/0.62/0.72 | 0.75+ 档被裁太强；辉光层数<3 读作纯色 |\n| 闪电线宽 | 外 6/中 2.4/核 1.4 @1080p | 加倍即被裁\"粗\"；颜色偏紫红（magenta 216,60,190 系） |\n| 闪电频率 | 稀疏零星（同屏 ≤2 道，间歇 >8f） | 频率高读作电弧故障；实测 demo 仍略密于原片 |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- 判例三条（用户逐字意见沉淀）：①填充过程禁跟随光点；②闪电\n  必须紫红+细；③泛光宁弱勿强，波前梯度补足冲击力\n- 与 type-rhythm-sync/cel-flash-stomp 分工：那些是节拍/砸字；\n  本卡是单词\"充能\"的能量语义，词必须是句中卖点词\n- 关键词外的正文保持纯白静止——全句都动就没有\"只有它通电\"的戏\n\n## 参考实现\ndemos/typography/gradient-word-sweep/\n（GradientWordSweep.tsx）\n原片出处：clickup-30.mp4\n\n## Reference implementation (Remotion)\n\n- demos/typography/gradient-word-sweep/GradientWordSweep.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/gradient-word-sweep/GradientWordSweep.tsx\n\nThe component imports shared fixtures from `demos/_fixtures` and helpers from `assets/lib` in the repo; SKILL.md there documents the render workflow.\n",
  "flow": [
    {
      "step": "Read the recipe",
      "note": "Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points."
    },
    {
      "step": "Get the code",
      "tool": "git",
      "cmd": "git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install"
    },
    {
      "step": "Register + render",
      "tool": "remotion",
      "cmd": "npx remotion render <CompositionId> out.mp4   # component: demos/typography/gradient-word-sweep/GradientWordSweep.tsx"
    },
    {
      "step": "Or re-implement",
      "note": "Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing."
    }
  ],
  "files": [
    {
      "path": "demos/typography/gradient-word-sweep/GradientWordSweep.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/gradient-word-sweep/GradientWordSweep.tsx"
    }
  ],
  "contract": "React/Remotion composition (TSX) from the open-source video-shotcraft library (Apache-2.0). It is not a browser-playable template: clone the repo, register the component in a Remotion project and render with `npx remotion render`. The recipe (prompt) describes timing, easing and parameters so you can also re-implement the shot in any engine.",
  "render": {
    "engine": "remotion",
    "frames": 111,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/gradient-word-sweep/GradientWordSweep.tsx"
    ],
    "note": "Clone https://github.com/Vincentwei1021/video-shotcraft (npm install). The component lives under demos/{category}/{name}/ and imports shared fixtures from demos/_fixtures and helpers from assets/lib. Copy it (with those imports) into a Remotion project, register a <Composition> with the template's width/height/fps/duration in Root.tsx, then `npx remotion render <CompositionId> out.mp4`. SKILL.md in the repo documents the full Claude Code / Codex workflow. The prompt below is the shot recipe (timing, easing, parameter table, pitfalls) — enough to re-implement the shot in HTML/WAAPI or another engine if you prefer."
  },
  "source": "// ===== demos/typography/gradient-word-sweep/GradientWordSweep.tsx =====\n// gradient-word-sweep v3 —— 批次 12 按用户意见微调（v2 结构保留）：\n// 1) 闪电偏紫红色 + 线宽调细（约减半）；\n// 2) 整体泛光强度略降（各辉光层 opacity 下调）；\n// 3) 波前尾迹梯度：刚被点亮的字符辉光最强，随扫过距离衰减到稳态\n//    （trailing-window 增亮层，填充结束后淡出到稳态呼吸）。\nimport React from 'react';\nimport { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';\n\nconst mulberry32 = (a: number) => () => {\n  let t = (a += 0x6d2b79f5);\n  t = Math.imul(t ^ (t >>> 15), t | 1);\n  t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n};\n\nconst FONT = '\"Avenir Next\", Futura, \"Helvetica Neue\", sans-serif';\n// 截图 5：S 偏蓝青 → 中段紫 → 粉 → 尾部琥珀\nconst GRAD = 'linear-gradient(92deg, #59c2ff 0%, #9d6bff 32%, #ff6ed4 62%, #ffc46b 100%)';\n\nconst FILL_START = 12;\nconst FILL_END = 30; // 18 帧 ≈ 0.6s，快扫\nconst LIGHT_START = FILL_END + 3;\n\n// ---------- 种子化噪声与闪电 ----------\nconst rand = mulberry32(20260718);\nconst FLICKER: number[] = Array.from({ length: 160 }, () => rand());\n\ntype Bolt = { d: string; long: boolean };\nconst makeLongBolt = (r: () => number): Bolt => {\n  // 词上方勾连长弧：横跨若干字符，锯齿折线\n  const x0 = 30 + r() * 220;\n  const x1 = x0 + 160 + r() * 320;\n  const yBase = 38 + r() * 42;\n  const n = 7 + Math.floor(r() * 4);\n  let d = `M ${x0.toFixed(1)} ${(yBase + 26 + r() * 20).toFixed(1)}`;\n  for (let i = 1; i <= n; i++) {\n    const x = x0 + ((x1 - x0) * i) / n + (r() - 0.5) * 22;\n    const arch = Math.sin((i / n) * Math.PI) * -22; // 中段拱起\n    const y = yBase + arch + (r() - 0.5) * 30 + (i === n ? 30 + r() * 18 : 0);\n    d += ` L ${x.toFixed(1)} ${y.toFixed(1)}`;\n  }\n  return { d, long: true };\n};\nconst makeShortBolt = (r: () => number): Bolt => {\n  // 字符之间的短勾连：竖向小锯齿\n  const x = 60 + r() * 540;\n  const y0 = 72 + r() * 24;\n  const y1 = y0 + 55 + r() * 45;\n  const n = 4 + Math.floor(r() * 3);\n  let d = `M ${x.toFixed(1)} ${y0.toFixed(1)}`;\n  for (let i = 1; i <= n; i++) {\n    const y = y0 + ((y1 - y0) * i) / n;\n    const xx = x + (r() - 0.5) * 30;\n    d += ` L ${xx.toFixed(1)} ${y.toFixed(1)}`;\n  }\n  return { d, long: false };\n};\n\nconst BOLTS: Bolt[] = Array.from({ length: 16 }, (_, i) =>\n  i % 3 === 0 ? makeShortBolt(rand) : makeLongBolt(rand),\n);\n// 闪烁事件：帧窗内某条闪电点亮，随机跳位\ntype Flash = { at: number; life: number; bolt: number };\nconst FLASHES: Flash[] = Array.from({ length: 36 }, () => ({\n  at: LIGHT_START + Math.floor(rand() * 70),\n  life: 2 + Math.floor(rand() * 4),\n  bolt: Math.floor(rand() * BOLTS.length),\n}));\n\nexport const GradientWordSweep: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  const enter = interpolate(frame, [0, 12], [0, 1], {\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n\n  // 快速填充进度\n  const p = interpolate(frame, [FILL_START, FILL_END], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.quad),\n  });\n  const pPct = p * 100;\n  const filling = frame >= FILL_START && frame <= FILL_END + 4;\n  const headFade = interpolate(frame, [FILL_END, FILL_END + 6], [1, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  // 波前尾迹：刚点亮字符最亮，向后衰减；填充结束后整体淡出到稳态\n  const trailFade = interpolate(frame, [FILL_END, FILL_END + 10], [1, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const TRAIL = 34; // 尾迹长度（% 宽度）\n  const trailMask =\n    `linear-gradient(90deg, transparent 0%, transparent ${Math.max(0, pPct - TRAIL)}%, ` +\n    `rgba(0,0,0,0.9) ${Math.max(0, pPct - 3)}%, rgba(0,0,0,0.9) ${Math.min(100, pPct + 1)}%, ` +\n    `transparent ${Math.min(100, pPct + 6)}%)`;\n\n  const noise = FLICKER[Math.min(frame, FLICKER.length - 1)];\n  // 当前帧活跃闪电\n  const active = FLASHES.filter((f) => frame >= f.at && frame < f.at + f.life);\n  const boltBoost = active.length > 0 ? 0.35 : 0;\n  // 辉光呼吸：填满后带噪声微闪 + 闪电时增亮\n  const glowLvl =\n    interpolate(frame, [FILL_START, FILL_END], [0, 1], {\n      extrapolateLeft: 'clamp',\n      extrapolateRight: 'clamp',\n    }) *\n    (0.82 + 0.18 * noise) +\n    boltBoost;\n\n  // 软边遮罩：辉光层的填充边缘不生硬\n  const softMask = (soft: number): string =>\n    p >= 1\n      ? 'none'\n      : `linear-gradient(90deg, #000 0%, #000 ${Math.max(0, pPct - soft)}%, transparent ${Math.min(100, pPct + soft * 0.6)}%)`;\n  const maskStyle = (soft: number): React.CSSProperties =>\n    p >= 1\n      ? {}\n      : {\n          WebkitMaskImage: softMask(soft),\n          maskImage: softMask(soft),\n        };\n\n  const lineStyle: React.CSSProperties = {\n    fontFamily: FONT,\n    fontWeight: 700,\n    fontStyle: 'italic',\n    fontSize: 92,\n    letterSpacing: -1,\n    lineHeight: 1.28,\n    color: '#ffffff',\n    whiteSpace: 'nowrap',\n  };\n\n  const gradText: React.CSSProperties = {\n    position: 'absolute',\n    inset: 0,\n    backgroundImage: GRAD,\n    WebkitBackgroundClip: 'text',\n    backgroundClip: 'text',\n    color: 'transparent',\n  };\n\n  return (\n    <AbsoluteFill style={{ background: '#050505', justifyContent: 'center', alignItems: 'center' }}>\n      {/* 词后方环境泛光（大半径，随充能增强） */}\n      <div\n        style={{\n          position: 'absolute',\n          left: 500,\n          top: 340,\n          width: 820,\n          height: 320,\n          borderRadius: '50%',\n          background:\n            'radial-gradient(closest-side, rgba(180,110,255,0.6), rgba(255,110,212,0.25) 55%, transparent 78%)',\n          filter: 'blur(38px)',\n          opacity: 0.55 * glowLvl,\n        }}\n      />\n      <div\n        style={{\n          textAlign: 'center',\n          opacity: enter,\n          transform: `translateY(${(1 - enter) * 36}px)`,\n        }}\n      >\n        <div style={lineStyle}>\n          <span style={{ position: 'relative', display: 'inline-block' }}>\n            <span>Supercharged</span>\n            {/* AE 式辉光：大半径柔光层（最糊） */}\n            <span\n              aria-hidden\n              style={{\n                ...gradText,\n                ...maskStyle(14),\n                filter: 'blur(46px) saturate(1.6)',\n                opacity: 0.55 * glowLvl,\n                transform: 'scale(1.05)',\n              }}\n            >\n              Supercharged\n            </span>\n            {/* 中晕层 */}\n            <span\n              aria-hidden\n              style={{\n                ...gradText,\n                ...maskStyle(10),\n                filter: 'blur(18px) saturate(1.4) brightness(1.15)',\n                opacity: 0.62 * glowLvl,\n              }}\n            >\n              Supercharged\n            </span>\n            {/* 近核柔光 */}\n            <span\n              aria-hidden\n              style={{\n                ...gradText,\n                ...maskStyle(7),\n                filter: 'blur(6px) brightness(1.25)',\n                opacity: 0.72 * Math.min(1, glowLvl + 0.1),\n              }}\n            >\n              Supercharged\n            </span>\n            {/* 清晰渐变本体 */}\n            <span\n              aria-hidden\n              style={{\n                ...gradText,\n                clipPath: `inset(-25% ${100 - pPct}% -25% 0)`,\n              }}\n            >\n              Supercharged\n            </span>\n            {/* 波前尾迹增亮：刚点亮字符辉光最强，向后衰减到稳态 */}\n            {trailFade > 0.01 && (\n              <span\n                aria-hidden\n                style={{\n                  ...gradText,\n                  WebkitMaskImage: trailMask,\n                  maskImage: trailMask,\n                  filter: 'blur(9px) saturate(1.7) brightness(1.7)',\n                  opacity: 0.95 * trailFade,\n                }}\n              >\n                Supercharged\n              </span>\n            )}\n            {/* 填充头字符过曝亮核（字符形状，非独立光点；仅填充期间） */}\n            {filling && p < 1 && (\n              <span\n                aria-hidden\n                style={{\n                  position: 'absolute',\n                  inset: 0,\n                  color: '#fff',\n                  clipPath: `inset(-25% ${Math.max(0, 100 - pPct)}% -25% ${Math.max(0, pPct - 10)}%)`,\n                  filter: 'blur(3px)',\n                  opacity: 0.9 * headFade,\n                  textShadow: '0 0 22px rgba(255,255,255,0.9), 0 0 55px rgba(216,150,255,0.8)',\n                }}\n              >\n                Supercharged\n              </span>\n            )}\n            {/* 勾连闪电：填充完成后，字符之间/词上方随机闪烁跳位 */}\n            <svg\n              aria-hidden\n              viewBox=\"0 0 700 240\"\n              style={{\n                position: 'absolute',\n                left: -25,\n                top: -62,\n                width: 700,\n                height: 240,\n                overflow: 'visible',\n                pointerEvents: 'none',\n              }}\n            >\n              {active.map((f, i) => {\n                const b = BOLTS[f.bolt];\n                const decay = 1 - (frame - f.at) / f.life;\n                return (\n                  <g key={`${f.at}-${i}`} opacity={Math.min(1, 1.1 * decay)}>\n                    <path\n                      d={b.d}\n                      fill=\"none\"\n                      stroke=\"rgba(216,60,190,0.65)\"\n                      strokeWidth={b.long ? 6 : 4.5}\n                      strokeLinejoin=\"miter\"\n                      style={{ filter: 'blur(6px)' }}\n                    />\n                    <path\n                      d={b.d}\n                      fill=\"none\"\n                      stroke=\"rgba(235,110,215,0.9)\"\n                      strokeWidth={b.long ? 2.4 : 1.9}\n                      strokeLinejoin=\"miter\"\n                      style={{ filter: 'blur(1.5px)' }}\n                    />\n                    <path\n                      d={b.d}\n                      fill=\"none\"\n                      stroke=\"#ffd8f2\"\n                      strokeWidth={b.long ? 1.4 : 1.1}\n                      strokeLinejoin=\"miter\"\n                    />\n                  </g>\n                );\n              })}\n            </svg>\n          </span>{' '}\n          <span>performance</span>\n        </div>\n        <div style={lineStyle}>with rock-solid reliability</div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}