Motion · UI entrance

Wall reveal: three moves

Three whole-wall batch entrances — bento cell-by-cell light-up, grid wave flip, and blueprint draw-on; all reveal in place with no displacement, complementing the fly-in, displacement-type deck-deal-flyin.

1920×108016:930 fps5.06s · 152 framesremotionLicense: Apache-2.0#shotcraft#remotion#ui-entrance

Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player

Variants

bento-light-upbento-light-up · Cell-by-cell light-up
A dark 3×2 bento wall sits dimmed and waiting, an amber flowing light traces each cell's outline in turn as its content brightens and lifts; once all are lit the camera pushes in slowly to settle
A gravitas/dark-toned brand passage; a steady-paced feature overview
grid-wave-flipgrid-wave-flip · Wave flip
A 3×3 gray-backed card wall flips 180° in place along a diagonal wavefront on rotateX, revealing its front content; the last card overshoots and bounces back
A brisk feature wall; a fast passage that sweeps the whole screen in one second
wireframe-draw-onwireframe-draw-on · Blueprint draw-on
The interface is first drawn in groups as a fine SVG-line blueprint, then a glowing amber vertical line sweeps left to right, and wherever it passes the wireframe solidifies into the real interface
A "from design to product / from idea to product" narrative; the product's first full reveal

When to use

The overall reveal of a feature wall/card wall/whole-page UI; passages where content is already in place and needs to "appear" rather than "pour in"

Intention

Until now, the library's only answer for "how does a whole wall of content enter" was deck-deal-flyin — high-energy, displacement-type, "things pouring in." But not every wall should pour: a brand passage wants gravitas, a feature wall wants briskness, a "from design to product" passage wants to show process. These three moves share a common trait — non-displacement batch entrance — elements reveal in place, the layout is already correct from frame one, and what the viewer watches is "a wavefront of state-change sweeping across a wall," not "objects flying." Ask first when choosing: should this wall "pour in" or "light up"? The former goes to deck-deal-flyin, the latter to this card.

Duration & energy

  • Duration: Single style ~4.3–5s (A 150f / B 130f / C 150f @30fps, including an establishing hold and a still close)
  • Energy: Medium

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

"一墙内容怎么出场"库内此前只有 deck-deal-flyin 一个答案——高能量、位移型、 "东西涌进来"。但不是每面墙都该涌:品牌段要庄重、功能墙要轻快、 "从设计到实物"要讲工序。这三式共同点是非位移批量入场——元素原地显形, 版式从第一帧就成立,观众看的是"状态变化的波前扫过一面墙"而不是"物体飞行"。 选型先问一句:这面墙该"涌进来"还是"亮起来"?前者去 deck-deal-flyin,后者进本卡。

三式选型

| 式 | 做法 | 适用 | |----|------|------| | A bento-light-up 逐格点亮 | 暗场 3×2 bento 墙压暗待命,强调色流光逐格描边一圈、内容随即提亮上浮,全亮后镜头缓推收住 | 内容庄重/暗场品牌段;节奏沉稳的功能总览 | | B grid-wave-flip 波浪翻面 | 3×3 灰背卡墙沿对角线波前依次 rotateX 原位翻转 180°,翻出正面内容,尾张过冲回弹 | 轻快功能墙;一秒扫完全屏的快节奏段 | | C wireframe-draw-on 蓝图描线成形 | 界面先以 SVG 细线蓝图分组描画,再一条强调色发光竖线左→右扫过,扫过处线框实体化成真实界面 | "从设计到实物/从想法到产品"叙事;产品首次整体亮相 |

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | A 节拍 | hold 20f → 每格间隔 12f 依次激活(六格 ~96f 全亮)→ scale 1→1.04 缓推 25f(bezier 0.33,0,0.2,1)→ 静止收尾 | 12f 级联是"逐格可辨"与"整墙拖沓"的平衡点;格数增多可压到 8–10f,但间隔均匀本身就是这一式的庄重感来源,别学发牌加速 | | A 单格 | 暗态 opacity 0.18 + translateY 20px;SVG rect pathLength=100 + dashoffset 描边 8f(强调色 #e8b45e 4px + drop-shadow 辉光);描边过半(+6f)内容 back-out(0.3,1.4,0.5,1) 弹出 8f 提亮归位;描完 +12f 流光退火到 0.4 常亮细边 | 描边与内容弹出要接力不同步——同帧起读作整格闪现;点亮中段辉光脉冲用 lit·(1−lit)·4 抛物线,峰值幅度按格 seed 微差(正弦哈希,禁真随机) | | B 波前 | hold 20f → delay=(row+col)*6f 对角线步进,每张 rotateX 0→180° 翻 14f bezier(0.35,0,0.25,1);尾张冲到 190° 再 8f 弹回 180° | (row+col) 是对角波的灵魂,换 row 或 col 单向扫立刻平庸;只给尾张过冲——张张过冲读作整墙果冻 | | B 翻面结构 | 共享 perspective 1200px 容器(整墙一个消失点),每张双面卡 preserve-3d:灰背朝外 + 正面预转 rotateX(180°),两面都 backfaceVisibility hidden | perspective 加在墙容器不是单卡上,否则九个消失点各翻各的;翻到 90° 最薄处叠一道 4px 白色高光线(不随卡旋转、贴格位随角度 45°→135° 从上缘扫到下缘,opacity=1−|angle−90|/45),阴影随 sin(angle) 抬起再收回 | | C 描线 | hold 20f → 分组错峰各 30f 描完:侧栏 20f 起/顶栏 30f 起/6 卡 40+i*3f 起/折线图 52f 起;SVG pathLength=1 + dashoffset 1→0,描边 #8f8f8d 2.5px round | 分组错峰是"手在画"的感觉来源,全部同帧起笔读作打印机;卡内占位线再按 +3f 细错峰,长短用 seed 哈希微差 | | C 实体化 | 88–118f 实体层 clip-path inset(0 right% 0 0) 从左向右展开 30f,bezier(0.55,0,0.25,1);前沿跟 4px 强调色竖线(#ffc46b + 双层辉光 boxShadow),86–92f 淡入、112–120f 淡出 | 竖线位置与 clip 前沿必须同一个 scan 值驱动,异步读作穿帮;描线段与扫描段之间留 ~6f 喘息(82→88f),线框画完立刻扫读作赶工 | | 静止收尾 | 三式动作全部落定后真静止 ≥15f(A 在缓推结束后、B 在尾张回弹后、C 在扫描线淡出后起算) | R1 呼吸判例:静止期一切属性锁死,包括辉光/阴影/高光残留 |

已知坑

  • **demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿,

首次实战须以真实素材回验**

  • A/C 的强调色描边/扫描线属"单点高质量光效"范畴(Q4 三约束内合规),但一支片

整墙点亮 ≤1 次(P4 手法去重)——A 和 C 同片共用即算两次整墙光效,选一

  • B 式翻面后的正面实战要接真实截图:backface 侧预留高清纹理(Q2),翻转中

正面会以斜角被看,低倍截图先按审美准则 Q2 的高分辨率栅格化技法处理再上

  • C 式线框几何必须与实体层布局逐像素对位(demo 里按 FakeDashboard A 手排),

换素材要重排线框——对不齐则扫描线扫过瞬间元素跳位,一眼穿帮

  • 三式都是"原位显形",别混入位移入场(如 B 翻面时再加 translateY 飞入)——

位移需求去 deck-deal-flyin,杂交会两头不像

  • A 式暗态 opacity 0.18 依赖暗底(#2a2a28)成立,亮底下压暗读作褪色故障

参考实现

demos/ui-entrance/wall-reveal-moves/ (BentoLightUp.tsx / GridWaveFlip.tsx / WireframeDrawOn.tsx)

实现状态:三式均有参考实现与 Gallery 动态样片。

Reference implementation (Remotion)

  • demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx
  • demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx
  • demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.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.

  1. Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
  2. Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
  3. 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/wall-reveal-moves.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx, demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx, demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("wall-reveal-moves")

Prompt

Use the video-shotcraft shot recipe "Wall reveal: three moves" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/wall-reveal-moves.json
Raw TSX: https://gimgs.net/motion/wall-reveal-moves/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx, demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx, demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.06s

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).

JSON · Markdown · llms.txt

14,891 chars · Apache-2.0Open raw template ↗
// ===== demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { Card, TitleBlock } from '../../_fixtures/Fixtures';

// bento-light-up:暗场里 3×2 bento 墙压暗待命,随节拍逐格点亮——
// 边框流光先描一圈(琥珀),格内内容随后提亮上浮弹出;六格全亮后整体微推收住。
// 节拍:0–20 建立(hold) → 每格间隔 12f 依次激活(描边 8f + 内容弹出 8f)
//       → ~96f 全亮 → 96–121 scale 1→1.04 缓推 → 121–150 静止收尾。

const BG = '#2a2a28';
const AMBER = '#e8b45e';
const FIRST = 20; // 首格激活帧
const GAP = 12; // 格间节拍
const CELL_W = 480;
const CELL_H = 330;
const GUT = 44;
const LEFT = (1920 - (CELL_W * 3 + GUT * 2)) / 2;
const TOP = (1080 - (CELL_H * 2 + GUT)) / 2 + 30;

const Cell: React.FC<{ i: number; frame: number }> = ({ i, frame }) => {
  const start = FIRST + i * GAP;
  const col = i % 3;
  const row = Math.floor(i / 3);
  const x = LEFT + col * (CELL_W + GUT);
  const y = TOP + row * (CELL_H + GUT);

  // ① 边框流光:pathLength=100 的 dashoffset 描边,8f 走完一圈
  const draw = interpolate(frame, [start, start + 8], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  // 描完后流光退火:琥珀亮边 → 弱化成常亮细边
  const strokeFade = interpolate(frame, [start + 12, start + 26], [1, 0.4], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.quad),
  });

  // ② 内容提亮 + 上浮弹出:描边过半后接力,8f 弹出(back-out 带一点过冲)
  const lit = interpolate(frame, [start + 6, start + 14], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const rise = interpolate(frame, [start + 6, start + 14], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.3, 1.4, 0.5, 1),
  });
  const opacity = 0.18 + 0.82 * lit;
  const ty = 20 * (1 - rise);
  // seed 正弦哈希做每格微差(点亮瞬间的辉光强度略有随机感)
  const jitter = Math.abs(Math.sin(i * 127.3) * 43758.5453 % 1);
  const glow = lit * (1 - lit) * 4 * (14 + jitter * 6); // 点亮中段最亮的辉光脉冲

  return (
    <div style={{ position: 'absolute', left: x, top: y, width: CELL_W, height: CELL_H }}>
      {/* 暗态卡 + 点亮后的内容(同一张卡,靠 opacity/translateY 提亮浮出) */}
      <div
        style={{
          opacity,
          transform: `translateY(${ty}px)`,
          boxShadow: lit > 0.5 ? `0 0 ${glow}px rgba(232,180,94,${0.35 * lit * (1 - lit) * 4})` : 'none',
          borderRadius: 14,
        }}
      >
        <Card w={CELL_W} h={CELL_H} seed={i + 1} />
      </div>
      {/* 边框流光:SVG rect 描边一圈 */}
      {draw > 0 && (
        <svg
          width={CELL_W}
          height={CELL_H}
          viewBox={`0 0 ${CELL_W} ${CELL_H}`}
          style={{ position: 'absolute', left: 0, top: ty, overflow: 'visible' }}
        >
          <rect
            x={2}
            y={2}
            width={CELL_W - 4}
            height={CELL_H - 4}
            rx={14}
            fill="none"
            stroke={AMBER}
            strokeWidth={4}
            pathLength={100}
            strokeDasharray={100}
            strokeDashoffset={100 * (1 - draw)}
            opacity={strokeFade}
            style={{ filter: `drop-shadow(0 0 ${6 + jitter * 4}px ${AMBER})` }}
          />
        </svg>
      )}
    </div>
  );
};

export const BentoLightUp: React.FC = () => {
  const frame = useCurrentFrame();

  // ③ 六格全亮(~96f)后整体缓推 scale 1→1.04,25f 收住,之后真静止
  const push = interpolate(frame, [96, 121], [1, 1.04], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.33, 0, 0.2, 1),
  });

  // 标题随首格点亮微微提亮,交代场景
  const titleLit = interpolate(frame, [FIRST, FIRST + 20], [0.25, 0.75], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.quad),
  });

  return (
    <AbsoluteFill style={{ background: BG, overflow: 'hidden' }}>
      <div
        style={{
          position: 'absolute',
          inset: 0,
          transform: `scale(${push})`,
          transformOrigin: '960px 540px',
        }}
      >
        <div style={{ position: 'absolute', left: LEFT, top: TOP - 110, opacity: titleLit, filter: 'invert(1)' }}>
          <TitleBlock text="Features" size={64} />
        </div>
        {Array.from({ length: 6 }).map((_, i) => (
          <Cell key={i} i={i} frame={frame} />
        ))}
      </div>
    </AbsoluteFill>
  );
};


// ===== demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { Card, G } from '../../_fixtures/Fixtures';

// grid-wave-flip〔入场退场〕:3×3 灰背卡片墙沿对角线波前依次 rotateX 翻转 180°,
// 灰背翻成正面内容卡;波浪约一秒扫完全屏,最后一张落定带轻微过冲。
// 结构:hold 20f → delay=(row+col)*6f、每张 14f bezier(0.35,0,0.25,1) → 尾张过冲回弹 → 静止收尾。

const COLS = 3;
const ROWS = 3;
const CELL_W = 520;
const CELL_H = 280;
const GAP = 36;
const HOLD = 20; // 开头建立
const STAGGER = 6; // 对角线波前步进
const FLIP = 14; // 单张翻转时长

const flipEase = Easing.bezier(0.35, 0, 0.25, 1);

// 单张卡的翻转角度:普通卡 0→180;最后一张(波前最末)过冲到 ~190 再回落 180
const angleAt = (frame: number, row: number, col: number): number => {
  const delay = HOLD + (row + col) * STAGGER;
  const isLast = row === ROWS - 1 && col === COLS - 1;
  if (!isLast) {
    return interpolate(frame, [delay, delay + FLIP], [0, 180], {
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp',
      easing: flipEase,
    });
  }
  // 过冲:14f 冲到 190°,再 8f 弹回 180°
  const main = interpolate(frame, [delay, delay + FLIP], [0, 190], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: flipEase,
  });
  const settle = interpolate(frame, [delay + FLIP, delay + FLIP + 8], [0, -10], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  return main + settle;
};

export const GridWaveFlip: React.FC = () => {
  const frame = useCurrentFrame();
  const wallW = COLS * CELL_W + (COLS - 1) * GAP;
  const wallH = ROWS * CELL_H + (ROWS - 1) * GAP;

  return (
    <AbsoluteFill
      style={{
        background: G.bg,
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      {/* 卡片墙:共享 perspective 1200px 容器,整墙一个消失点 */}
      <div
        style={{
          width: wallW,
          height: wallH,
          perspective: 1200,
          perspectiveOrigin: '50% 50%',
          display: 'grid',
          gridTemplateColumns: `repeat(${COLS}, ${CELL_W}px)`,
          gridTemplateRows: `repeat(${ROWS}, ${CELL_H}px)`,
          gap: GAP,
        }}
      >
        {Array.from({ length: ROWS * COLS }).map((_, i) => {
          const row = Math.floor(i / COLS);
          const col = i % COLS;
          const angle = angleAt(frame, row, col);
          // 高光线:翻到 90°(最薄处)时最亮,位置随角度从上缘扫向下缘
          const glow = Math.max(0, 1 - Math.abs(angle - 90) / 45);
          const glowTop = interpolate(angle, [45, 135], [8, 92], {
            extrapolateLeft: 'clamp',
            extrapolateRight: 'clamp',
          });
          // 翻转中抬起阴影,落定后收回
          const lift = Math.sin(Math.min(Math.max(angle, 0), 180) * (Math.PI / 180));
          return (
            <div key={i} style={{ width: CELL_W, height: CELL_H, position: 'relative' }}>
              <div
                style={{
                  position: 'absolute',
                  inset: 0,
                  transformStyle: 'preserve-3d',
                  transform: `rotateX(${angle}deg)`,
                  boxShadow: `0 ${4 + lift * 22}px ${10 + lift * 40}px rgba(0,0,0,${0.08 + lift * 0.16})`,
                  borderRadius: 14,
                }}
              >
                {/* 灰背面(初始朝外) */}
                <div
                  style={{
                    position: 'absolute',
                    inset: 0,
                    backfaceVisibility: 'hidden',
                    WebkitBackfaceVisibility: 'hidden',
                    background: '#c7c7c5',
                    border: `2px solid ${G.bar}`,
                    borderRadius: 14,
                    boxSizing: 'border-box',
                    display: 'flex',
                    justifyContent: 'center',
                    alignItems: 'center',
                  }}
                >
                  {/* 背面只留一个哑光圆点标记,强调"灰背" */}
                  <div
                    style={{
                      width: 44,
                      height: 44,
                      borderRadius: 22,
                      background: G.mid,
                      opacity: 0.55,
                    }}
                  />
                </div>
                {/* 正面内容卡(预转 180°,翻过来正好朝外) */}
                <div
                  style={{
                    position: 'absolute',
                    inset: 0,
                    backfaceVisibility: 'hidden',
                    WebkitBackfaceVisibility: 'hidden',
                    transform: 'rotateX(180deg)',
                    borderRadius: 14,
                  }}
                >
                  <Card w={CELL_W} h={CELL_H} seed={i + 1} style={{ width: '100%', height: '100%' }} />
                </div>
              </div>
              {/* 最薄处高光线:不随卡旋转,贴在格位上随角度纵向移动 */}
              {glow > 0.01 && (
                <div
                  style={{
                    position: 'absolute',
                    left: '4%',
                    width: '92%',
                    top: `${glowTop}%`,
                    height: 4,
                    borderRadius: 2,
                    background:
                      'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 50%, rgba(255,255,255,0) 100%)',
                    boxShadow: '0 0 14px rgba(255,255,255,0.8)',
                    opacity: glow,
                    pointerEvents: 'none',
                  }}
                />
              )}
            </div>
          );
        })}
      </div>
    </AbsoluteFill>
  );
};


// ===== demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx =====
import React from 'react';
import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion';
import { FakeDashboard, G } from '../../_fixtures/Fixtures';

// wireframe-draw-on〔入场退场〕:界面先以蓝图细线描画成形(stroke-dashoffset
// 分组错峰),随后一条发光竖线从左向右扫过,扫过之处线框实体化为真实灰阶界面。
// 节拍:0–20 hold 空底 → 20–82 分组描线 → 88–118 光线扫描实体化 → 118–150 hold。

// FakeDashboard variant A 的几何(1920×1080):
// 侧栏 220 宽;顶栏 x220–1920 h72;内容区 padding36 gap28,3×2 卡片
// 卡 w524 h454,x = 256/808/1360,y = 108/590。
const CARD_W = 524;
const CARD_H = 454;
const CARD_X = [256, 808, 1360];
const CARD_Y = [108, 590];

const seedFrac = (i: number) => {
  const v = Math.sin(i * 127.3) * 43758.5453;
  return v - Math.floor(v);
};

export const WireframeDrawOn: React.FC = () => {
  const frame = useCurrentFrame();

  // 描线进度:每组 30f 画完,start 错峰
  const draw = (start: number) =>
    interpolate(frame, [start, start + 30], [0, 1], {
      extrapolateLeft: 'clamp',
      extrapolateRight: 'clamp',
      easing: Easing.bezier(0.4, 0, 0.3, 1),
    });

  const tSide = draw(20); // 侧栏
  const tTop = draw(30); // 顶栏
  const tCards = Array.from({ length: 6 }, (_, i) => draw(40 + i * 3)); // 卡片错峰
  const tChart = draw(52); // 折线图

  // 实体化扫描:88–118 从左向右
  const scan = interpolate(frame, [88, 118], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
    easing: Easing.bezier(0.55, 0, 0.25, 1),
  });
  const scanX = scan * 1920;
  const lineOpacity = interpolate(frame, [86, 92, 112, 120], [0, 1, 1, 0], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });

  // 描线小工具:pathLength=1 + dashoffset 从 1 → 0
  const stroke = (t: number): React.SVGAttributes<SVGElement> => ({
    fill: 'none',
    stroke: G.mid,
    strokeWidth: 2.5,
    pathLength: 1,
    strokeDasharray: 1,
    strokeDashoffset: 1 - t,
    strokeLinecap: 'round',
    opacity: t > 0 ? 1 : 0,
  });

  // 折线图数据(seed 正弦哈希,落在卡 (1,1) 内部)
  const chartCardX = CARD_X[1];
  const chartCardY = CARD_Y[1];
  const chartPts = Array.from({ length: 8 }, (_, i) => {
    const px = chartCardX + 50 + (i * (CARD_W - 100)) / 7;
    const py = chartCardY + 130 + seedFrac(i + 3) * 230;
    return `${px},${py}`;
  }).join(' ');

  return (
    <AbsoluteFill style={{ background: G.bg, overflow: 'hidden' }}>
      {/* —— 线框层(蓝图描线) —— */}
      <svg
        width={1920}
        height={1080}
        viewBox="0 0 1920 1080"
        style={{ position: 'absolute', inset: 0 }}
      >
        {/* 侧栏 */}
        <rect x={4} y={4} width={216} height={1072} rx={2} {...stroke(tSide)} />
        <rect x={26} y={30} width={40} height={40} rx={10} {...stroke(draw(24))} />
        {Array.from({ length: 7 }).map((_, i) => {
          const w = (216 - 44) * (0.6 + ((i * 29) % 35) / 100);
          return (
            <line
              key={`s${i}`}
              x1={26}
              y1={94 + i * 30}
              x2={26 + w}
              y2={94 + i * 30}
              {...stroke(draw(26 + i * 2))}
            />
          );
        })}

        {/* 顶栏 */}
        <line x1={220} y1={72} x2={1916} y2={72} {...stroke(tTop)} />
        <rect x={252} y={27} width={180} height={18} rx={9} {...stroke(draw(34))} />
        <rect x={1476} y={18} width={320} height={36} rx={18} {...stroke(draw(36))} />
        <circle cx={1834} cy={36} r={18} {...stroke(draw(38))} />

        {/* 3×2 卡片:轮廓 + 内部占位线 */}
        {tCards.map((t, i) => {
          const cx = CARD_X[i % 3];
          const cy = CARD_Y[Math.floor(i / 3)];
          const titleW = (CARD_W - 36) * (0.45 + (((i + 1) * 37) % 40) / 100);
          return (
            <g key={`c${i}`}>
              <rect x={cx} y={cy} width={CARD_W} height={CARD_H} rx={14} {...stroke(t)} />
              <rect x={cx + 18} y={cy + 18} width={titleW} height={16} rx={8} {...stroke(draw(46 + i * 3))} />
              <line x1={cx + 18} y1={cy + 62} x2={cx + 18 + (CARD_W - 36) * 0.82} y2={cy + 62} {...stroke(draw(49 + i * 3))} />
              <line x1={cx + 18} y1={cy + 88} x2={cx + 18 + (CARD_W - 36) * 0.62} y2={cy + 88} {...stroke(draw(51 + i * 3))} />
              <circle cx={cx + 31} cy={cy + CARD_H - 31} r={13} {...stroke(draw(53 + i * 3))} />
            </g>
          );
        })}

        {/* 折线图(画在中列下排卡内) */}
        <polyline points={chartPts} {...stroke(tChart)} strokeWidth={3} />
      </svg>

      {/* —— 实体层:clip-path inset 从左向右展开 —— */}
      <div
        style={{
          position: 'absolute',
          inset: 0,
          clipPath: `inset(0 ${(1 - scan) * 100}% 0 0)`,
        }}
      >
        <FakeDashboard variant="A" />
      </div>

      {/* —— 4px 发光扫描竖线(琥珀微光) —— */}
      <div
        style={{
          position: 'absolute',
          left: scanX - 2,
          top: 0,
          width: 4,
          height: 1080,
          background: '#ffc46b',
          opacity: lineOpacity,
          boxShadow:
            '0 0 18px 6px rgba(232, 163, 61, 0.65), 0 0 60px 18px rgba(232, 163, 61, 0.28)',
        }}
      />
    </AbsoluteFill>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx, demos/ui-entrance/wall-reveal-moves/GridWaveFlip.tsx, demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

More in this category