{
  "name": "ecosystem-orbit",
  "type": "registry:block",
  "description": "Logo with orbiting satellites",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "layout",
    "timing",
    "motion-tokens"
  ],
  "composition": {
    "id": "EcosystemOrbit",
    "component": "EcosystemOrbit",
    "durationInFrames": 180,
    "fps": 30,
    "width": 1920,
    "height": 1080,
    "importPath": "@/compositions/ecosystem-orbit/index"
  },
  "files": [
    {
      "path": "registry/bases/default/compositions/ecosystem-orbit/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont } from \"@remotion/google-fonts/Inter\";\nimport {\n  AbsoluteFill,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport { getSafeAreaPadding, scaleFont } from \"@/remotion/lib/layout\";\nimport { EASING } from \"@/remotion/lib/motion-tokens\";\n\nconst { fontFamily } = loadFont(\"normal\", {\n  weights: [\"600\", \"700\"],\n  subsets: [\"latin\"],\n});\n\nexport type EcosystemOrbitProps = {\n  centerLabel?: string;\n  satellites?: string[];\n  accentColor?: string;\n};\n\nconst COLORS = {\n  bg: \"#080810\",\n  center: \"#e8b86d\",\n  satellite: \"#2dd4bf\",\n  line: \"rgba(232,184,109,0.35)\",\n} as const;\n\n/**\n * One-shot ambient demo, not a seamless loop: the orbit's rotation period\n * (2π × 38 frames ≈ 239 frames) and the pulse period (2π × 14 ≈ 88 frames)\n * don't divide the composition's duration cleanly, and the satellites never\n * complete a full revolution within a typical preview length — so there is\n * no frame where the motion state repeats. Rather than force a seamless\n * wrap, the orbit settles to a stop and fades out as a deliberate exit beat.\n */\nconst EXIT_DURATION = 30;\n\nexport const EcosystemOrbit: React.FC<EcosystemOrbitProps> = ({\n  centerLabel = \"RemotionUI\",\n  satellites = [\"CLI\", \"Scenes\", \"Reels\", \"Docs\", \"Registry\"],\n  accentColor = COLORS.center,\n}) => {\n  const frame = useCurrentFrame();\n  const { fps, width, height, durationInFrames } = useVideoConfig();\n  const safe = getSafeAreaPadding({ width, height });\n  const cx = width / 2;\n  const cy = height / 2;\n  const radius = Math.min(width, height) * 0.28;\n  const centerEnter = spring({\n    frame,\n    fps,\n    config: { damping: 16, stiffness: 100, mass: 0.9 },\n    durationInFrames: 30,\n  });\n\n  /** Settle: freeze the orbit's motion clock before the fade begins so the\n   *  satellites glide to a rest position instead of vanishing mid-spin. */\n  const exitStart = durationInFrames - EXIT_DURATION;\n  const settledFrame = Math.min(frame, exitStart);\n  const pulse = (Math.sin(settledFrame / 14) + 1) / 2;\n  const exitProgress = interpolate(\n    frame,\n    [exitStart, durationInFrames - 1],\n    [0, 1],\n    { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\", easing: EASING.exit },\n  );\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: COLORS.bg,\n        backgroundImage: `radial-gradient(circle at 50% 50%, ${accentColor}12, transparent 55%)`,\n        fontFamily,\n        opacity: 1 - exitProgress,\n      }}\n    >\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", inset: 0, pointerEvents: \"none\" }}\n      >\n        {satellites.map((label, i) => {\n          const stagger = i * 5;\n          const sp = spring({\n            frame: frame - stagger,\n            fps,\n            config: { damping: 18, stiffness: 90, mass: 1 },\n            durationInFrames: 45,\n          });\n          const angle =\n            (i / satellites.length) * Math.PI * 2 + settledFrame / 38;\n          const x = cx + Math.cos(angle) * radius * sp;\n          const y = cy + Math.sin(angle) * radius * sp;\n          const activeIdx = Math.floor(settledFrame / 28) % satellites.length;\n          const lineOpacity =\n            activeIdx === i\n              ? interpolate(\n                  settledFrame % 28,\n                  [0, 6, 22, 28],\n                  [0.2, 0.85, 0.85, 0.2],\n                  { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" },\n                )\n              : 0.15;\n          return (\n            <line\n              key={`line-${label}`}\n              x1={cx}\n              y1={cy}\n              x2={x}\n              y2={y}\n              stroke={COLORS.line}\n              strokeWidth={2}\n              opacity={lineOpacity * sp}\n            />\n          );\n        })}\n      </svg>\n      <div\n        style={{\n          position: \"absolute\",\n          left: cx - scaleFont(90, width),\n          top: cy - scaleFont(44, width),\n          width: scaleFont(180, width),\n          height: scaleFont(88, width),\n          borderRadius: 999,\n          background: `${accentColor}22`,\n          border: `1px solid ${accentColor}66`,\n          display: \"grid\",\n          placeItems: \"center\",\n          color: \"#f4f4f5\",\n          fontSize: scaleFont(28, width),\n          fontWeight: 700,\n          opacity: centerEnter,\n          transform: `scale(${0.88 + centerEnter * 0.12 + pulse * 0.03})`,\n          boxShadow: `0 0 ${Math.round(40 + pulse * 24)}px ${accentColor}44`,\n        }}\n      >\n        {centerLabel}\n      </div>\n      {satellites.map((label, i) => {\n        const stagger = i * 5;\n        const sp = spring({\n          frame: frame - stagger,\n          fps,\n          config: { damping: 18, stiffness: 90, mass: 1 },\n          durationInFrames: 45,\n        });\n        const angle =\n          (i / satellites.length) * Math.PI * 2 + settledFrame / 38;\n        const x = cx + Math.cos(angle) * radius * sp - scaleFont(48, width);\n        const y = cy + Math.sin(angle) * radius * sp - scaleFont(20, width);\n        const activeIdx = Math.floor(settledFrame / 28) % satellites.length;\n        const lift =\n          activeIdx === i\n            ? interpolate(\n                settledFrame % 28,\n                [0, 8, 20, 28],\n                [1, 1.08, 1.08, 1],\n                {\n                  extrapolateLeft: \"clamp\",\n                  extrapolateRight: \"clamp\",\n                  easing: EASING.enter,\n                },\n              )\n            : 1;\n\n        return (\n          <div\n            key={label}\n            style={{\n              position: \"absolute\",\n              left: x,\n              top: y,\n              width: scaleFont(96, width),\n              height: scaleFont(40, width),\n              borderRadius: 12,\n              background: `${COLORS.satellite}18`,\n              border: `1px solid ${COLORS.satellite}55`,\n              display: \"grid\",\n              placeItems: \"center\",\n              color: \"#e2e8f0\",\n              fontSize: scaleFont(18, width),\n              fontWeight: 600,\n              opacity: sp,\n              transform: `scale(${lift})`,\n              boxShadow:\n                activeIdx === i\n                  ? `0 0 20px ${COLORS.satellite}44`\n                  : undefined,\n            }}\n          >\n            {label}\n          </div>\n        );\n      })}\n      <div\n        style={{\n          position: \"absolute\",\n          left: safe.paddingLeft,\n          right: safe.paddingRight,\n          bottom: safe.paddingBottom,\n          textAlign: \"center\",\n          color: \"#71717a\",\n          fontSize: scaleFont(22, width),\n          opacity: interpolate(frame, [40, 55], [0, 1], {\n            extrapolateLeft: \"clamp\",\n            extrapolateRight: \"clamp\",\n          }),\n        }}\n      >\n        Integrations orbit your brand\n      </div>\n    </AbsoluteFill>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "reels",
    "drive": "time",
    "tier": "advanced"
  }
}