{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "branch-tree",
  "title": "Branch Tree",
  "description": "A tree branches from one component into many leaves, ending on 48 variants.",
  "dependencies": ["remotion", "@remotion/google-fonts", "@remotion/paths"],
  "files": [
    {
      "path": "registry/remocn/branch-tree/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/SpaceGrotesk\";\nimport { evolvePath } from \"@remotion/paths\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\n/** gradients.sh \"aurora\" palette, top to bottom of the leaf column. Light keeps the hues, deeper. */\nconst AURORA = {\n  dark: [\"#a0f0c8\", \"#3fb5a3\", \"#5147b6\", \"#b25c9c\"],\n  light: [\"#3fae86\", \"#2a8f84\", \"#4a3fb0\", \"#a2468b\"],\n} as const;\n\nfunction lerpHex(stops: readonly string[], k: number) {\n  const x = Math.min(1, Math.max(0, k)) * (stops.length - 1);\n  const i = Math.min(stops.length - 2, Math.floor(x));\n  const w = x - i;\n  const ch = (h: string, o: number) => Number.parseInt(h.slice(o, o + 2), 16);\n  const m = (o: number) =>\n    Math.round(ch(stops[i], o) + (ch(stops[i + 1], o) - ch(stops[i], o)) * w);\n  return `rgb(${m(1)}, ${m(3)}, ${m(5)})`;\n}\n\nexport interface BranchTreeProps {\n  depth?: number;\n  branching?: number | number[];\n  title?: string;\n  rootLabel?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_GROW = Easing.bezier(0.45, 0, 0.2, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    node: \"#8B8A90\",\n    hair: \"rgba(255,255,255,0.3)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    node: \"#8E8C86\",\n    hair: \"rgba(18,18,20,0.32)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst ROOT_IN = 4;\nconst EDGE = 12;\nconst LEAD = 2;\nconst SIB = 2;\nconst MAX_LEAVES = 96;\n\ninterface TreeNode {\n  level: number;\n  parent: number;\n  sib: number;\n  sibs: number;\n  y: number;\n  path: number[];\n  children: number[];\n  arrive: number;\n  start: number;\n}\n\nfunction buildTree(depth: number, fan: number[]) {\n  const nodes: TreeNode[] = [\n    {\n      level: 0,\n      parent: -1,\n      sib: 0,\n      sibs: 1,\n      y: 0,\n      path: [],\n      children: [],\n      arrive: ROOT_IN,\n      start: ROOT_IN,\n    },\n  ];\n  let frontier = [0];\n  for (let l = 1; l <= depth; l++) {\n    const next: number[] = [];\n    const k = fan[l - 1];\n    for (const pi of frontier) {\n      const parent = nodes[pi];\n      for (let j = 0; j < k; j++) {\n        const start = parent.arrive + LEAD + Math.abs(j - (k - 1) / 2) * SIB;\n        const idx = nodes.length;\n        nodes.push({\n          level: l,\n          parent: pi,\n          sib: j,\n          sibs: k,\n          y: 0,\n          path: [...parent.path, j],\n          children: [],\n          arrive: start + EDGE,\n          start,\n        });\n        parent.children.push(idx);\n        next.push(idx);\n      }\n    }\n    frontier = next;\n  }\n  return { nodes, leaves: frontier };\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={43}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nexport function BranchTree({\n  depth = 3,\n  branching = [3, 4, 4],\n  title = \"variants\",\n  rootLabel = \"1 component\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: BranchTreeProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n\n  const D = Math.min(5, Math.max(1, Math.round(depth)));\n  const raw = Array.isArray(branching) ? branching : [branching];\n  const fan: number[] = [];\n  let leafCount = 1;\n  for (let l = 0; l < D; l++) {\n    const want = Math.min(\n      6,\n      Math.max(1, Math.round(raw[Math.min(l, raw.length - 1)] ?? 2)),\n    );\n    const k = Math.max(1, Math.min(want, Math.floor(MAX_LEAVES / leafCount)));\n    fan.push(k);\n    leafCount *= k;\n  }\n\n  const { nodes, leaves } = buildTree(D, fan);\n\n  const rootX = width * 0.19;\n  const leafX = width * 0.61;\n  const cy = height / 2;\n  const treeH = height * 0.76;\n  const xOf = (l: number) => rootX + ((leafX - rootX) * l) / D;\n\n  let acc = 0;\n  const raws: number[] = [];\n  leaves.forEach((li, j) => {\n    if (j > 0) {\n      const a = nodes[leaves[j - 1]].path;\n      const b = nodes[li].path;\n      let common = 0;\n      while (common < D && a[common] === b[common]) common++;\n      acc += 1 + 0.9 * (D - 1 - common);\n    }\n    raws.push(acc);\n  });\n  const span = acc || 1;\n  leaves.forEach((li, j) => {\n    nodes[li].y =\n      leaves.length > 1 ? cy - treeH / 2 + (treeH * raws[j]) / span : cy;\n  });\n  for (let i = nodes.length - 1; i >= 0; i--) {\n    const n = nodes[i];\n    if (n.children.length) {\n      n.y =\n        (nodes[n.children[0]].y + nodes[n.children[n.children.length - 1]].y) /\n        2;\n    }\n  }\n\n  const lastArrive = Math.max(...leaves.map((i) => nodes[i].arrive));\n  const HIT = lastArrive + 6;\n\n  const land = spring({\n    frame: t - HIT,\n    fps,\n    config: { damping: 18, stiffness: 150, mass: 0.8 },\n  });\n  const landO = interpolate(t, [HIT, HIT + 8], [0, 1], CLAMP);\n  const blur = interpolate(t, [HIT, HIT + 14], [1, 0], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const count = interpolate(t, [HIT, HIT + 24], [0.6, 1], {\n    ...CLAMP,\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  const cap = interpolate(t, [HIT + 10, HIT + 28], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const settle = interpolate(t, [HIT - 4, HIT + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const rootIn = spring({\n    frame: t - ROOT_IN,\n    fps,\n    config: { damping: 16, stiffness: 160, mass: 0.7 },\n  });\n  const rootLab = interpolate(t, [ROOT_IN + 2, ROOT_IN + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const push = interpolate(t, [0, 140], [0.965, 1.005], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const stage = interpolate(t, [0, 8], [0.5, 1], CLAMP);\n\n  const stroke = Math.max(1, 1.25 * u);\n  const leafR = Math.max(2.4, 3.4 * u);\n  const nodeR = Math.max(2.4, 3.4 * u);\n  const numSize = 200 * u;\n  const aur = AURORA[theme];\n  const top = cy - treeH / 2;\n  const tint = (y: number) => lerpHex(aur, (y - top) / treeH);\n  const gid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const drift = interpolate(t, [0, 150], [0, 1], { ...CLAMP, easing: EASE_IO });\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          transform: `scale(${push})`,\n          transformOrigin: \"45% 50%\",\n          opacity: stage,\n        }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          <defs>\n            <linearGradient\n              id={`${gid}e`}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={0}\n              y1={top}\n              x2={0}\n              y2={top + treeH}\n            >\n              {aur.map((c, i) => (\n                <stop key={c} offset={i / (aur.length - 1)} stopColor={c} />\n              ))}\n            </linearGradient>\n          </defs>\n          {nodes.map((n, i) => {\n            if (n.parent < 0 || t < n.start) return null;\n            const par = nodes[n.parent];\n            const px = xOf(par.level);\n            const cx = xOf(n.level);\n            const mx = px + (cx - px) * 0.5;\n            const d = `M${px.toFixed(2)} ${par.y.toFixed(2)} C${mx.toFixed(2)} ${par.y.toFixed(2)} ${mx.toFixed(2)} ${n.y.toFixed(2)} ${cx.toFixed(2)} ${n.y.toFixed(2)}`;\n            const k = interpolate(t, [n.start, n.arrive], [0, 1], {\n              ...CLAMP,\n              easing: EASE_GROW,\n            });\n            const evo = evolvePath(k, d);\n            return (\n              <path\n                key={`e${i}`}\n                d={d}\n                fill=\"none\"\n                stroke={n.level === D ? `url(#${gid}e)` : p.hair}\n                strokeWidth={stroke}\n                strokeLinecap=\"round\"\n                strokeDasharray={evo.strokeDasharray}\n                strokeDashoffset={evo.strokeDashoffset}\n                opacity={\n                  n.level === D\n                    ? (theme === \"dark\" ? 0.5 : 0.6) + 0.25 * settle\n                    : 1 - 0.3 * settle\n                }\n              />\n            );\n          })}\n\n          {nodes.map((n, i) => {\n            if (n.level === 0 || t < n.arrive - 2) return null;\n            const isLeaf = n.level === D;\n            const s = spring({\n              frame: t - n.arrive + 1,\n              fps,\n              config: isLeaf\n                ? { damping: 11, stiffness: 220, mass: 0.6 }\n                : { damping: 18, stiffness: 200, mass: 0.6 },\n            });\n            const o = interpolate(\n              t,\n              [n.arrive - 2, n.arrive + 3],\n              [0, 1],\n              CLAMP,\n            );\n            return (\n              <circle\n                key={`n${i}`}\n                cx={xOf(n.level)}\n                cy={n.y}\n                r={(isLeaf ? leafR : nodeR) * (0.4 + 0.6 * s)}\n                fill={isLeaf ? tint(n.y) : p.node}\n                opacity={o}\n              />\n            );\n          })}\n\n          <circle\n            cx={rootX}\n            cy={nodes[0].y}\n            r={14 * u * (0.7 + 0.3 * rootIn)}\n            fill=\"none\"\n            stroke={accent}\n            strokeWidth={stroke * 1.4}\n            opacity={interpolate(t, [ROOT_IN, ROOT_IN + 6], [0, 1], CLAMP)}\n          />\n          <circle\n            cx={rootX}\n            cy={nodes[0].y}\n            r={7 * u * (0.4 + 0.6 * rootIn)}\n            fill={p.fg}\n            opacity={interpolate(t, [ROOT_IN, ROOT_IN + 5], [0, 1], CLAMP)}\n          />\n        </svg>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: rootX,\n            top: nodes[0].y - 34 * u,\n            transform: `translate(-50%, -100%) translateY(${(1 - rootLab) * 6 * u}px)`,\n            opacity: rootLab,\n            fontFamily: sans,\n            fontWeight: 400,\n            fontSize: 21 * u,\n            letterSpacing: \"-0.01em\",\n            color: p.fg,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {rootLabel}\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: leafX + 64 * u,\n            top: cy,\n            transform: `translateY(${-numSize * 0.6}px)`,\n          }}\n        >\n          <div\n            style={{\n              fontFamily: sans,\n              fontWeight: 500,\n              fontSize: numSize,\n              lineHeight: 0.9,\n              letterSpacing: \"-0.05em\",\n              paddingRight: 0.06 * numSize,\n              backgroundImage: `linear-gradient(${172 - 10 * drift}deg, ${aur[0]} 4%, ${aur[1]} 34%, ${aur[2]} 70%, ${aur[3]} 100%)`,\n              WebkitBackgroundClip: \"text\",\n              backgroundClip: \"text\",\n              color: \"transparent\",\n              fontVariantNumeric: \"tabular-nums\",\n              whiteSpace: \"nowrap\",\n              opacity: landO,\n              filter: blur > 0.01 ? `blur(${blur * 8 * u}px)` : undefined,\n              transform: `translateX(${(1 - land) * -24 * u}px)`,\n            }}\n          >\n            {Math.round(leafCount * count)}\n          </div>\n          <div\n            style={{\n              marginTop: 18 * u,\n              marginLeft: 0.04 * numSize,\n              opacity: cap,\n              transform: `translateY(${(1 - cap) * 8 * u}px)`,\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: 28 * u,\n              letterSpacing: \"-0.015em\",\n              color: p.muted,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {title}\n          </div>\n        </div>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 80% 80% at 50% 50%, transparent 62%, ${p.bg} 100%)`,\n          opacity: 0.6,\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/branch-tree.tsx"
    }
  ],
  "type": "registry:component"
}
