{
  "slug": "neon-triple-marquee",
  "title": "Triple neon marquee recap",
  "tagline": "Three rows of opposing neon marquee text (BETTER/FASTER/STRONGER) scroll endlessly, taking turns lighting up, then fade out.",
  "description": "Three rows of opposing neon marquee text (BETTER/FASTER/STRONGER) scroll endlessly, taking turns lighting up, then fade out.",
  "category": "outro",
  "tags": [
    "shotcraft",
    "remotion",
    "outro",
    "typography"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.06,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/neon-triple-marquee/poster-d97172f5ff8e.jpg",
    "video": "https://gimgs.net/motion/media/neon-triple-marquee/video-df8e2cb59d5c.mp4"
  },
  "variants": [
    {
      "key": "neon-triple-marquee",
      "label": "neon-triple-marquee",
      "description": "Three rows of opposing neon marquee recap — hollow-outlined giant BETTER/FASTER/STRONGER text fills the top/middle/bottom of the screen, odd and even rows scroll endlessly at constant speed in opposite directions, the three rows take turns lighting up on a 1/3 phase offset, and the whole group fades out at the end.",
      "use": "",
      "video": "https://gimgs.net/motion/media/neon-triple-marquee/video-df8e2cb59d5c.mp4",
      "poster": "https://gimgs.net/motion/media/neon-triple-marquee/poster-d97172f5ff8e.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/neon-triple-marquee.html",
  "json_url": "https://gimgs.net/motion/neon-triple-marquee.json",
  "template_url": "https://gimgs.net/motion/neon-triple-marquee/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/neon-triple-marquee.md",
  "updated_at": "2026-09-24T07:40:08.995Z",
  "prompt": "## When to use\n\nRecap section for the outro theme words; the \"afterglow\" beat for a three-word slogan (a step down after cel-flash-stomp's slam); music sections with no voiceover\n\n## Intention\n\nOne row of marquee is just web decor. Stack three rows, alternate direction on odd/even rows, and cycle the brightness by phase, and it becomes \"the theme words parading past the viewer.\" The key is **alternating light-up**: at any moment only one row is the neon lead, the rest dim to thin outline backdrops — all three lit at once is a broken sign, taking turns is a recap. The opposing scroll directions (top/bottom rows right, middle row left) create an \"enclosed\" feel: the eye is pinned in the middle as words stream past from both sides, reading as \"these three words never stop.\"\n\n## Duration & energy\n\n- Duration: 4–5s (demo 150f: 10f fade-in + loop body + 20f fade-out)\n- Energy: Medium-high (continuous flow + row-by-row pulsing, no instantaneous impact)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n一行 marquee 只是网页装饰；三行叠起来、奇偶反向、再让亮度按相位轮流\n走，就成了\"主题词在观众眼前列队巡游\"。命门是**明暗轮唱**：任一时刻\n只有一行是霓虹主角，其余压暗成细描边背景——三行全亮是灯牌事故，\n轮流亮才是 recap。反向对滚制造\"包围感\"：上下行向右、中行向左，\n视线被夹在中间，词从两侧源源不断流过，读作\"这三个词说不完\"。\n\n## 动效核心\n- 无缝回绕：副本按固定 unitW 等距绝对定位，`(frame*speed) % unitW`\n  取模平移——匀速 linear（marquee 类是库内 linear 豁免项）\n- 空心描边字：`-webkit-text-stroke`，color transparent；亮度脉冲\n  同时驱动三件事——描边宽 5→8px、opacity 0.35→1、双层 drop-shadow\n  辉光（8→30px + 20→70px）——只调 opacity 不调辉光读不出\"霓虹通电\"\n- 轮唱相位：周期 45f，行间相位差 15f（周期/3），余弦软包络且每行\n  只占周期前 1/3 亮、后 2/3 归零——保证\"一亮俩暗\"永远成立\n- 三行配色蓝/粉/琥珀（#4d9fff/#ff4dd2/#ffb347），深底 #050308；\n  中行反向且略快（14/17/14 px/f）——同速对滚读作镜像，破掉\n- 词尾加分隔点（•）撑词间距，unitW = 估宽 + 1.3 倍字号空隙\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 字号/行距 | 300px，行距 350px 排满 1080 | 行间留白 >80px 读作三条横幅广告而非一面词墙 |\n| 滚速 | 14/17/14 px/f，中行反向 | >25 读不清词只见光带；三行同速同向读作整版平移 |\n| 脉冲周期 | 45f，相位差 15f | 周期 <30f 三行抢闪读作故障；>70f 轮唱感断掉 |\n| 亮度区间 | opacity 0.35→1 + stroke 5→8px | 暗态 <0.25 暗行消失，\"三行\"结构丢了 |\n| 辉光 | 双层 drop-shadow，亮态 30/70px | 单层或 <20px 读作普通描边字不读作霓虹 |\n| 淡入淡出 | 入 10f / 出 20f 整组 opacity | 循环体无始无终，掐头去尾全靠这两端；硬切进出读作素材没铺满 |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- 与 page-waterfall-wall 分工：那卡是页面截图纵向瀑布做\"内容体量感\"，\n  本卡是文字行横向对滚做\"主题词余韵\"——素材是词不是页，位置在片尾不在中段\n- 与 outro-group-photo-launch 分工：全家福是峰值终镜（元素合影+发布会收场），\n  本卡是终镜前的 recap 铺垫或低成本备选终镜，两者可串（本卡→淡出→全家福）不可叠\n- 与 cel-flash-stomp 同吃\"三连词口号\"文案：那卡逐词砸（高能宣告），\n  本卡整组滚（余韵复读）——同一组词全片最多各用一次，先砸后滚顺序不可反\n- 副本数按 `ceil(1920/unitW)+3` 留冗余——短词（≤4 字符）unitW 小，\n  副本不足会在回绕瞬间露右侧空缺\n- 实战品牌色版：三行可统一品牌色靠明暗区分，但辉光对比须 ≥ demo 的\n  0.35→1 幅度，不然轮唱读不出来\n\n## 参考实现\ndemos/outro/neon-triple-marquee/\n（NeonTripleMarquee.tsx）\n原片出处：clickup-30 61–64.5s\n\n## Reference implementation (Remotion)\n\n- demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/outro/neon-triple-marquee/NeonTripleMarquee.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/outro/neon-triple-marquee/NeonTripleMarquee.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/outro/neon-triple-marquee/NeonTripleMarquee.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/outro/neon-triple-marquee/NeonTripleMarquee.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": 152,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/outro/neon-triple-marquee/NeonTripleMarquee.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/outro/neon-triple-marquee/NeonTripleMarquee.tsx =====\n// neon-triple-marquee —— clickup-30 61–64.5s\n// 三行霓虹描边巨字 BETTER/FASTER/STRONGER 满屏排布，\n// 奇偶行反向匀速无限滚动（marquee 允许 linear），\n// 三行明暗轮流脉冲（一行亮时其余压暗），结尾整组淡出。\nimport React from 'react';\nimport { AbsoluteFill, useCurrentFrame, interpolate } from 'remotion';\n\nconst FONT = '\"Arial Black\", \"Helvetica Neue\", Arial, sans-serif';\n\n// 单行 marquee：副本按固定 unitW 等距绝对定位，平移取模 → 回绕无缝。\nconst MarqueeRow: React.FC<{\n  word: string;\n  color: string;\n  dir: 1 | -1;\n  speed: number; // px/frame\n  frame: number;\n  y: number;\n  fontSize: number;\n  brightness: number; // 0..1 脉冲亮度\n}> = ({ word, color, dir, speed, frame, y, fontSize, brightness }) => {\n  // 逐字符估宽（上限估计），保证槽位 >= 实际文本宽，副本不重叠\n  const est = word.length * fontSize * 0.92;\n  const unitW = est + fontSize * 1.3; // 词间空隙（含分隔点）\n  const copies = Math.ceil(1920 / unitW) + 3;\n  const offsetRaw = (frame * speed) % unitW;\n  const offset = dir === 1 ? -unitW * 1.5 + offsetRaw : -unitW * 0.5 - offsetRaw;\n\n  const strokeW = 5 + brightness * 3;\n\n  return (\n    <div\n      style={{\n        position: 'absolute',\n        top: y,\n        left: 0,\n        width: '100%',\n        height: fontSize * 1.1,\n        overflow: 'visible',\n        transform: `translateX(${offset}px)`,\n        fontFamily: FONT,\n        fontWeight: 900,\n        fontSize,\n        letterSpacing: 4,\n        lineHeight: 1,\n        color: 'transparent',\n        WebkitTextStroke: `${strokeW}px ${color}`,\n        opacity: 0.35 + brightness * 0.65,\n        filter: `drop-shadow(0 0 ${8 + brightness * 22}px ${color}) drop-shadow(0 0 ${\n          20 + brightness * 50\n        }px ${color})`,\n      }}\n    >\n      {Array.from({ length: copies }).map((_, i) => (\n        <span\n          key={i}\n          style={{ position: 'absolute', left: i * unitW, top: 0, whiteSpace: 'nowrap' }}\n        >\n          {word}\n          <span style={{ display: 'inline-block', transform: `translateX(${fontSize * 0.4}px)` }}>\n            {'•'}\n          </span>\n        </span>\n      ))}\n    </div>\n  );\n};\n\nexport const NeonTripleMarquee: React.FC = () => {\n  const f = useCurrentFrame();\n\n  // 三行轮流脉冲：周期 45 帧，每行占 1/3 相位，余弦软脉冲\n  const pulse = (idx: number) => {\n    const period = 45;\n    const phase = (((f - idx * (period / 3)) % period) + period) % period;\n    const t = phase / period;\n    if (t < 1 / 3) return 0.5 - 0.5 * Math.cos(t * 3 * Math.PI * 2);\n    return 0;\n  };\n\n  // 入场淡入 + 结尾整组淡出\n  const groupOpacity = interpolate(f, [0, 10, 128, 148], [0, 1, 1, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  const rows: { word: string; color: string; dir: 1 | -1; speed: number }[] = [\n    { word: 'BETTER', color: '#4d9fff', dir: 1, speed: 14 },\n    { word: 'FASTER', color: '#ff4dd2', dir: -1, speed: 17 },\n    { word: 'STRONGER', color: '#ffb347', dir: 1, speed: 14 },\n  ];\n\n  return (\n    <AbsoluteFill style={{ background: '#050308', overflow: 'hidden' }}>\n      <div style={{ opacity: groupOpacity, position: 'absolute', inset: 0 }}>\n        {rows.map((r, i) => (\n          <MarqueeRow\n            key={r.word}\n            word={r.word}\n            color={r.color}\n            dir={r.dir}\n            speed={r.speed}\n            frame={f}\n            y={40 + i * 350}\n            fontSize={300}\n            brightness={pulse(i)}\n          />\n        ))}\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}