{
  "slug": "radial-wave",
  "title": "Radial wave dot grid",
  "tagline": "A 17×9 grid of dots lights up in a wave based on distance from a source point, each dot overshooting to 1.5x scale then settling.",
  "description": "A 17×9 grid of dots lights up in a wave based on distance from a source point, each dot overshooting to 1.5x scale then settling.",
  "category": "ui-entrance",
  "tags": [
    "shotcraft",
    "remotion",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.3,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/radial-wave/poster-f41461d1f4f8.jpg",
    "video": "https://gimgs.net/motion/media/radial-wave/video-b69dea556874.mp4"
  },
  "variants": [
    {
      "key": "radial-wave",
      "label": "radial-wave",
      "description": "A 17×9 grid of dots lights up in a staggered wave based on Euclidean distance from a source point, each dot scaling up to an overshoot of 1.5x before settling to steady light; once the first wave finishes sweeping through, a second bright-blue pulse contracts back inward from the outer ring to the center.",
      "use": "",
      "video": "https://gimgs.net/motion/media/radial-wave/video-b69dea556874.mp4",
      "poster": "https://gimgs.net/motion/media/radial-wave/poster-f41461d1f4f8.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/radial-wave.html",
  "json_url": "https://gimgs.net/motion/radial-wave.json",
  "template_url": "https://gimgs.net/motion/radial-wave/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/radial-wave.md",
  "updated_at": "2026-09-24T07:40:16.702Z",
  "prompt": "## When to use\n\nA \"system power-on\" beat for a product/brand opening; also used as an establishing shot for data grids, node maps, or coverage-area narratives\n\n## Intention\n\nRewrites the static fact of \"many dots existing at once\" into the dynamic fact of \"energy spreading from one source to everyone.\" What the viewer sees isn't a dot grid, it's a power-on moment: the center lights first, brightness pushes outward like a wave, and once it's done the whole grid holds steady, laying the groundwork for whatever content comes next. The second, reverse wave is an acknowledgment — telling the viewer this system responds.\n\n## Duration & energy\n\n- Duration: About 3.8s (114f@30fps)\n- Energy: Medium-high (one burst at the start, settling into a low-energy steady-lit base after the wavefront passes)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n把\"很多点同时存在\"这件静态事实，改写成\"能量从一个源头扩散到全体\"的动态事实。观众\n看到的不是一张点阵图，而是一次通电：中心先亮，亮度像水波推过去，走完之后整张阵列\n留在常亮态——为下一拍要放的内容铺好底。第二道反向波是回执，告诉观众这个系统会回应。\n\n## 动效核心\n- 阵列 17×9 共 153 点，dot 用百分比定位（`left:(c+0.5)/COLS`），因此改变行列数不需要\n  重算坐标；每点预存归一化距离 `dist = hypot(c-cx, r-cy) / maxD`（maxD = hypot(8,4)）\n- 第一道波：`seg(t, dist*0.35, dist*0.35+0.18, E.outCubic)`——起点由距离线性推迟（最外\n  圈延后 0.35），单点自己的行程恒定 0.18；波前是\"起点错峰\"而不是变速\n- 每点的 scale 不是简单 0→1，而是 `w1*(1+0.5*sin(w1*π))`：中途鼓到约 1.5 再收回 1，\n  正弦包络让过冲发生在行程正中而非落位时\n- 第二道波：`seg(t, 0.62+(1-dist)*0.25, +0.15)` 把偏移量反号（外圈先动），取\n  `sin(w2*π)` 做纯脉冲，叠加 0.8 的 scale 增量与 0.25 的透明度增量，走完不留状态\n- 脉冲峰值处（`pulse2 > 0.3`）硬切成 `#b9f2ff` 并挂 `0 0 12px #7fd8ff` 光晕——只有第二\n  道波带发光，第一道波全程是 `#6c8cff` 常色，两道波在视觉上不会混成一件事\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 阵列规模 | COLS=17 / ROWS=9（153 点） | 点数决定波前的\"分辨率\"；<8 列时距离分档太粗，波读作三四圈同心环在跳 |\n| 波速系数 | `dist*0.35` | 这个 0.35 就是波速倒数：调大波走得慢、拖尾长；>0.5 时最外圈要等到全片过半才亮 |\n| 单点行程 | 0.18（≈21f） | 决定波前的厚度；<0.1 波前锐利像扫描线，>0.3 全场几乎同时亮，扩散感消失 |\n| 过冲幅度 | `1+0.5*sin(w1*π)` → 峰值 1.5 | 系数 0.5 是\"弹一下\"的量；去掉正弦项就是平淡的 0→1 淡入，>0.8 点会互相咬边 |\n| 第二道波 | 起点 `0.62+(1-dist)*0.25`，窗 0.15，幅度 0.8 | 0.62 是两波之间的呼吸；提前到 0.5 以下会和第一道波尾巴叠在一起读作一团乱闪 |\n| 常亮底 | `opacity = 0.25 + w1*0.5 + pulse2*0.25` | 0.25 是未点亮点的存在感；调到 0 则波前之外一片黑，阵列的\"规模\"交代不出来 |\n| 高亮阈值 | `pulse2 > 0.3` 切色 + 光晕 | 阈值越低发光段越长；这是硬切不是渐变，阈值贴近 0 时会看到明显的开关跳变 |\n\n## 已知坑\n- 波源写死在几何中心（`cx=(COLS-1)/2, cy=(ROWS-1)/2`）。要改成从角落或某个\"节点\"起波，\n  必须同时改 cx/cy 与 maxD，否则归一化距离 >1，最外圈的起点会被推到 t=1 之外永不点亮\n- 末帧不完全静止：中心点（dist=0）的第二道波窗是 0.87→1.02，t=1 时才走到 87%，\n  `pulse2≈0.41` 仍在高亮。要做循环或干净收尾，得把第二道波起点压到 0.85 以内\n- dot 的 `width:10px` 与 `margin:-5px` 是绑定的一对（靠负 margin 居中），改点径必须同步改\n  margin，否则整阵列会朝右下偏半个点径\n- 颜色只有两档（常色 `#6c8cff` / 峰值 `#b9f2ff`），换品牌色要改这两处加光晕色共三处；\n  背景 `#0a0b10` 是发光的前提，白底上这套亮度脉冲基本看不见\n\n## 参考实现\ndemos/ui-entrance/radial-wave/\n（RadialWave.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/ui-entrance/radial-wave/RadialWave.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/radial-wave/RadialWave.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/radial-wave/RadialWave.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/radial-wave/RadialWave.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/radial-wave/RadialWave.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": 129,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/radial-wave/RadialWave.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/radial-wave/RadialWave.tsx =====\n// radial-wave — Grid Radial Wave 点阵涟漪（motion-lab 定稿转原生 Remotion）\n// 17×9 圆点阵列，波从中心向外按欧氏距离 stagger 扩散：每点 scale 0→1.5→1 +\n// 亮度脉冲，波前过后留下常亮点阵；第二道波反向收拢。offset = k·dist(cell, origin)。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React from 'react';\nimport { DesignStage, E, seg, useT } from '../../_fixtures/Motion';\n\nexport const RADIAL_WAVE_DURATION = 114; // 3800ms @30fps\n\nconst COLS = 17;\nconst ROWS = 9;\nconst CX = (COLS - 1) / 2;\nconst CY = (ROWS - 1) / 2;\nconst MAX_D = Math.hypot(CX, CY);\n\nconst DOTS = Array.from({ length: ROWS * COLS }, (_, i) => {\n  const r = Math.floor(i / COLS);\n  const c = i % COLS;\n  return {\n    left: `${((c + 0.5) / COLS) * 100}%`,\n    top: `${((r + 0.5) / ROWS) * 100}%`,\n    dist: Math.hypot(c - CX, r - CY) / MAX_D,\n  };\n});\n\nexport const RadialWave: React.FC = () => {\n  const t = useT();\n  return (\n    <DesignStage bg=\"#0a0b10\">\n      {DOTS.map(({ left, top, dist }, i) => {\n        // 第一道波：扩散点亮；第二道波：反向脉冲\n        const w1 = seg(t, dist * 0.35, dist * 0.35 + 0.18, E.outCubic);\n        const w2 = seg(t, 0.62 + (1 - dist) * 0.25, 0.62 + (1 - dist) * 0.25 + 0.15);\n        const pulse2 = Math.sin(w2 * Math.PI);\n        const s = w1 * (1 + 0.5 * Math.sin(w1 * Math.PI)) + pulse2 * 0.8;\n        return (\n          <div\n            key={i}\n            style={{\n              position: 'absolute',\n              width: 10,\n              height: 10,\n              borderRadius: '50%',\n              left,\n              top,\n              margin: -5,\n              background: pulse2 > 0.3 ? '#b9f2ff' : '#6c8cff',\n              transform: `scale(${s})`,\n              opacity: 0.25 + w1 * 0.5 + pulse2 * 0.25,\n              boxShadow: pulse2 > 0.3 ? '0 0 12px #7fd8ff' : 'none',\n            }}\n          />\n        );\n      })}\n    </DesignStage>\n  );\n};\n\n\n"
}