{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "phyllotaxis",
  "title": "Phyllotaxis",
  "description": "Seeds spiral out at the golden angle into a sunflower head, then an accent Fibonacci arm pulses outward.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/phyllotaxis/index.tsx",
      "content": "\"use client\";\n\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type PhyllotaxisProps = {\n  /** Number of seeds. */\n  count?: number;\n  /** Seed size multiplier. */\n  size?: number;\n  /** Frames for all seeds to spiral out. */\n  growFrames?: number;\n  /** Frames per pulse along the accent arm once grown. */\n  pulseFrames?: number;\n  /** Arm spacing: every n-th seed forms one parastichy (a Fibonacci number reads best). */\n  arm?: number;\n  /** Slow rotation of the head in degrees per second; 0 holds still. */\n  rotation?: number;\n  speed?: number;\n  background?: string;\n  color?: string;\n  accent?: string;\n};\n\nexport const phyllotaxisDefaults: Required<PhyllotaxisProps> = {\n  count: 420,\n  size: 1,\n  growFrames: 60,\n  pulseFrames: 45,\n  arm: 21,\n  rotation: 4,\n  speed: 1,\n  background: \"#111111\",\n  color: \"#FFFFFF\",\n  accent: \"#E5322D\",\n};\n\nconst GOLDEN = (137.508 * Math.PI) / 180;\nconst POP = 10;\nconst pop = Easing.out(Easing.back(2.2));\nconst clamp = { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" } as const;\nconst fmt = (n: number) => Math.round(n * 10) / 10;\nconst dot = (x: number, y: number, r: number) =>\n  `M ${fmt(x - r)} ${fmt(y)} a ${fmt(r)} ${fmt(r)} 0 1 0 ${fmt(r * 2)} 0 a ${fmt(r)} ${fmt(r)} 0 1 0 ${fmt(-r * 2)} 0 `;\n\nexport function Phyllotaxis({\n  count = phyllotaxisDefaults.count,\n  size = phyllotaxisDefaults.size,\n  growFrames = phyllotaxisDefaults.growFrames,\n  pulseFrames = phyllotaxisDefaults.pulseFrames,\n  arm = phyllotaxisDefaults.arm,\n  rotation = phyllotaxisDefaults.rotation,\n  speed = phyllotaxisDefaults.speed,\n  background = phyllotaxisDefaults.background,\n  color = phyllotaxisDefaults.color,\n  accent = phyllotaxisDefaults.accent,\n}: PhyllotaxisProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const n = Math.max(1, Math.round(count));\n  const step = Math.max(1, Math.round(arm));\n  const time = frame * speed;\n  const cx = width / 2;\n  const cy = height / 2;\n  const maxR = Math.min(width, height) * 0.44;\n  const c = maxR / Math.sqrt(n);\n  const spin = (rotation * Math.PI * time) / 180 / fps;\n\n  // Seeds appear in order with a little overshoot; the reveal eases so the\n  // dense centre does not flash in all at once.\n  const grown = (i: number) => {\n    const at = growFrames * Math.sqrt(i / n);\n    return pop(Math.min(1, Math.max(0, (time - at) / POP)));\n  };\n  const armIn = interpolate(time, [growFrames, growFrames + 14], [0, 1], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n  const since = time - growFrames - 10;\n  const pulse = since > 0 ? (since / Math.max(1, pulseFrames)) % 1 : -1;\n\n  let plain = \"\";\n  let red = \"\";\n  let spine = \"\";\n  for (let i = 1; i <= n; i++) {\n    const s = grown(i);\n    if (s <= 0) continue;\n    const radius = c * Math.sqrt(i);\n    const theta = i * GOLDEN + spin;\n    const x = cx + Math.cos(theta) * radius;\n    const y = cy + Math.sin(theta) * radius;\n    // Outer seeds are a touch larger, like a sunflower head.\n    const base = c * 0.42 * size * (0.55 + 0.45 * Math.sqrt(i / n));\n    const onArm = i % step === 0 && armIn > 0;\n    if (!onArm) {\n      plain += dot(x, y, base * s);\n      continue;\n    }\n    // Pulse travels outward along the arm as a soft gaussian bump.\n    const u = Math.sqrt(i / n);\n    const bump =\n      pulse >= 0 ? Math.exp(-(((u - pulse * 1.4 + 0.2) / 0.09) ** 2)) : 0;\n    red += dot(x, y, base * s * (1 + 0.25 * armIn + bump * 0.9));\n    spine += `${spine ? \"L\" : \"M\"} ${fmt(x)} ${fmt(y)} `;\n  }\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: background }}>\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        viewBox={`0 0 ${width} ${height}`}\n        role=\"img\"\n        aria-label=\"Phyllotaxis spiral\"\n        style={{ display: \"block\" }}\n      >\n        <path d={plain} fill={color} />\n        {spine ? (\n          <path\n            d={spine}\n            fill=\"none\"\n            stroke={accent}\n            strokeOpacity={0.35 * armIn}\n            strokeWidth={Math.min(width, height) / 720}\n          />\n        ) : null}\n        <path d={red} fill={color} fillOpacity={1 - armIn} />\n        <path d={red} fill={accent} fillOpacity={armIn} />\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/phyllotaxis.tsx"
    }
  ],
  "type": "registry:component"
}
