{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sponsor-ln",
  "title": "remocn ✕ LN — New sponsor",
  "description": "remocn demo composition \"remocn ✕ LN — New sponsor\" — installs the full Remotion composition. Generated with AI from the prompt in demos/sponsor-ln/prompt.md.",
  "dependencies": [
    "@remotion/gif",
    "@remotion/google-fonts",
    "@remotion/transitions",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/backdrop.json",
    "https://remocn.dev/r/soft-blur-in.json"
  ],
  "files": [
    {
      "path": "src/demos/sponsor-ln/index.tsx",
      "content": "import React from \"react\";\nimport { AbsoluteFill, Easing, Img, interpolate, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { demoAsset } from \"@/lib/demo-assets\";\nimport {\n  TransitionSeries,\n  linearTiming,\n  type TransitionPresentation,\n  type TransitionPresentationComponentProps,\n} from \"@remotion/transitions\";\nimport { Gif } from \"@remotion/gif\";\nimport { loadFont as loadSans } from \"@remotion/google-fonts/Manrope\";\n\nimport { Backdrop } from \"@/components/remocn/backdrop\";\nimport { SoftBlurIn } from \"@/components/remocn/soft-blur-in\";\n\n// Bind the sans font to the CSS variable the remocn typography components read.\nconst { fontFamily: SANS_FAMILY } = loadSans(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\", \"600\", \"700\", \"800\"],\n});\n\nconst SANS =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nconst INK = \"#fafafa\";\nconst FAINT = \"rgba(250,250,250,0.5)\";\nconst ACCENT = \"#a78bfa\";\n\nconst clampOpts = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\n// ---------------------------------------------------------------------------\n// Scene timings (frames @ 30fps). Transitions overlap.\n// ---------------------------------------------------------------------------\nconst S_INTRO = 60; //  centered animated emoji\nconst S_HOOK = 72; //   text animation\nconst S_MAIN = 100; //  sponsor lockup\n\nconst T_X = 16; //  crossfade\nconst T_ZB = 18; // zoom-blur\n\nexport const SPONSOR_LN_DURATION = S_INTRO + S_HOOK + S_MAIN - (T_ZB + T_X);\n\n// ===========================================================================\n// Scene 1 — Intro. A single animated emoji, centered, springing in with a\n// gentle float.\n// ===========================================================================\nconst IntroScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const s = spring({\n    frame,\n    fps,\n    config: { damping: 12, stiffness: 150, mass: 0.9 },\n  });\n  const scale = interpolate(s, [0, 1], [0.4, 1]);\n  const float = Math.sin(frame / 14) * 8;\n  const size = 260;\n  return (\n    <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n      <div\n        style={{\n          width: size,\n          height: size,\n          opacity: s,\n          transform: `translateY(${float}px) scale(${scale})`,\n          filter: \"drop-shadow(0 22px 44px rgba(0,0,0,0.5))\",\n        }}\n      >\n        <Gif\n          src={demoAsset(\"emoji.gif\")}\n          width={size}\n          height={size}\n          fit=\"contain\"\n        />\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 2 — Hook. Text animation via remocn's soft-blur-in typography.\n// ===========================================================================\nconst HookScene: React.FC = () => (\n  <AbsoluteFill style={{ padding: \"0 90px\" }}>\n    <SoftBlurIn\n      text=\"Say hello my new sponsor\"\n      fontSize={56}\n      fontWeight={600}\n      color={INK}\n      blur={14}\n    />\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 3 — Main. The sponsor block on the logo, choreographed:\n//   1. The LN avatar + name pop in, centered on screen, and hold briefly.\n//   2. The (avatar + name) group slides to the right while, in sync, the\n//      Remocn wordmark pushes in from the left and a cross appears between them.\n// Implemented with absolute positioning so the group can travel from centre to\n// its final slot deterministically.\n// ===========================================================================\nconst AVATAR_SIZE = 168; // DOM (entrance) size — the avatar first appears at this\nconst AVATAR_FINAL_SIZE = 84; // shrinks to this once settled inline with the text\nconst FINAL_SCALE = AVATAR_FINAL_SIZE / AVATAR_SIZE;\n\n// Final-layout horizontal centres (canvas is 1280 wide → centre at 640).\nconst REMOCN_X = 470; // centre of the \"Remocn\" wordmark\n// Right edge of \"Remocn\" as rendered at fontSize 76 / weight 700 (measured).\nconst REMOCN_RIGHT = 611;\nconst AVATAR_X = 800; // centre of the avatar\nconst NAME_GAP = 28; // gap between the avatar's right edge and the \"LN\" name\n// The cross sits evenly between Remocn's right edge and the avatar's FINAL left edge.\nconst CROSS_X = Math.round(\n  (REMOCN_RIGHT + (AVATAR_X - AVATAR_FINAL_SIZE / 2)) / 2,\n);\nconst ROW_Y = 350; // vertical centre of the row\n\n// Frame the group starts sliding right (and Remocn / cross start entering).\nconst SPLIT_AT = 34;\n\nconst AvatarVisual: React.FC = () => (\n  <div\n    style={{ position: \"relative\", width: AVATAR_SIZE, height: AVATAR_SIZE }}\n  >\n    {/* Soft accent halo behind the portrait. */}\n    <div\n      style={{\n        position: \"absolute\",\n        inset: -18,\n        borderRadius: \"50%\",\n        background: `radial-gradient(circle, ${ACCENT}55 0%, transparent 68%)`,\n      }}\n    />\n    <Img\n      src={demoAsset(\"ln.jpg\")}\n      style={{\n        position: \"relative\",\n        width: AVATAR_SIZE,\n        height: AVATAR_SIZE,\n        borderRadius: \"50%\",\n        objectFit: \"cover\",\n        border: \"3px solid rgba(250,250,250,0.9)\",\n        boxShadow: \"0 24px 60px rgba(0,0,0,0.55)\",\n      }}\n    />\n  </div>\n);\n\nconst MainScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n\n  // 1 — Avatar + name pop in, centered.\n  const pop = spring({\n    frame,\n    fps,\n    config: { damping: 13, stiffness: 150, mass: 0.9 },\n  });\n  const popScale = interpolate(pop, [0, 1], [0.6, 1]);\n\n  // 2 — The group slides from screen centre (640) to its final slot (AVATAR_X).\n  const slide = spring({\n    frame: frame - SPLIT_AT,\n    fps,\n    config: { damping: 16, stiffness: 110, mass: 1 },\n  });\n  const groupX = interpolate(slide, [0, 1], [640 - AVATAR_X, 0]);\n  // As it slides right, the avatar shrinks from full size down to the inline\n  // size so it sits exactly on the text row.\n  const shrink = interpolate(slide, [0, 1], [1, FINAL_SCALE], clampOpts);\n  const avatarScale = popScale * shrink;\n  // \"LN\" follows the avatar's (shrinking) right edge at a fixed gap.\n  const nameLeft = AVATAR_X + (AVATAR_SIZE * shrink) / 2 + NAME_GAP;\n\n  // Remocn pushes in from the left, in sync with the slide.\n  const remocnOpacity = interpolate(\n    frame,\n    [SPLIT_AT + 2, SPLIT_AT + 24],\n    [0, 1],\n    clampOpts,\n  );\n  const remocnX = interpolate(frame, [SPLIT_AT + 2, SPLIT_AT + 26], [-36, 0], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n\n  // The cross appears between them, slightly after the split begins.\n  const crossSpring = spring({\n    frame: frame - (SPLIT_AT + 8),\n    fps,\n    config: { damping: 11, stiffness: 170, mass: 0.7 },\n  });\n  const crossOpacity = interpolate(\n    frame,\n    [SPLIT_AT + 8, SPLIT_AT + 20],\n    [0, 1],\n    clampOpts,\n  );\n  const crossScale = interpolate(crossSpring, [0, 1], [0.5, 1]);\n\n  return (\n    <AbsoluteFill>\n      {/* Remocn wordmark — enters from the left. */}\n      <span\n        style={{\n          position: \"absolute\",\n          left: REMOCN_X,\n          top: ROW_Y,\n          transform: `translate(-50%, -50%) translateX(${remocnX}px)`,\n          fontFamily: SANS,\n          fontWeight: 700,\n          fontSize: 76,\n          letterSpacing: \"-0.03em\",\n          color: INK,\n          opacity: remocnOpacity,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        Remocn\n      </span>\n\n      {/* Cross — appears between the two. */}\n      <span\n        style={{\n          position: \"absolute\",\n          left: CROSS_X,\n          top: ROW_Y,\n          transform: `translate(-50%, -50%) scale(${crossScale})`,\n          fontFamily: SANS,\n          fontWeight: 400,\n          fontSize: 52,\n          color: FAINT,\n          opacity: crossOpacity,\n        }}\n      >\n        ✕\n      </span>\n\n      {/* Avatar — pops in centred, then slides right with the name. */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: AVATAR_X,\n          top: ROW_Y,\n          transform: `translate(-50%, -50%) translateX(${groupX}px) scale(${avatarScale})`,\n          opacity: pop,\n        }}\n      >\n        <AvatarVisual />\n      </div>\n\n      {/* Sponsor name — sits to the right of the avatar and travels with it. */}\n      <span\n        style={{\n          position: \"absolute\",\n          left: nameLeft,\n          top: ROW_Y,\n          transform: `translate(0, -50%) translateX(${groupX}px)`,\n          fontFamily: SANS,\n          fontWeight: 800,\n          fontSize: 44,\n          color: INK,\n          letterSpacing: \"-0.01em\",\n          opacity: pop,\n        }}\n      >\n        LN\n      </span>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Transition presentations.\n// ===========================================================================\ntype EmptyProps = Record<string, never>;\n\nconst Crossfade: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n}) => {\n  const entering = presentationDirection === \"entering\";\n  const opacity = entering ? presentationProgress : 1 - presentationProgress;\n  return <AbsoluteFill style={{ opacity }}>{children}</AbsoluteFill>;\n};\nconst crossfade = (): TransitionPresentation<EmptyProps> => ({\n  component: Crossfade,\n  props: {},\n});\n\nconst ZoomBlur: React.FC<TransitionPresentationComponentProps<EmptyProps>> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n}) => {\n  const entering = presentationDirection === \"entering\";\n  const p = presentationProgress;\n  const style: React.CSSProperties = entering\n    ? {\n        opacity: p,\n        transform: `scale(${0.9 + p * 0.1})`,\n        filter: p < 1 ? `blur(${(1 - p) * 16}px)` : undefined,\n      }\n    : {\n        opacity: 1 - p,\n        transform: `scale(${1 + p * 0.12})`,\n        filter: p > 0 ? `blur(${p * 16}px)` : undefined,\n      };\n  return <AbsoluteFill style={style}>{children}</AbsoluteFill>;\n};\nconst zoomBlur = (): TransitionPresentation<EmptyProps> => ({\n  component: ZoomBlur,\n  props: {},\n});\n\n// ===========================================================================\n// Composition root.\n// ===========================================================================\nexport const SponsorLnDemo: React.FC = () => {\n  return (\n    <AbsoluteFill\n      style={\n        {\n          \"--font-geist-sans\": SANS_FAMILY,\n          background: \"#0a0a0a\",\n        } as React.CSSProperties\n      }\n    >\n      {/* Persistent image background for the whole video. */}\n      <Backdrop fill={{ type: \"image\", src: demoAsset(\"bg.png\") }} />\n      {/* Scrim to deepen contrast for the foreground content. */}\n      <AbsoluteFill\n        style={{\n          background:\n            \"radial-gradient(120% 120% at 50% 42%, rgba(10,10,10,0.4) 0%, rgba(10,10,10,0.82) 100%)\",\n        }}\n      />\n\n      <TransitionSeries>\n        {/* 1 — Intro emoji */}\n        <TransitionSeries.Sequence durationInFrames={S_INTRO}>\n          <IntroScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_ZB })}\n          presentation={zoomBlur()}\n        />\n\n        {/* 2 — Hook text */}\n        <TransitionSeries.Sequence durationInFrames={S_HOOK}>\n          <HookScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_X })}\n          presentation={crossfade()}\n        />\n\n        {/* 3 — Main sponsor lockup */}\n        <TransitionSeries.Sequence durationInFrames={S_MAIN}>\n          <MainScene />\n        </TransitionSeries.Sequence>\n      </TransitionSeries>\n    </AbsoluteFill>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/sponsor-ln/index.tsx"
    },
    {
      "path": "src/lib/demo-assets.ts",
      "content": "import { staticFile } from \"remotion\";\n\n// Demo assets are loaded from absolute URLs so a demo installed into another\n// project via the registry renders identically without copying this repo's\n// public/ directory. The same URLs are used on the site and in local renders,\n// which keeps a single code path (network access is required for rendering).\n//\n// Override with REMOTION_DEMO_ASSETS_BASE — only REMOTION_-prefixed env vars\n// reach Remotion compositions (next.config.js additionally inlines it for the\n// site's <Player>). Two override forms:\n//   - \"local\" — serve straight from this repo's public/ via staticFile();\n//     works in Remotion Studio, CLI renders and the Next dev site. Set it in\n//     the gitignored .env so previews never depend on pushed assets.\n//   - any URL — e.g. a local static server (http://127.0.0.1:8123).\nexport const DEMO_ASSETS_BASE =\n  process.env.REMOTION_DEMO_ASSETS_BASE ||\n  \"https://raw.githubusercontent.com/Remocn/remocn-collections/main/public\";\n\nexport const demoAsset = (path: string): string => {\n  const clean = path.replace(/^\\/+/, \"\");\n  if (DEMO_ASSETS_BASE === \"local\") return staticFile(clean);\n  return `${DEMO_ASSETS_BASE}/${clean}`;\n};\n",
      "type": "registry:component",
      "target": "lib/demo-assets.ts"
    },
    {
      "path": "src/demos/sponsor-ln/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nQuick sponsor shoutout video for LN, three scenes over the bg.png backdrop we already have. Open on their emoji springing in centered with a gentle float, then have \"Say hello my new sponsor\" resolve in with remocn's soft blur-in text style. For the main scene, assemble a Remocn x LN lockup — the Remocn wordmark slides in, a cross fades between the two marks, LN's avatar springs into a ringed circle with a soft accent halo around it, and their name settles in underneath. Keep it short and simple, same pattern as our other sponsor spots, built with remocn primitives.\n",
      "type": "registry:file",
      "target": "demos/sponsor-ln/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { SponsorLnDemo } from \"@/demos/sponsor-ln\";\n  <Composition id=\"sponsor-ln\" component={SponsorLnDemo} durationInFrames={198} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render sponsor-ln out/sponsor-ln.mp4",
  "type": "registry:block"
}