Motion · Entrada de UI

Neon frame orbit drop

Neon frame draws in, then the camera arcs across the page as every element drops into place on the same frame.

1920×108016:930 fps4.6s · 138 quadrosremotionLicença: Apache-2.0#shotcraft#remotion#ui-entrance#effects#camera

Fonte: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composição Remotion (React/TSX) — vídeo de prévia, sem player no navegador

When to use

A single-page UI's one-time grand entrance (vs. a tour/reveal-by-section); the hero reveal for dark neon-toned sequences; sister shot to neon-frame-forerun

Intention

A twin variant of neon-frame-forerun: the frame still leads and content still drops in after, but here the camera is not static — it arcs continuously from a left-side view of the page to a right-side view, and while it orbits, every component drops into place from above. The critical rule is simultaneous landing: all components and text rise and land on the same frame, with soft shadows converging and vanishing in sync — this is the grammar of a "unified entrance" (precedent: a staggered-drop version was rejected — "all components and text should drop into place from the air at the same time"). Staggered drops belong to tour shots (graze-face-tour/runway); a one-time single-page reveal must land as one, and the rhythm semantics must not be mixed.

Duration & energy

  • Duration: Frame draw ~0.5s + orbit & simultaneous drop ~2.5s + settle ~1s; full shot 4–4.5s
  • Energy: Medium-high (one big one-time move, static once it settles)

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

意图

neon-frame-forerun 的孪生变体:同样框先行、内容后落,但镜头 不是固定的——从页面左侧视角连续弧线旋转到右侧视角,旋转进行 的同时全部组件从空中贴落。命门是同时贴合:所有组件和文字 同帧起落、同帧贴合、软影同步消失——这是"整体登场"的语法 (判例:错峰版被裁"应该是所有组件和文字同时从空中往下贴合")。 错峰贴落属于巡礼镜(graze-face-tour/runway),一页一次性亮相 必须齐落,节奏语义不能混用。

动效核心

  • 霓虹描框:渐变霓虹框 ~14f 奔画成型(同 neon-frame-forerun)
  • 视角弧线:rotateY +38° → -26° 连续弧线(透视原点 30%→64% 随动

近似相机环绕),贯穿全片

  • 同时贴落:全部元素统一 LAND 时刻(归一化 ~0.52)——同帧离地

悬空、同帧空中下降、同帧贴合;FloatWrap 同形软影(blur/offset/ opacity 随高度)全体同步收敛

  • 背景霓虹管框群衬场,主面板落定后让位

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 贴落同时性 | 全元素同帧起落(零错峰) | 命门;任何错峰都会读成巡礼语法(判例裁决原话见上) | | 视角弧线 | rotateY +38°→-26°、透视原点随动 | 弧度小读作抖动;只转不落=空转 | | 旋转×贴落耦合 | 贴落窗口居中于旋转行程 | 旋转完才落=两段戏;落完还在转=收不了尾 | | 悬浮高度/软影 | 同 graze-face-tour(100–180px、影随高度) | 影不可感=没做(累计判例) | | 描框 | ~14f 奔画 | 同 neon-frame-forerun |

已知坑

  • 参数经占位素材两轮调校通过,非实战定稿,首次实战须以真实素材回验
  • 贴落节奏语义判例(本卡与 graze-face-tour 互为反例):

巡礼镜=错开起点+重叠并行下落;整体登场镜=全体同帧。选错语法 两边都会被裁

  • 与 neon-frame-forerun 分工:那张镜头固定、错峰贴落+页面点亮

三层咬合;本卡镜头旋转+同帧齐落。同段落二选一,别连用(同框 语言重复)

  • 弧线旋转是透视原点近似而非真实 3D 相机环绕,极端角度下框辉光

形变略平——实战若需大角度环绕考虑真 3D 场景

参考实现

demos/ui-entrance/neon-frame-orbit-drop/ (NeonFrameForerunOrbit.tsx) 原片出处:无对应原段

Reference implementation (Remotion)

  • demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx

The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.

Tudo nesta página também é legível por máquina. Aponte seu agente para o JSON ou adicione o servidor MCP do gimgs e peça para buscar templates de motion.

  1. Baixe o JSON — contém a receita do plano (timing, easing, tabela de parâmetros, armadilhas) e o código TSX completo.
  2. Clone o repositório video-shotcraft; o componente importa fixtures de demos/_fixtures e helpers de assets/lib.
  3. Registre o componente como <Composition> em um projeto Remotion e renderize com `npx remotion render`, ou reimplemente a receita no seu próprio engine.
curl -s https://gimgs.net/motion/neon-frame-orbit-drop.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("neon-frame-orbit-drop")

Prompt

Use the video-shotcraft shot recipe "Neon frame orbit drop" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/neon-frame-orbit-drop.json
Raw TSX: https://gimgs.net/motion/neon-frame-orbit-drop/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 4.6s

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

12,717 chars · Apache-2.0Abrir template bruto ↗
// ===== demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx =====
// neon-frame-forerun-orbit v5(批次 14 #1)。用户意见(逐字):
// "这个应该是所有组件和文字同时从空中往下贴合"
// ——单点修正:v4 的错峰贴落改为**所有组件和文字同时**从空中往下贴合
// (同帧起落、同帧贴合,同形软影同步收敛)。判例:整体登场镜=同时贴合,
// 错峰是巡礼镜的语法。其余全部保留:同款霓虹渐变框+灰面板+背景霓虹管
// 框群;镜头视角 rotateY 从左侧(+38°) 连续弧线旋到右侧(-26°)。
import React, { useId } from 'react';
import { AbsoluteFill, useCurrentFrame, interpolate, Easing } from 'remotion';

const easeFall = Easing.bezier(0.5, 0.05, 0.6, 1); // 加速下落、末端软着陆

/* 悬浮 + 同形软影(对标批次11 GrazeFaceTour FloatWrap):
 * h=悬浮高度(px)。本体向左上抬起,原位留模糊压暗同形影,h→0 重合影消失 */
const FloatWrap: React.FC<{ h: number; children: React.ReactNode }> = ({ h, children }) => (
  <div style={{ position: 'relative' }}>
    {h > 1 && (
      <div style={{
        position: 'absolute', inset: 0,
        transform: `translate(${h * 0.26}px, ${h * 0.48}px) scale(${1 + h * 0.0012})`,
        filter: `blur(${2.5 + h * 0.09}px) brightness(0.32) saturate(0.4)`,
        opacity: Math.min(0.4, 0.16 + h * 0.005),
        pointerEvents: 'none',
      }}>{children}</div>
    )}
    <div style={{ transform: `translate(${-h * 0.36}px, ${-h * 0.82}px)` }}>{children}</div>
  </div>
);

/* v5:所有组件和文字**同时**从空中往下贴合——统一 land 时刻 LAND,
 * 同帧起落(t=LAND-FALL)、同帧贴合(t=LAND),软影同步收敛;
 * 各组件仅悬浮高度 H 略有差异(同窗下落,速度随高度自然区分) */
const LAND = 0.52;
const liftOf = (t: number, land: number, H: number) => {
  const FALL = 0.3;
  const p = Math.min(1, Math.max(0, (t - (LAND - FALL)) / FALL));
  return (1 - easeFall(p)) * H;
};

const mulberry32 = (a: number) => () => {
  let t = (a += 0x6d2b79f5);
  t = Math.imul(t ^ (t >>> 15), t | 1);
  t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};

const ink = '#3c3c3a';
const mid = '#9a9a98';
const line = '#e3e3e1';

const PW = 1330;
const PH = 900;
const PL = 1000;

const FRAME_D = `M 0 ${PH / 2} L 0 0 L ${PW} 0 L ${PW} ${PH} L 0 ${PH} Z`;

const Chip: React.FC<{ w: number }> = ({ w }) => (
  <div style={{
    width: w, height: 74, background: '#fdfdfc', border: `2px solid ${line}`,
    borderRadius: 10, padding: '12px 14px', boxSizing: 'border-box',
    display: 'flex', flexDirection: 'column', gap: 9,
  }}>
    <div style={{ height: 11, width: '70%', background: '#b5b5b3', borderRadius: 5 }} />
    <div style={{ height: 9, width: '48%', background: line, borderRadius: 5 }} />
  </div>
);

/* 与 v3 同款灰面板;t=贴落进程,各组件错峰空中贴落(重叠并行) */
const GrayHome: React.FC<{ t?: number }> = ({ t = 1 }) => {
  const L = (land: number, H = 72) => liftOf(t, land, H * 1.7);
  return (
  <div style={{ width: PW, height: PH, background: '#f5f5f4', borderRadius: 6, display: 'flex', overflow: 'hidden', boxSizing: 'border-box' }}>
    <div style={{ width: 290, borderRight: `2px solid ${line}`, padding: '26px 24px', boxSizing: 'border-box' }}>
      <FloatWrap h={L(0.24, 84)}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
          <div style={{ width: 28, height: 28, borderRadius: 8, background: ink }} />
          <div style={{ height: 15, width: 88, background: ink, borderRadius: 7 }} />
        </div>
      </FloatWrap>
      {[76, 56, 96, 110, 66, 60].map((w, i) => (
        <FloatWrap key={i} h={L(0.3 + i * 0.045, 66)}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11, height: 34 }}>
            <div style={{ width: 17, height: 17, borderRadius: 5, background: mid }} />
            <div style={{ height: 10, width: w, background: '#c7c7c5', borderRadius: 5 }} />
          </div>
        </FloatWrap>
      ))}
      <FloatWrap h={L(0.56, 62)}>
        <div style={{ height: 11, width: 70, background: '#b0b0ae', borderRadius: 5, margin: '24px 0 12px' }} />
      </FloatWrap>
      {[104, 126, 96, 118, 88].map((w, i) => (
        <FloatWrap key={i} h={L(0.62 + i * 0.05, 66)}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11, height: 32 }}>
            <div style={{ width: 16, height: 16, borderRadius: 4, background: '#b8b8b6' }} />
            <div style={{ height: 10, width: w, background: '#cfcfcd', borderRadius: 5 }} />
          </div>
        </FloatWrap>
      ))}
    </div>
    <div style={{ flex: 1, padding: '30px 40px', boxSizing: 'border-box' }}>
      <FloatWrap h={L(0.26, 90)}>
        <div style={{ height: 24, width: 130, background: '#565654', borderRadius: 10, marginBottom: 22 }} />
      </FloatWrap>
      <FloatWrap h={L(0.34, 80)}>
        <div style={{ height: 46, border: `2px solid ${line}`, borderRadius: 12, marginBottom: 26, display: 'flex', alignItems: 'center', padding: '0 16px', background: '#f5f5f4' }}>
          <div style={{ width: 17, height: 17, borderRadius: 9, border: `2px solid ${mid}` }} />
          <div style={{ height: 10, width: 250, background: line, borderRadius: 5, marginLeft: 12 }} />
        </div>
      </FloatWrap>
      <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 30 }}>
        {[0, 1, 2, 3, 4, 5, 6].map((i) => (
          <FloatWrap key={i} h={L(0.42 + i * 0.04, 76)}>
            <Chip w={222} />
          </FloatWrap>
        ))}
      </div>
      <FloatWrap h={L(0.72, 60)}>
        <div style={{ display: 'flex', gap: 18, marginBottom: 18 }}>
          {[54, 84, 50, 82].map((w, i) => (
            <div key={i} style={{ height: 11, width: w, background: i === 0 ? '#8a8a88' : line, borderRadius: 5 }} />
          ))}
        </div>
      </FloatWrap>
      {[0, 1, 2, 3].map((i) => (
        <FloatWrap key={i} h={L(0.78 + i * 0.055, 64)}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, height: 46, borderBottom: `2px solid ${line}` }}>
            <div style={{ width: 13, height: 13, borderRadius: 7, background: '#c26a6a' }} />
            <div style={{ height: 11, width: 160 + ((i * 37) % 70), background: '#b6b6b4', borderRadius: 5 }} />
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 7 }}>
              {[0, 1, 2].map((k) => <div key={k} style={{ width: 20, height: 20, borderRadius: 10, background: '#d2d2d0' }} />)}
            </div>
          </div>
        </FloatWrap>
      ))}
    </div>
  </div>
  );
};

type BgFrame = { x: number; y: number; w: number; h: number; hue: string; phase: number; period: number; skew: number };
const rng = mulberry32(20260718);
const HUES = ['#b06af0', '#e879c9', '#f0a35c', '#6a7df0', '#e0679a', '#8a5cf0', '#c06af0'];
const BG_FRAMES: BgFrame[] = Array.from({ length: 18 }).map(() => ({
  x: rng() * 2000 - 120, y: rng() * 1100 - 60,
  w: 160 + rng() * 480, h: 70 + rng() * 220,
  hue: HUES[Math.floor(rng() * HUES.length)],
  phase: rng() * 90, period: 55 + rng() * 70,
  skew: -14 + rng() * 10,
}));

export const NeonFrameForerunOrbit: React.FC = () => {
  const frame = useCurrentFrame();
  // 滤镜/渐变 ID 按实例生成,多实例同场不串引(useId 的 «:» 在 url() 里非法,需清洗)
  const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
  // 开场快速描框(同款左缘中点两头奔画,14 帧成型——样式与 v3 一致)
  const trace = interpolate(frame, [0, 14], [0, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
    easing: Easing.bezier(0.3, 0.1, 0.3, 1),
  });
  // 面板早亮:本变体主角是旋转+贴落,不复刻暗转亮长过程
  const lit = interpolate(frame, [8, 30], [0.25, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
    easing: Easing.bezier(0.35, 0, 0.3, 1),
  });
  const frameLine = interpolate(frame, [96, 130], [1, 0.35], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
  });
  const rimGlow = interpolate(frame, [0, 20, 108, 138], [0.7, 1, 0.75, 0.5], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
  });
  const bgLit = interpolate(frame, [0, 30, 100, 136], [0.3, 1, 0.85, 0.1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
  });
  // 视角弧线:从页面左侧(+38°)连续旋到右侧(-26°),全程不停——
  // ease-in-out 起止柔和、中段持续转动
  const orbit = interpolate(frame, [0, 128], [0, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
    easing: Easing.bezier(0.42, 0.05, 0.32, 1),
  });
  const rotY = 38 - 64 * orbit;             // +38° → -26°
  const rotX = 6 - 2.5 * orbit;
  const rotZ = 3 - 7.5 * orbit;             // 左视角微仰 → 右视角微俯(对齐 v3 落定姿态)
  const scale = 0.9 + 0.1 * Math.sin(orbit * Math.PI) + 0.02 * orbit; // 弧线中段略推近
  // 透视原点随视角横移:镜头从左绕到右
  const pOrigin = 30 + 34 * orbit;
  const headP = trace * (PL / 2);
  // 贴落进程:与旋转同时进行(帧 10–118),错峰起点+重叠下落
  const drop = interpolate(frame, [10, 118], [0, 1], {
    extrapolateLeft: 'clamp', extrapolateRight: 'clamp',
  });

  return (
    <AbsoluteFill style={{ background: '#060509', overflow: 'hidden' }}>
      {/* 背景霓虹管框群(同款) */}
      <svg width={1920} height={1080} style={{ position: 'absolute' }}>
        <defs>
          <filter id={`obgblur-${uid}`} x="-60%" y="-60%" width="220%" height="220%">
            <feGaussianBlur stdDeviation={7} />
          </filter>
        </defs>
        {BG_FRAMES.map((b, i) => {
          const breath = 0.5 + 0.5 * Math.sin(((frame + b.phase) / b.period) * Math.PI * 2);
          const op = bgLit * (0.18 + 0.4 * breath);
          return (
            <g key={i} transform={`translate(${b.x} ${b.y}) skewY(${b.skew * 0.4}) skewX(${b.skew})`}>
              <rect width={b.w} height={b.h} rx={4} fill="none"
                stroke={b.hue} strokeWidth={7} filter={`url(#obgblur-${uid})`} opacity={op * 0.8} />
              <rect width={b.w} height={b.h} rx={4} fill="none"
                stroke={b.hue} strokeWidth={2} opacity={op} />
            </g>
          );
        })}
      </svg>
      {/* 主体:视角弧线旋转中的框+面板 */}
      <div style={{ position: 'absolute', inset: 0, perspective: 1500, perspectiveOrigin: `${pOrigin}% 44%` }}>
        <div style={{
          position: 'absolute', left: (1920 - PW) / 2, top: (1080 - PH) / 2 - 10,
          transform: `scale(${scale}) rotateY(${rotY}deg) rotateX(${rotX}deg) rotateZ(${rotZ}deg)`,
          transformStyle: 'preserve-3d',
        }}>
          <div style={{ opacity: trace > 0.4 ? 1 : 0, filter: `brightness(${Math.max(0.05, lit)})` }}>
            <GrayHome t={drop} />
            <div style={{
              position: 'absolute', inset: 0, borderRadius: 6,
              background: 'linear-gradient(150deg, rgba(30,20,60,0.5), rgba(0,0,0,0.78))',
              opacity: 1 - lit,
            }} />
          </div>
          <div style={{
            position: 'absolute', left: -10, top: -10, width: PW + 20, height: PH + 20,
            borderRadius: 12, opacity: rimGlow * Math.min(1, trace * 1.6),
            boxShadow: '-18px -8px 42px 6px rgba(185,95,240,0.42), 22px 24px 56px 12px rgba(240,150,90,0.30), 0 14px 80px 22px rgba(200,100,220,0.20)',
          }} />
          <svg width={PW + 80} height={PH + 80} viewBox={`-40 -40 ${PW + 80} ${PH + 80}`}
            style={{ position: 'absolute', left: -40, top: -40 }}>
            <defs>
              <linearGradient id={`omainfg-${uid}`} gradientUnits="userSpaceOnUse" x1={0} y1={0} x2={PW} y2={PH}>
                <stop offset="0%" stopColor="#c07af5" />
                <stop offset="38%" stopColor="#e58bd8" />
                <stop offset="72%" stopColor="#f0b06a" />
                <stop offset="100%" stopColor="#e8925c" />
              </linearGradient>
              <filter id={`ofblur-${uid}`} x="-40%" y="-40%" width="180%" height="180%">
                <feGaussianBlur stdDeviation={10} />
              </filter>
              <filter id={`ofblur2-${uid}`} x="-40%" y="-40%" width="180%" height="180%">
                <feGaussianBlur stdDeviation={3} />
              </filter>
            </defs>
            {[1, -1].map((dir) => (
              <g key={dir}>
                <path d={FRAME_D} pathLength={PL} fill="none" stroke={`url(#omainfg-${uid})`}
                  strokeWidth={14} strokeLinecap="butt" filter={`url(#ofblur-${uid})`}
                  strokeDasharray={`${headP} ${PL}`}
                  strokeDashoffset={dir === 1 ? 0 : -(PL - headP)}
                  opacity={0.6 * rimGlow} />
                <path d={FRAME_D} pathLength={PL} fill="none" stroke={`url(#omainfg-${uid})`}
                  strokeWidth={3.5} strokeLinecap="butt"
                  strokeDasharray={`${headP} ${PL}`}
                  strokeDashoffset={dir === 1 ? 0 : -(PL - headP)}
                  opacity={0.95 * frameLine} />
                {trace < 1 && (
                  <path d={FRAME_D} pathLength={PL} fill="none" stroke="#ffffff"
                    strokeWidth={6} strokeLinecap="round" filter={`url(#ofblur2-${uid})`}
                    strokeDasharray={`8 ${PL}`}
                    strokeDashoffset={dir === 1 ? -(Math.max(0, headP - 8)) : -(PL - headP)}
                    opacity={0.95} />
                )}
              </g>
            ))}
          </svg>
        </div>
      </div>
    </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/neon-frame-orbit-drop/NeonFrameForerunOrbit.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
—

Mais nesta categoria