{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "introducing-remocn",
  "title": "remocn — Introducing remocn (shaders cut)",
  "description": "remocn demo composition \"remocn — Introducing remocn (shaders cut)\" — installs the full Remotion composition. Generated with AI from the prompt in demos/introducing-remocn/prompt.md.",
  "dependencies": [
    "@paper-design/shaders-react",
    "@remotion/google-fonts",
    "@remotion/transitions",
    "remotion"
  ],
  "registryDependencies": [
    "https://remocn.dev/r/kinetic-center-build.json",
    "https://remocn.dev/r/line-by-line-slide.json",
    "https://remocn.dev/r/scale-down-fade.json",
    "https://remocn.dev/r/shader-color-panels.json",
    "https://remocn.dev/r/shader-dithering.json",
    "https://remocn.dev/r/shader-god-rays.json",
    "https://remocn.dev/r/shader-mesh-gradient.json",
    "https://remocn.dev/r/shader-metaballs.json",
    "https://remocn.dev/r/shader-simplex-noise.json",
    "https://remocn.dev/r/shader-smoke-ring.json",
    "https://remocn.dev/r/shader-swirl.json",
    "https://remocn.dev/r/shader-voronoi.json",
    "https://remocn.dev/r/shader-warp.json",
    "https://remocn.dev/r/short-slide-right.json"
  ],
  "files": [
    {
      "path": "src/demos/introducing-remocn/index.tsx",
      "content": "import React from \"react\";\nimport { AbsoluteFill, Easing, Img, Sequence, Series, 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 { loadFont as loadSans } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\n\nimport { ScaleDownFade } from \"@/components/remocn/scale-down-fade\";\nimport { ShortSlideRight } from \"@/components/remocn/short-slide-right\";\nimport { KineticCenterBuild } from \"@/components/remocn/kinetic-center-build\";\nimport { LineByLineSlide } from \"@/components/remocn/line-by-line-slide\";\n\nimport { ShaderSimplexNoise } from \"@/components/remocn/shader-simplex-noise\";\nimport { ShaderSwirl } from \"@/components/remocn/shader-swirl\";\nimport { ShaderDithering } from \"@/components/remocn/shader-dithering\";\nimport { ShaderColorPanels } from \"@/components/remocn/shader-color-panels\";\nimport { ShaderWarp } from \"@/components/remocn/shader-warp\";\nimport { ShaderMeshGradient } from \"@/components/remocn/shader-mesh-gradient\";\nimport { ShaderVoronoi } from \"@/components/remocn/shader-voronoi\";\nimport { ShaderMetaballs } from \"@/components/remocn/shader-metaballs\";\nimport { ShaderGodRays } from \"@/components/remocn/shader-god-rays\";\nimport { ShaderSmokeRing } from \"@/components/remocn/shader-smoke-ring\";\n\n// Bind Manrope 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});\nconst { fontFamily: MONO_FAMILY } = loadMono(\"normal\", {\n  subsets: [\"latin\"],\n  weights: [\"400\", \"500\"],\n});\n\nconst SANS =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\nconst MONO = `${MONO_FAMILY}, ui-monospace, SFMono-Regular, monospace`;\n\n// The shipped remocn.dev brand: warm obsidian + one lime accent.\nconst OBSIDIAN = \"#141318\";\nconst INK = \"#f2f2f2\";\nconst MUTED = \"rgba(242,242,242,0.62)\";\nconst FAINT = \"rgba(242,242,242,0.4)\";\nconst LIME = \"#C3E88D\";\n\nconst clampOpts = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\n// ---------------------------------------------------------------------------\n// Scene timings (frames @ 30fps). Transitions overlap.\n// Shader covers run fade-in (12f) → opaque hold (~500ms) → fade-out, so the\n// shader itself gets read as a moment, not a flash.\n// ---------------------------------------------------------------------------\nconst S_PAIN = 208; //     two scale-down-fade pain lines + room for the swirl\nconst S_MEET = 150; //     \"Meet Remocn\"\nconst S_TAGLINE = 76; //   \"Cinematic video components for React\"\nconst S_POS = 96; //       kinetic \"Like shadcn/ui, for video\"\nconst REG_BEAT = 26; //    one fullscreen shader per hard cut\nconst S_VALUE = 104; //    three value lines\nconst S_INSTALL_TITLE = 70; // \"It lands in your repo\"\nconst S_INSTALL_CMD = 176; //  typed command + 3D name rolodex\nconst S_OUTRO = 150; //    smoke ring blooms → mark + wordmark + url\n\n// Paced like the paper-shaders OutroScene: unwind ~34f, hold ~22f, wind ~38f.\nconst T_SWIRL = 104; //    shader-swirl cover (twist 1→0 → hold → 0→1)\nconst T_DITHER = 40; //    lime dither dissolve (held mid-frame)\nconst T_X = 14; //         crossfade\nconst T_BLUR = 16; //      blur crossfade\n\n// ===========================================================================\n// The remocn mark — the real brand asset from remocn.dev/logo.svg,\n// downloaded to public/remocn-logo.svg.\n// ===========================================================================\nconst RemocnMark: React.FC<{ size: number }> = ({ size }) => (\n  <Img\n    src={demoAsset(\"remocn-logo.svg\")}\n    style={{ width: size, height: size, display: \"block\" }}\n  />\n);\n\n// Readability scrim over a backdrop shader.\nconst Scrim: React.FC<{ strength?: number }> = ({ strength = 1 }) => (\n  <AbsoluteFill\n    style={{\n      background: `radial-gradient(120% 120% at 50% 42%, rgba(20,19,24,${\n        0.3 * strength\n      }) 0%, rgba(20,19,24,${0.78 * strength}) 100%)`,\n    }}\n  />\n);\n\n// ===========================================================================\n// Scene 1 — Pain. Two lines land solo on the shared backdrop.\n// ===========================================================================\nconst PainScene: React.FC = () => (\n  <AbsoluteFill>\n    <Sequence durationInFrames={60}>\n      <ScaleDownFade\n        text=\"Every launch needs a video\"\n        fontSize={54}\n        fontWeight={400}\n        color={INK}\n      />\n    </Sequence>\n    <Sequence from={60} durationInFrames={60}>\n      <ScaleDownFade\n        text=\"Yours shouldn't take a week\"\n        fontSize={54}\n        fontWeight={400}\n        color={INK}\n      />\n    </Sequence>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 2 — Meet Remocn. The text itself is static on entry (the swirl\n// cover's exit reveals it with a z-axis scale), then it plays its own exit —\n// fading up and out — so the tagline never overlaps it.\n// ===========================================================================\nconst MeetScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n\n  const exitP = interpolate(\n    frame,\n    [durationInFrames - 18, durationInFrames - 2],\n    [0, 1],\n    { ...clampOpts, easing: Easing.in(Easing.cubic) },\n  );\n\n  return (\n    <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n      <span\n        style={{\n          fontFamily: SANS,\n          fontWeight: 400,\n          fontSize: 76,\n          letterSpacing: \"-0.03em\",\n          color: INK,\n          opacity: 1 - exitP,\n          transform: `translateY(${exitP * -10}px) scale(${1 - exitP * 0.05})`,\n          filter: `blur(${exitP * 6}px)`,\n        }}\n      >\n        Meet Remocn\n      </span>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 3 — Tagline. Enters via short-slide-right only after Meet Remocn\n// has fully exited (hard cut between the scenes, no overlap).\n// ===========================================================================\nconst TaglineScene: React.FC = () => (\n  <AbsoluteFill>\n    <ShortSlideRight\n      text=\"Cinematic video components for React\"\n      fontSize={48}\n      fontWeight={400}\n      color={INK}\n    />\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 4 — Positioning. The mental model assembles word by word.\n// ===========================================================================\nconst PositioningScene: React.FC = () => (\n  <AbsoluteFill>\n    <Sequence from={24}>\n      <KineticCenterBuild\n        text=\"Like shadcn/ui, for video\"\n        fontSize={62}\n        fontWeight={400}\n        color={INK}\n      />\n    </Sequence>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 5 — The registry montage. Six shaders hard-cut, one category each.\n// ===========================================================================\ntype RegistryBeat = { label: string; node: React.ReactNode };\n\nconst REGISTRY_BEATS: RegistryBeat[] = [\n  {\n    label: \"Text animations\",\n    node: (\n      <ShaderColorPanels\n        speed={2}\n        colorBack={OBSIDIAN}\n        colors={[\"#39364d\", \"#55506e\", \"#7d76a0\", \"#a49dcb\"]}\n        density={3}\n        length={1.1}\n      />\n    ),\n  },\n  {\n    label: \"Transitions\",\n    node: (\n      <ShaderWarp\n        speed={2}\n        colors={[\"#141318\", \"#2c2a38\", \"#5b5773\", \"#b9b4d6\"]}\n        swirl={0.6}\n      />\n    ),\n  },\n  {\n    label: \"Backgrounds\",\n    node: (\n      <ShaderMeshGradient\n        speed={2}\n        colors={[\"#141318\", \"#2b2a3a\", \"#565170\", \"#8f88ae\"]}\n        distortion={0.8}\n        swirl={0.2}\n      />\n    ),\n  },\n  {\n    label: \"UI blocks\",\n    node: (\n      <ShaderVoronoi\n        speed={2}\n        colors={[\"#232130\", \"#3d3a52\", \"#6b6590\", \"#a9a2cf\"]}\n        colorGap=\"#0d0c10\"\n        glow={0.1}\n      />\n    ),\n  },\n  {\n    label: \"UI primitives\",\n    node: (\n      <ShaderMetaballs\n        speed={2}\n        colorBack={OBSIDIAN}\n        colors={[\"#4a4661\", \"#7d76a0\", \"#C3E88D\", \"#e6e2f5\"]}\n      />\n    ),\n  },\n  {\n    label: \"Shaders\",\n    node: (\n      <ShaderGodRays\n        speed={2}\n        colorBack=\"#0d0d10\"\n        colorBloom=\"#48522f\"\n        colors={[\"#C3E88D\", \"#e8f2d0\", \"#6a7550\", \"#39412a\"]}\n        intensity={0.9}\n        bloom={0.5}\n      />\n    ),\n  },\n];\n\n// The last beat carries the dither transition on top, so it gets its\n// clean 26 frames back by absorbing the overlap.\nconst S_REGISTRY = REGISTRY_BEATS.length * REG_BEAT + T_DITHER;\n\nconst RegistryLabel: React.FC<{ label: string }> = ({ label }) => {\n  const frame = useCurrentFrame();\n  const opacity = interpolate(frame, [0, 7], [0, 1], clampOpts);\n  const y = interpolate(frame, [0, 8], [10, 0], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n  const blur = interpolate(frame, [0, 7], [6, 0], clampOpts);\n  return (\n    <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n      <span\n        style={{\n          fontFamily: SANS,\n          fontWeight: 400,\n          fontSize: 58,\n          letterSpacing: \"-0.03em\",\n          color: INK,\n          opacity,\n          transform: `translateY(${y}px)`,\n          filter: `blur(${blur}px)`,\n        }}\n      >\n        {label}\n      </span>\n    </AbsoluteFill>\n  );\n};\n\nconst RegistryScene: React.FC = () => (\n  <AbsoluteFill style={{ background: OBSIDIAN }}>\n    <Series>\n      {REGISTRY_BEATS.map((beat, i) => (\n        <Series.Sequence\n          key={beat.label}\n          durationInFrames={\n            i === REGISTRY_BEATS.length - 1 ? REG_BEAT + T_DITHER : REG_BEAT\n          }\n        >\n          <AbsoluteFill style={{ background: OBSIDIAN }}>\n            {beat.node}\n            <Scrim strength={0.7} />\n            <RegistryLabel label={beat.label} />\n          </AbsoluteFill>\n        </Series.Sequence>\n      ))}\n    </Series>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 6 — The numbers. Three claims accumulate as a block.\n// ===========================================================================\nconst ValueScene: React.FC = () => (\n  <AbsoluteFill>\n    <Sequence from={24}>\n      <LineByLineSlide\n        text={\"110+ components\\nOne command to install\\nThe code is yours\"}\n        fontSize={50}\n        fontWeight={400}\n        color={INK}\n      />\n    </Sequence>\n  </AbsoluteFill>\n);\n\n// ===========================================================================\n// Scene 7a — Install title. Its own typographic beat; plays its own exit so\n// the command scene starts on an empty canvas.\n// ===========================================================================\nconst InstallTitleScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n\n  const exitP = interpolate(\n    frame,\n    [durationInFrames - 18, durationInFrames - 2],\n    [0, 1],\n    { ...clampOpts, easing: Easing.in(Easing.cubic) },\n  );\n\n  return (\n    <AbsoluteFill\n      style={{\n        opacity: 1 - exitP,\n        transform: `translateY(${exitP * -10}px) scale(${1 - exitP * 0.05})`,\n        filter: `blur(${exitP * 6}px)`,\n      }}\n    >\n      <ShortSlideRight\n        text=\"It lands in your repo\"\n        fontSize={50}\n        fontWeight={400}\n        color={INK}\n      />\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 7b — Install command. The command types itself; once typing lands,\n// the package name becomes a 3D rolodex and flips through other registry\n// components — any component, same command.\n// ===========================================================================\nconst CMD = \"npx shadcn add @remocn/\";\nconst PKG_NAMES = [\n  \"kinetic-center-build\",\n  \"per-character-rise\",\n  \"shader-warp\",\n  \"command-menu\",\n  \"claude-chat\",\n];\nconst CMD_START = 10;\nconst FLIP_START = 56; // first flip, after the typed command has settled\nconst FLIP_PER = 24; //  one name every 24 frames\nconst FLIP_DUR = 10; //  the 3D flip itself\n\nconst InstallCmdScene: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  const full = CMD + PKG_NAMES[0];\n  const typed = Math.max(\n    0,\n    Math.min(full.length, Math.floor((frame - CMD_START) * 1.5)),\n  );\n  const visible = full.slice(0, typed);\n  const cmdOpacity = interpolate(\n    frame,\n    [CMD_START - 4, CMD_START],\n    [0, 1],\n    clampOpts,\n  );\n  const typingDone = typed >= full.length;\n  const caretOn = typingDone ? Math.floor(frame / 15) % 2 === 0 : true;\n  // The caret leaves just before the rolodex starts spinning.\n  const caretOpacity = interpolate(\n    frame,\n    [FLIP_START - 12, FLIP_START - 4],\n    [1, 0],\n    clampOpts,\n  );\n  const flipping = frame >= FLIP_START - 4;\n\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        opacity: cmdOpacity,\n      }}\n    >\n      <span style={{ fontFamily: MONO, fontSize: 28, color: MUTED }}>\n        <span style={{ color: FAINT }}>$ </span>\n        <span>{visible.slice(0, Math.min(typed, 15))}</span>\n        <span style={{ color: LIME }}>\n          {typed > 15 ? visible.slice(15, Math.min(typed, CMD.length)) : \"\"}\n        </span>\n        <span\n          style={{\n            display: \"inline-block\",\n            width: `${PKG_NAMES[0].length}ch`,\n            textAlign: \"left\",\n            position: \"relative\",\n            verticalAlign: \"bottom\",\n            perspective: 420,\n            color: LIME,\n          }}\n        >\n          {/* Invisible sizer keeps the container's height and baseline. */}\n          <span style={{ visibility: \"hidden\" }}>{PKG_NAMES[0]}</span>\n          {!flipping ? (\n            <span\n              style={{\n                position: \"absolute\",\n                left: 0,\n                top: 0,\n                whiteSpace: \"nowrap\",\n              }}\n            >\n              {typed > CMD.length ? visible.slice(CMD.length) : \"\"}\n              <span\n                style={{\n                  display: \"inline-block\",\n                  width: 14,\n                  height: 28,\n                  marginLeft: 3,\n                  verticalAlign: \"-4px\",\n                  background: caretOn ? MUTED : \"transparent\",\n                  opacity: caretOpacity,\n                }}\n              />\n            </span>\n          ) : (\n            PKG_NAMES.map((name, i) => {\n              // The outgoing name clears first; the incoming one follows\n              // half a flip later, so the two never sit on top of each other.\n              const inStart = FLIP_START + (i - 1) * FLIP_PER + 5;\n              const outStart = FLIP_START + i * FLIP_PER;\n\n              let rotate = 0;\n              let y = 0;\n              let opacity = 1;\n\n              if (i > 0) {\n                const pIn = interpolate(\n                  frame,\n                  [inStart, inStart + FLIP_DUR],\n                  [0, 1],\n                  { ...clampOpts, easing: Easing.out(Easing.cubic) },\n                );\n                rotate = (1 - pIn) * -85;\n                y = (1 - pIn) * 20;\n                opacity = pIn;\n              }\n              if (i < PKG_NAMES.length - 1) {\n                const pOut = interpolate(\n                  frame,\n                  [outStart, outStart + FLIP_DUR],\n                  [0, 1],\n                  { ...clampOpts, easing: Easing.in(Easing.cubic) },\n                );\n                rotate += pOut * 85;\n                y -= pOut * 20;\n                opacity *= 1 - pOut;\n              }\n              if (opacity <= 0.001) return null;\n\n              return (\n                <span\n                  key={name}\n                  style={{\n                    position: \"absolute\",\n                    left: 0,\n                    top: 0,\n                    whiteSpace: \"nowrap\",\n                    opacity,\n                    transform: `translateY(${y}px) rotateX(${rotate}deg)`,\n                    transformOrigin: \"50% 50%\",\n                    backfaceVisibility: \"hidden\",\n                  }}\n                >\n                  {name}\n                </span>\n              );\n            })\n          )}\n        </span>\n      </span>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Scene 8 — Outro. A smoke ring blooms open, the lockup assembles inside it.\n// ===========================================================================\nconst OutroScene: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n\n  const ringRadius = interpolate(frame, [6, 56], [0.03, 0.19], {\n    ...clampOpts,\n    easing: Easing.out(Easing.cubic),\n  });\n\n  const markIn = spring({\n    frame: frame - 52,\n    fps,\n    config: { damping: 13, stiffness: 150, mass: 0.8 },\n  });\n  const markScale = interpolate(markIn, [0, 1], [0.5, 1]);\n\n  const wordIn = spring({\n    frame: frame - 62,\n    fps,\n    config: { damping: 14, stiffness: 130, mass: 0.9 },\n  });\n  const wordX = interpolate(wordIn, [0, 1], [-18, 0]);\n  const wordOpacity = interpolate(frame, [62, 76], [0, 1], clampOpts);\n\n  const creditOpacity = interpolate(frame, [110, 128], [0, 1], clampOpts);\n\n  return (\n    <AbsoluteFill\n      style={{\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        background: OBSIDIAN,\n      }}\n    >\n      <AbsoluteFill style={{ opacity: 0.7 }}>\n        <ShaderSmokeRing\n          speed={0.8}\n          colorBack={OBSIDIAN}\n          colors={[\"#33323d\", \"#525b40\"]}\n          radius={ringRadius}\n          thickness={0.4}\n          scale={0.85}\n        />\n      </AbsoluteFill>\n      <Scrim strength={0.55} />\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 26,\n          transform: \"translateY(-14px)\",\n          position: \"relative\",\n        }}\n      >\n        <div style={{ opacity: markIn, transform: `scale(${markScale})` }}>\n          <RemocnMark size={76} />\n        </div>\n        <span\n          style={{\n            fontFamily: SANS,\n            fontWeight: 400,\n            fontSize: 84,\n            letterSpacing: \"-0.03em\",\n            color: INK,\n            opacity: wordOpacity,\n            transform: `translateX(${wordX}px)`,\n          }}\n        >\n          Remocn\n        </span>\n      </div>\n\n      <span\n        style={{\n          position: \"absolute\",\n          bottom: 42,\n          fontFamily: SANS,\n          fontWeight: 400,\n          fontSize: 19,\n          color: FAINT,\n          opacity: creditOpacity,\n        }}\n      >\n        Open source, all the way down\n      </span>\n    </AbsoluteFill>\n  );\n};\n\n// ===========================================================================\n// Transition presentations — the scene changes are shaders too.\n// Cover envelope: fade in over ~12f, hold fully opaque for ~500ms while the\n// shader plays, then fade out. Children swap underneath the hold.\n// ===========================================================================\ntype EmptyProps = Record<string, never>;\n\nconst coverEnvelope = (p: number) =>\n  interpolate(p, [0, 0.3, 0.65, 1], [0, 1, 1, 0], clampOpts);\n\nconst coverChildOpacity = (p: number, entering: boolean) =>\n  entering\n    ? interpolate(p, [0.58, 0.66], [0, 1], clampOpts)\n    : interpolate(p, [0.28, 0.36], [1, 0], clampOpts);\n\n// The swirl cover mirrors the paper-shaders outro: it fades in over the\n// outgoing scene as the dark twist=1 field, unwinds 1 → 0 into fully open\n// bands, holds there for ~500ms, then winds back 0 → 1 — and synchronized\n// with the wind-back the incoming text resolves through scale + blur on top.\nconst SwirlCover: React.FC<\n  TransitionPresentationComponentProps<EmptyProps>\n> = ({ children, presentationProgress, presentationDirection }) => {\n  const entering = presentationDirection === \"entering\";\n  const p = presentationProgress;\n  // Same gentle curve as the paper-shaders outro swirl.\n  const twist = interpolate(p, [0.1, 0.42, 0.64, 1], [1, 0, 0, 1], {\n    ...clampOpts,\n    easing: Easing.bezier(0.42, 0, 0.58, 1),\n  });\n  // The swirl's colorBack is transparent, so at twist = 1 the cover IS\n  // invisible and the shared backdrop shows through the whole time — no\n  // opacity fade needed, the wind itself brings the bands in and out.\n\n  // The incoming text waits until the wind-back has reached twist ≈ 0.25\n  // (p ≈ 0.77 on the bezier), then grows from deep inside the swirl's dark\n  // center opening to full size as the bands wind shut around it.\n  const childStyle: React.CSSProperties = entering\n    ? {\n        opacity: interpolate(p, [0.77, 0.87], [0, 1], clampOpts),\n        transform: `scale(${interpolate(p, [0.77, 1], [0.3, 1], {\n          ...clampOpts,\n          easing: Easing.out(Easing.cubic),\n        })})`,\n        filter: `blur(${interpolate(p, [0.77, 0.93], [10, 0], clampOpts)}px)`,\n      }\n    : { opacity: interpolate(p, [0.06, 0.13], [1, 0], clampOpts) };\n\n  // The cover sits UNDER the entering children: the incoming text z-scales\n  // in on top of the swirl while it winds shut beneath it.\n  return (\n    <AbsoluteFill>\n      {entering ? (\n        <AbsoluteFill style={{ pointerEvents: \"none\" }}>\n          <ShaderSwirl\n            speed={1}\n            twist={twist}\n            colors={[\"#1f1d29\", \"#413d56\", \"#C3E88D\"]}\n            colorBack=\"rgba(20,19,24,0)\"\n            bandCount={10}\n            softness={0.35}\n          />\n        </AbsoluteFill>\n      ) : null}\n      <AbsoluteFill style={childStyle}>{children}</AbsoluteFill>\n    </AbsoluteFill>\n  );\n};\nconst swirlCover = (): TransitionPresentation<EmptyProps> => ({\n  component: SwirlCover,\n  props: {},\n});\n\n// The frame dissolves through a slow drift of lime dither pixels.\nconst DitherCover: React.FC<\n  TransitionPresentationComponentProps<EmptyProps>\n> = ({ children, presentationProgress, presentationDirection }) => {\n  const entering = presentationDirection === \"entering\";\n  const p = presentationProgress;\n  const size = interpolate(p, [0, 1], [1.6, 2.8], clampOpts);\n  return (\n    <AbsoluteFill>\n      <AbsoluteFill style={{ opacity: coverChildOpacity(p, entering) }}>\n        {children}\n      </AbsoluteFill>\n      {entering ? (\n        <AbsoluteFill\n          style={{ opacity: coverEnvelope(p), pointerEvents: \"none\" }}\n        >\n          <ShaderDithering\n            speed={1.5}\n            colorBack={OBSIDIAN}\n            colorFront={LIME}\n            shape=\"simplex\"\n            size={size}\n          />\n        </AbsoluteFill>\n      ) : null}\n    </AbsoluteFill>\n  );\n};\nconst ditherCover = (): TransitionPresentation<EmptyProps> => ({\n  component: DitherCover,\n  props: {},\n});\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 BlurFade: 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.94 + p * 0.06})`,\n        filter: p < 1 ? `blur(${(1 - p) * 12}px)` : undefined,\n      }\n    : {\n        opacity: 1 - p,\n        transform: `scale(${1 + p * 0.08})`,\n        filter: p > 0 ? `blur(${p * 12}px)` : undefined,\n      };\n  return <AbsoluteFill style={style}>{children}</AbsoluteFill>;\n};\nconst blurFade = (): TransitionPresentation<EmptyProps> => ({\n  component: BlurFade,\n  props: {},\n});\n\n// ===========================================================================\n// Composition root.\n// ===========================================================================\nexport const INTRODUCING_REMOCN_DURATION =\n  S_PAIN +\n  S_MEET +\n  S_TAGLINE +\n  S_POS +\n  S_REGISTRY +\n  S_VALUE +\n  S_INSTALL_TITLE +\n  S_INSTALL_CMD +\n  S_OUTRO -\n  (T_SWIRL + T_DITHER + T_DITHER + T_X + T_BLUR);\n\nexport const IntroducingRemocnDemo: React.FC = () => {\n  return (\n    <AbsoluteFill\n      style={\n        {\n          \"--font-geist-sans\": SANS_FAMILY,\n          background: OBSIDIAN,\n        } as React.CSSProperties\n      }\n    >\n      {/* One quiet simplex-noise field carries the whole video. */}\n      <ShaderSimplexNoise\n        speed={0.35}\n        colors={[\"#141318\", \"#1a1922\", \"#232231\"]}\n        stepsPerColor={2}\n        softness={0.8}\n      />\n      <Scrim strength={0.85} />\n\n      <TransitionSeries>\n        {/* 1 — Pain, two lines */}\n        <TransitionSeries.Sequence durationInFrames={S_PAIN}>\n          <PainScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_SWIRL })}\n          presentation={swirlCover()}\n        />\n\n        {/* 2 — Meet Remocn (plays its own exit, hard cut into the tagline) */}\n        <TransitionSeries.Sequence durationInFrames={S_MEET}>\n          <MeetScene />\n        </TransitionSeries.Sequence>\n\n        {/* 3 — Tagline, its own typography beat */}\n        <TransitionSeries.Sequence durationInFrames={S_TAGLINE}>\n          <TaglineScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_DITHER })}\n          presentation={ditherCover()}\n        />\n\n        {/* 4 — Like shadcn/ui, for video */}\n        <TransitionSeries.Sequence durationInFrames={S_POS}>\n          <PositioningScene />\n        </TransitionSeries.Sequence>\n\n        {/* 5 — Registry montage, hard cut in */}\n        <TransitionSeries.Sequence durationInFrames={S_REGISTRY}>\n          <RegistryScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_DITHER })}\n          presentation={ditherCover()}\n        />\n\n        {/* 6 — The numbers */}\n        <TransitionSeries.Sequence durationInFrames={S_VALUE}>\n          <ValueScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_X })}\n          presentation={crossfade()}\n        />\n\n        {/* 7 — Install */}\n        <TransitionSeries.Sequence durationInFrames={S_INSTALL_TITLE}>\n          <InstallTitleScene />\n        </TransitionSeries.Sequence>\n\n        {/* 7b — Install command (title exits itself, hard cut) */}\n        <TransitionSeries.Sequence durationInFrames={S_INSTALL_CMD}>\n          <InstallCmdScene />\n        </TransitionSeries.Sequence>\n        <TransitionSeries.Transition\n          timing={linearTiming({ durationInFrames: T_BLUR })}\n          presentation={blurFade()}\n        />\n\n        {/* 8 — Outro lockup */}\n        <TransitionSeries.Sequence durationInFrames={S_OUTRO}>\n          <OutroScene />\n        </TransitionSeries.Sequence>\n      </TransitionSeries>\n    </AbsoluteFill>\n  );\n};\n",
      "type": "registry:component",
      "target": "demos/introducing-remocn/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/introducing-remocn/prompt.md",
      "content": "<!-- TODO(draft): placeholder written by AI — replace with the real prompt used to generate this video -->\n\nBuild the first introduction video for remocn's own X account, and make it entirely out of remocn's own shaders and typography — nothing borrowed. Use the shipped brand: Manrope, warm obsidian background, a single lime accent, one quiet noise field running underneath the whole thing. Do a before/after/bridge arc — a couple of pain lines, a shader-swirl cover that winds open into \"Meet remocn\", the tagline \"Cinematic video components for React\", then \"Like shadcn/ui, for video\" building in kinetically. Hard-cut through six of our shader categories fullscreen (mesh-gradient, warp, voronoi, metaballs, god-rays, color-panels type stuff) as pure spectacle, then a short value block (110+ components, one command, code is yours), the install command typing itself, and a smoke-ring outro that assembles the remocn mark and remocn.dev.\n",
      "type": "registry:file",
      "target": "demos/introducing-remocn/prompt.md"
    }
  ],
  "docs": "Register the composition in your Remotion Root:\n\n  import { IntroducingRemocnDemo } from \"@/demos/introducing-remocn\";\n  <Composition id=\"introducing-remocn\" component={IntroducingRemocnDemo} durationInFrames={1012} fps={30} width={1280} height={720} />\n\nRequires Tailwind v4 wired into Remotion (@remotion/tailwind-v4).\nRender locally: npx remotion render introducing-remocn out/introducing-remocn.mp4 --gl=angle",
  "type": "registry:block"
}