{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sponsor-reactbits",
  "title": "remocn ✕ react-bits — New sponsor",
  "description": "remocn demo composition \"remocn ✕ react-bits — New sponsor\" — installs the full Remotion composition. Generated with AI from the prompt in demos/sponsor-reactbits/prompt.md.",
  "dependencies": [
    "@paper-design/shaders-react",
    "@remotion/google-fonts",
    "@remotion/transitions",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/focus-pull.json",
    "https://remocn.dev/r/push-through.json",
    "https://remocn.dev/r/shader-dithering.json"
  ],
  "files": [
    {
      "path": "src/demos/sponsor-reactbits/index.tsx",
      "content": "import React from \"react\";\nimport { AbsoluteFill, Easing, Img, interpolate, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { demoAsset } from \"@/lib/demo-assets\";\nimport { TransitionSeries, linearTiming } from \"@remotion/transitions\";\nimport { loadFont as loadSans } from \"@remotion/google-fonts/Manrope\";\n\nimport { ShaderDithering } from \"@/components/remocn/shader-dithering\";\nimport { pushThrough } from \"@/components/remocn/push-through\";\nimport { focusPull } from \"@/components/remocn/focus-pull\";\n\n// react-bits speaks in motion, we speak in Manrope — normal weight only.\nconst { fontFamily: SANS_FAMILY } = loadSans(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\"],\n});\n\nconst SANS = `${SANS_FAMILY}, -apple-system, BlinkMacSystemFont, sans-serif`;\n\n// Palette lifted from reactbits.dev itself: #060010 is the landing body\n// background, #5227FF the signature accent used across the whole library.\nconst BG = \"#060010\";\nconst VIOLET = \"#5227FF\";\nconst INK = \"#fafafa\";\nconst MUTED = \"rgba(250,250,250,0.6)\";\nconst FAINT = \"rgba(250,250,250,0.45)\";\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_HOOK = 70; //    per-word kinetic hook\nconst S_REVEAL = 95; //  react-bits wordmark reveal + tagline\nconst S_COUNT = 60; //   130 count-up\nconst S_LOCKUP = 125; // Remocn ✕ react bits + reactbits.dev\n\n// The tagline, cut into rhythmic beats — each word is its own hard-cut scene.\nconst BEAT_WORDS = [\n  \"Free\",\n  \"Customizable\",\n  \"Animations for\",\n  \"Text\",\n  \"Backgrounds\",\n  \"UI\",\n];\nconst BEAT_DURS = [14, 14, 16, 12, 12, 22]; // accelerating into the tail\nconst S_BEATS = BEAT_DURS.reduce((a, b) => a + b, 0);\n\nconst T_PT = 18; // push-through\nconst T_FP = 18; // focus-pull\n\nexport const SPONSOR_REACTBITS_DURATION =\n  S_HOOK + S_REVEAL + S_COUNT + S_BEATS + S_LOCKUP - (T_PT + T_FP + T_PT);\n\n// ---------------------------------------------------------------------------\n// Slow camera drift — every scene rides a barely-there push-in so no frame\n// is ever static. durationInFrames is Sequence-scoped inside TransitionSeries.\n// ---------------------------------------------------------------------------\nconst Drift: React.FC<{ children: React.ReactNode; grow?: number }> = ({\n  children,\n  grow = 0.035,\n}) => {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n  const scale = interpolate(frame, [0, durationInFrames], [1, 1 + grow]);\n  return (\n    <AbsoluteFill style={{ transform: `scale(${scale})` }}>\n      {children}\n    </AbsoluteFill>\n  );\n};\n\n// ---------------------------------------------------------------------------\n// Per-word rise — the react-bits \"Split Text\" register, rebuilt frame-driven:\n// each word resolves out of blur while rising onto the baseline.\n// ---------------------------------------------------------------------------\nconst WordsRise: React.FC<{\n  text: string;\n  fontSize: number;\n  color?: string;\n  delay?: number;\n  stagger?: number;\n}> = ({ text, fontSize, color = INK, delay = 0, stagger = 4 }) => {\n  const frame = useCurrentFrame();\n  const ease = Easing.bezier(0.2, 0.8, 0.2, 1);\n  return (\n    <span\n      style={{\n        fontFamily: SANS,\n        fontWeight: 400,\n        fontSize,\n        color,\n        lineHeight: 1.25,\n      }}\n    >\n      {text.split(\" \").map((word, i) => {\n        const local = frame - delay - i * stagger;\n        const p = interpolate(local, [0, 24], [0, 1], {\n          ...clampOpts,\n          easing: ease,\n        });\n        return (\n          <span\n            key={i}\n            style={{\n              display: \"inline-block\",\n              whiteSpace: \"pre\",\n              opacity: p,\n              transform: `translateY(${(1 - p) * 30}px)`,\n              filter: p < 1 ? `blur(${(1 - p) * 10}px)` : undefined,\n            }}\n          >\n            {word}\n            {i < text.split(\" \").length - 1 ? \" \" : \"\"}\n          </span>\n        );\n      })}\n    </span>\n  );\n};\n\n// ===========================================================================\n// Scene 1 — Hook. The sponsor line lands word by word.\n// ===========================================================================\nconst HookScene: React.FC = () => (\n  <Drift>\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        padding: \"0 120px\",\n        textAlign: \"center\",\n      }}\n    >\n      <WordsRise text=\"Say hello to my new sponsor\" fontSize={58} />\n    </AbsoluteFill>\n  </Drift>\n);\n\n// ===========================================================================\n// Scene 2 — Reveal. The react-bits wordmark resolves out of depth, then the\n// tagline settles beneath it.\n// ===========================================================================\nconst RevealScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const p = interpolate(frame, [4, 34], [0, 1], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n  const scale = interpolate(p, [0, 1], [1.14, 1]);\n  return (\n    <Drift>\n      <AbsoluteFill\n        style={{\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          gap: 36,\n        }}\n      >\n        <Img\n          src={demoAsset(\"reactbits-logo.svg\")}\n          style={{\n            width: 660,\n            opacity: p,\n            transform: `scale(${scale})`,\n            filter: p < 1 ? `blur(${(1 - p) * 22}px)` : undefined,\n          }}\n        />\n        <div style={{ textAlign: \"center\" }}>\n          <WordsRise\n            text=\"The largest & most creative library of animated React components\"\n            fontSize={25}\n            color={MUTED}\n            delay={30}\n            stagger={2}\n          />\n        </div>\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Scene 3 — Proof. The count-up react-bits is famous for, turned on itself.\n// The plain number, nothing else.\n// ===========================================================================\nconst CountScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const count = interpolate(frame, [4, 44], [0, 130], {\n    ...clampOpts,\n    easing: Easing.out(Easing.poly(3)),\n  });\n  return (\n    <Drift>\n      <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n        <span\n          style={{\n            fontFamily: SANS,\n            fontWeight: 400,\n            fontSize: 200,\n            lineHeight: 1,\n            color: INK,\n            fontVariantNumeric: \"tabular-nums\",\n          }}\n        >\n          {Math.round(count)}\n        </span>\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Scene 4 — Beats. The tagline cut into hard-cut word slots, switching on a\n// rhythm: Free / Customizable / animations for / text / backgrounds / UI.\n// Each word snaps in fast and holds until the next cut — no cross-fades.\n// ===========================================================================\nconst BEAT_STARTS = BEAT_DURS.map((_, i) =>\n  BEAT_DURS.slice(0, i).reduce((a, b) => a + b, 0),\n);\n\nconst BeatsScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  let active = 0;\n  for (let i = 0; i < BEAT_STARTS.length; i++) {\n    if (frame >= BEAT_STARTS[i]) active = i;\n  }\n  const local = frame - BEAT_STARTS[active];\n  const p = interpolate(local, [0, 8], [0, 1], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n  const scale = interpolate(p, [0, 1], [1.14, 1]);\n  return (\n    <Drift>\n      <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n        <span\n          style={{\n            fontFamily: SANS,\n            fontWeight: 400,\n            fontSize: 96,\n            lineHeight: 1.1,\n            color: INK,\n            opacity: p,\n            transform: `scale(${scale})`,\n            filter: p < 1 ? `blur(${(1 - p) * 8}px)` : undefined,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {BEAT_WORDS[active]}\n        </span>\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Scene 4 — Lockup. Remocn ✕ react bits assembles, reactbits.dev closes.\n// ===========================================================================\nconst LockupScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const ease = Easing.out(Easing.cubic);\n\n  // The two names glide in from opposite sides.\n  const sideP = interpolate(frame, [6, 30], [0, 1], {\n    ...clampOpts,\n    easing: ease,\n  });\n  const leftX = (1 - sideP) * -70;\n  const rightX = (1 - sideP) * 70;\n  const sideBlur = (1 - sideP) * 12;\n\n  // The cross springs in once the sides have almost settled.\n  const cross = spring({\n    frame: frame - 26,\n    fps,\n    config: { damping: 12, stiffness: 160, mass: 0.8 },\n  });\n  const crossOpacity = interpolate(frame, [26, 38], [0, 1], clampOpts);\n\n  return (\n    <Drift grow={0.05}>\n      <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n        <div style={{ display: \"flex\", alignItems: \"center\", gap: 40 }}>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 400,\n              fontSize: 66,\n              color: INK,\n              opacity: sideP,\n              transform: `translateX(${leftX}px)`,\n              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            Remocn\n          </span>\n          <span\n            style={{\n              fontFamily: SANS,\n              fontWeight: 400,\n              fontSize: 44,\n              color: FAINT,\n              opacity: crossOpacity,\n              transform: `scale(${interpolate(cross, [0, 1], [0.4, 1])})`,\n            }}\n          >\n            ✕\n          </span>\n          <Img\n            src={demoAsset(\"reactbits-logo.svg\")}\n            style={{\n              height: 58,\n              opacity: sideP,\n              transform: `translateX(${rightX}px)`,\n              filter: sideBlur > 0.2 ? `blur(${sideBlur}px)` : undefined,\n            }}\n          />\n        </div>\n      </AbsoluteFill>\n    </Drift>\n  );\n};\n\n// ===========================================================================\n// Composition root. One persistent dithering shader carries the whole video —\n// the paper.design warp/4x4 preset recolored into the react-bits register\n// (#060010 body, #5227FF accent), pushed back by a vignette so it textures\n// the dark instead of competing with it.\n// ===========================================================================\nexport const SponsorReactbitsDemo: React.FC = () => {\n  return (\n    <AbsoluteFill style={{ background: BG }}>\n      {/* Persistent shader backdrop — dithering / warp / 4x4. */}\n      <ShaderDithering\n        speed={0.55}\n        colorBack={BG}\n        colorFront={VIOLET}\n        shape=\"warp\"\n        type=\"4x4\"\n        size={2.5}\n      />\n      {/* Vignette scrim — keeps the dither a texture, not a subject. */}\n      <AbsoluteFill\n        style={{\n          background:\n            \"radial-gradient(120% 120% at 50% 42%, rgba(4,0,12,0.66) 0%, rgba(4,0,12,0.93) 100%)\",\n        }}\n      />\n\n      <TransitionSeries>\n        {/* 1 — Hook */}\n        <TransitionSeries.Sequence durationInFrames={S_HOOK}>\n          <HookScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_PT })}\n          presentation={pushThrough()}\n        />\n\n        {/* 2 — react-bits reveal */}\n        <TransitionSeries.Sequence durationInFrames={S_REVEAL}>\n          <RevealScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_FP })}\n          presentation={focusPull()}\n        />\n\n        {/* 3 — 130 count-up */}\n        <TransitionSeries.Sequence durationInFrames={S_COUNT}>\n          <CountScene />\n        </TransitionSeries.Sequence>\n\n        {/* 4 — Tagline word beats (hard cut in, hard cuts inside) */}\n        <TransitionSeries.Sequence durationInFrames={S_BEATS}>\n          <BeatsScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_PT })}\n          presentation={pushThrough()}\n        />\n\n        {/* 5 — Lockup + URL */}\n        <TransitionSeries.Sequence durationInFrames={S_LOCKUP}>\n          <LockupScene />\n        </TransitionSeries.Sequence>\n      </TransitionSeries>\n    </AbsoluteFill>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/sponsor-reactbits/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-reactbits/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nMake a cinematic sponsor announcement for react-bits. Use their real brand colors (the near-black background and that signature violet) inside a warping paper.design dither shader that stays on screen the whole video with a slow camera drift, and set everything in Manrope. Start with \"Say hello to my new sponsor\" landing word by word like their own split-text effect, then reveal the react-bits wordmark resolving out of depth with the tagline settling under it. Show a quick 130 count-up as proof (their own animation, no plus sign), then cut through the tagline word by word — free, customizable, animations for text, backgrounds, UI. Close with the Remocn x react-bits lockup sliding together from both sides. Use remocn's push-through and focus-pull transitions for the scene changes.\n",
      "type": "registry:file",
      "target": "demos/sponsor-reactbits/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { SponsorReactbitsDemo } from \"@/demos/sponsor-reactbits\";\n  <Composition id=\"sponsor-reactbits\" component={SponsorReactbitsDemo} durationInFrames={386} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render sponsor-reactbits out/sponsor-reactbits.mp4 --gl=angle",
  "type": "registry:block"
}