{
  "slug": "value-stagger-gradient",
  "title": "Value-stagger gradient bars",
  "tagline": "16 bars enter with delay as a time stagger, while height/hue/offset/blur each lay out as a value gradient from first to last; the second beat moves the stagger origin to center and re-lays the pulse peaking there.",
  "description": "16 bars enter with delay as a time stagger, while height/hue/offset/blur each lay out as a value gradient from first to last; the second beat moves the stagge",
  "category": "ui-entrance",
  "tags": [
    "shotcraft",
    "remotion",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.5,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/value-stagger-gradient/poster-298a8cf16c26.jpg",
    "video": "https://gimgs.net/motion/media/value-stagger-gradient/video-118e27c93f2b.mp4"
  },
  "variants": [
    {
      "key": "value-stagger-gradient",
      "label": "value-stagger-gradient",
      "description": "16 bars enter with delay as a time stagger, while height/hue/offset/blur each lay out as a value gradient from first to last; the second beat moves the stagger origin to center and re-lays the pulse peaking there",
      "use": "",
      "video": "https://gimgs.net/motion/media/value-stagger-gradient/video-118e27c93f2b.mp4",
      "poster": "https://gimgs.net/motion/media/value-stagger-gradient/poster-298a8cf16c26.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/value-stagger-gradient.html",
  "json_url": "https://gimgs.net/motion/value-stagger-gradient.json",
  "template_url": "https://gimgs.net/motion/value-stagger-gradient/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/value-stagger-gradient.md",
  "updated_at": "2026-09-24T07:40:33.843Z",
  "prompt": "## When to use\n\nA technique-demo/parametric-capability shot; also works directly as a data/spectrum/equalizer-style UI entrance\n\n## Intention\n\nStagger is usually only used to offset time. This card is about it also being able to offset **value** — the same index decides both \"when it moves\" and \"how much it moves.\" The two beats form a paired experiment: the first beat's origin is at the start (the gradient spreads monotonically left to right), the second beat's origin moves to center (the gradient spreads symmetrically around the axis). What the viewer sees isn't two animations, it's the same recipe with one parameter swapped. The on-screen code caption changes in sync with the corresponding stagger call, spelling it out.\n\n## Duration & energy\n\n- Duration: ~5.0s (150f@30fps)\n- Energy: Medium-high (the first beat is a continuous spread, the second beat's center pulse is a clear single peak)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\nstagger 通常只被用来错开时间。这张卡要说的是它还能错开**值**——同一个索引既决定\n\"什么时候动\"，也决定\"动到多少\"。两拍是一组对照实验：第一拍原点在首位（梯度从左到\n右单调铺开），第二拍原点换到中心（梯度以中轴对称铺开）。观众看到的不是两个动画，\n而是同一个配方换了一个参数。屏幕上的代码字幕同步换成对应的 stagger 调用，明说这件事。\n\n## 动效核心\n- 核心工具是一个 8 行的 `staggerVal(i, n, a, b, ease)`：把索引归一化成 `k = i/(n-1)`\n  再 `lerp(k, a, b)`——等价 anime.js 的 `stagger([a, b])` 值模式，可选 ease 让梯度非线性\n- 静态梯度（写在 setup 里，一次算完）：色相 `staggerVal(i,N,200,320)`、\n  最大高度 `staggerVal(i,N,92,32)`——16 根柱天生是一道从蓝到紫、从高到矮的斜坡\n- 动态梯度（每帧用）：位移量 `staggerVal(i,N,46,14)`、模糊量 `staggerVal(i,N,8,2)`\n  ——首位柱要走 46px、糊 8px，末位只走 14px、糊 2px。**同一次入场里每根柱的行程都不同**\n- 第一拍时间错峰：`d = i*0.02`，`e = seg(t, 0.06+d, 0.28+d, E.outCubic)`——从首位起，\n  16 根摊在 0.3 的窗上；`opacity = e`、`filter = blur((1-e)*b0)`、\n  `transform = translateY((1-e)*y0) scaleY(e)`，三者都被同一个 e 驱动\n- 第二拍换原点：`distC = |i - 7.5| / 7.5` 是到中心的归一化距离，\n  `w = seg(t, 0.56 + distC*0.13, +0.18)`——中心柱先动，波向两端扩，这就是 `from:'center'`\n- 第二拍的幅度也是梯度：`amp = lerp(1-distC, 0.06, 0.42)`，中心柱脉冲 42%、末端只有 6%；\n  `pulse = sin(w*π)` 是纯脉冲，同时叠进 `scaleY(e*(1+pulse*amp))` 与\n  `brightness(1+pulse*0.55)`\n- 字幕在 t=0.52 硬切文案，从 `scale: stagger([1, 0.35])  hue: stagger([200, 320])`\n  换成 `pulse: stagger([.06, .42], { from: 'center' })`——正好卡在两拍之间\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 柱数 | N=16（`left: 8+i*5.4%`，宽 3.4%） | 16 根让梯度有足够分档；<8 根梯度读作\"每根都不一样\"而非一道斜坡，>24 根柱间距要重算 |\n| 时间错峰 | `d = i*0.02`（≈3f/根，总 0.3） | 3f 是连绵的铺开；>0.05 会变成逐根点名，梯度的\"整体感\"丢失 |\n| 单柱行程 | 窗 0.22（≈33f，outCubic） | 行程是总错峰的 0.73 倍 → 大部分时间十来根同时在动；行程 < 错峰总量时变成一根接一根 |\n| 高度梯度 | `stagger([92, 32])`px | 首末 2.9 倍差是明确斜坡；比值 <1.5 读不出梯度，>4 末端柱矮到看不见 |\n| 色相梯度 | `stagger([200, 320])` | 120° 跨度（蓝→紫）；跨度 <40° 时色相白给，>200° 会绕回红色让斜坡读作环形 |\n| 位移/模糊梯度 | `stagger([46,14])`px / `stagger([8,2])`px | 这两条是本卡的\"值 stagger\"主证据——把它们改成常量，动效立刻退化成普通的 stagger 入场 |\n| 第二拍原点 | `from:'center'`，`distC*0.13` | 0.13 是从中心到端点的传播延迟（≈4f）；归零则整排同时脉冲，`from` 参数就没有意义了 |\n| 脉冲幅度梯度 | `lerp(1-distC, 0.06, 0.42)` | 中心 42% / 端点 6% 是 7 倍差，波形明显；两端也给大幅度会读作整排一起鼓，中心原点看不出 |\n| 亮度脉冲 | `brightness(1 + pulse*0.55)` | 55% 提亮让脉冲在暗底上可读；这一项没做梯度（全柱同幅），是有意的——太多梯度叠一起观众读不清哪个是主角 |\n\n## 已知坑\n- 第一拍与第二拍首尾相接：末柱入场结束于 `0.28+15*0.02 = 0.58`，而中心柱的脉冲在 0.56\n  就起。加柱数或加大时间错峰会让入场吃进脉冲段，两拍糊成一件事——加柱必须把 0.56 后推\n- 端点柱的第二拍窗口是 `0.69→0.87`，之后到 t=1 有约 0.13（20f）的完全静止。这是给\n  观众看清\"终态梯度\"的时间，不是空白\n- `distC` 的分母 `C=(N-1)/2` 在 N=1 时会除零；`staggerVal` 里已用 `n<=1 ? 0` 兜住，\n  但 `distC` 没有——柱数改成 1 会 NaN\n- 柱位 `left: 8+i*5.4%` 与宽 `3.4%` 是为 N=16 手配的（末柱右缘 8+15*5.4+3.4 = 92.4%）。\n  改柱数必须重算这两个百分比，否则柱会溢出或挤在左半边\n- 字幕是 anime.js 的 API 调用字符串，本质是**技法说明**而不是内容。用在真实成片里\n  要么换成有语义的标题，要么去掉——否则观众读到的是代码\n- 底部的 16 个小圆点（`bottom:17%`，opacity .35）是静态装饰，不参与任何动画；它们的\n  `left` 偏移 `+1.2%` 是与柱宽 3.4% 手对的居中量，改柱宽要一起改\n- 深底（`#0a0b10`）+ 高饱和柱（85%/66%）+ 12px 光晕是一套；浅色主题下光晕与\n  brightness 脉冲都失效，需要改用 saturate 或阴影表达脉冲\n\n## 参考实现\ndemos/ui-entrance/value-stagger-gradient/\n（ValueStaggerGradient.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.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/ui-entrance/value-stagger-gradient/ValueStaggerGradient.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/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.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": 165,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.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/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx =====\n// value-stagger-gradient — Value Stagger 数值梯度（motion-lab 定稿转原生 Remotion）\n// stagger 不只错开时间，还把属性值在 N 个元素上铺成梯度：16 根柱入场时\n// delay=stagger(时间)，同时高度/色相/模糊都是 stagger([from,to]) 的数值梯度；\n// 第二拍换 from:'center'，脉冲幅度以中心为原点重新铺开。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React from 'react';\nimport { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';\n\nexport const VALUE_STAGGER_GRADIENT_DURATION = 150; // 5000ms @30fps\n\n// 数值梯度 util（等价 stagger([a,b]) 的 value 模式）\nconst staggerVal = (i: number, n: number, a: number, b: number, ease?: (x: number) => number) => {\n  let k = n <= 1 ? 0 : i / (n - 1);\n  if (ease) k = ease(k);\n  return lerp(k, a, b);\n};\n\nconst N = 16;\nconst C = (N - 1) / 2;\nconst BARS = Array.from({ length: N }, (_, i) => ({\n  i,\n  hue: staggerVal(i, N, 200, 320), // 数值梯度：色相铺开\n  hMax: staggerVal(i, N, 92, 32), // 数值梯度：高度 1→0.35\n  distC: Math.abs(i - C) / C,\n}));\n\nexport const ValueStaggerGradient: React.FC = () => {\n  const t = useT();\n  return (\n    <DesignStage bg=\"#0a0b10\">\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          background: '#0a0b10',\n          overflow: 'hidden',\n          fontFamily: '\"SF Mono\",Menlo,monospace',\n        }}\n      >\n        {BARS.map(({ i, hue, hMax, distC }) => {\n          // 拍一：时间 stagger（linear from first）× 数值梯度（y/blur 同时铺开）\n          const d = i * 0.02;\n          const e = seg(t, 0.06 + d, 0.28 + d, E.outCubic);\n          const y0 = staggerVal(i, N, 46, 14); // 位移量本身也是梯度\n          const b0 = staggerVal(i, N, 8, 2); // 模糊量梯度\n          // 拍二：from:'center' —— 波与幅度都以中心为原点铺开\n          const w = seg(t, 0.56 + distC * 0.13, 0.74 + distC * 0.13);\n          const pulse = Math.sin(w * Math.PI);\n          const amp = lerp(1 - distC, 0.06, 0.42); // 幅度梯度：中心最大\n          return (\n            <React.Fragment key={i}>\n              <div\n                style={{\n                  position: 'absolute',\n                  bottom: '22%',\n                  left: `${8 + i * 5.4}%`,\n                  width: '3.4%',\n                  height: hMax,\n                  borderRadius: 5,\n                  transformOrigin: '50% 100%',\n                  background: `linear-gradient(180deg,hsl(${hue},85%,66%),hsl(${hue},70%,42%))`,\n                  boxShadow: `0 0 12px hsla(${hue},85%,58%,.25)`,\n                  opacity: e,\n                  filter: `blur(${(1 - e) * b0}px) brightness(${1 + pulse * 0.55})`,\n                  transform: `translateY(${(1 - e) * y0}px) scaleY(${e * (1 + pulse * amp)})`,\n                }}\n              />\n              <div\n                style={{\n                  position: 'absolute',\n                  bottom: '17%',\n                  left: `${8 + i * 5.4 + 1.2}%`,\n                  width: 4,\n                  height: 4,\n                  borderRadius: '50%',\n                  background: `hsl(${hue},70%,55%)`,\n                  opacity: 0.35,\n                }}\n              />\n            </React.Fragment>\n          );\n        })}\n        <div\n          style={{\n            position: 'absolute',\n            left: '50%',\n            top: '9%',\n            transform: 'translateX(-50%)',\n            color: '#5b6480',\n            fontSize: 10,\n            letterSpacing: '1.5px',\n            whiteSpace: 'nowrap',\n            opacity: 0.5 + 0.5 * seg(t, 0.04, 0.12),\n          }}\n        >\n          {t < 0.52\n            ? 'scale: stagger([1, 0.35])  hue: stagger([200, 320])'\n            : \"pulse: stagger([.06, .42], { from: 'center' })\"}\n        </div>\n      </div>\n    </DesignStage>\n  );\n};\n\n\n"
}