{
  "name": "tikitorch",
  "title": "TikiTorch",
  "type": "registry:component",
  "description": "A bamboo tiki torch with a woven head and a flickering flame and light, for a beach path or a garden. Can burn only after dusk.",
  "dependencies": [
    "@react-three/fiber@^9.6.1",
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.16.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "TikiTorch.tsx",
      "content": "import { useFrame } from '@react-three/fiber'\nimport { CuboidCollider, RigidBody } from '@react-three/rapier'\nimport { range, rng, sub, useSun, useWorld, type WorldComponentProps } from '@runek/core'\nimport { useMemo, useRef } from 'react'\nimport type { Group, PointLight } from 'three'\n\nexport interface TikiTorchProps extends WorldComponentProps {\n  /** Height to the top of the head, in units. */\n  height?: number\n  /** Bamboo pole and joints. */\n  color?: string\n  /** Woven head and bindings. Defaults to the palette's `bark`. */\n  headColor?: string\n  /** The outer flame. */\n  flameColor?: string\n  /** Light strength; it flickers around this. */\n  intensity?: number\n  /** Burning, or cold. */\n  on?: boolean\n  /** Burn only while the world's sun is down. */\n  nightOnly?: boolean\n}\n\n/**\n * A bamboo tiki torch: a jointed pole with a seeded lean, a woven head bound in cord, and an\n * animated flame with a warm flickering light, for a beachfront path or a garden at dusk.\n * `nightOnly` lights it only after sunset. One thin cuboid collider.\n */\nexport function TikiTorch({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  height = 1.7,\n  color,\n  headColor,\n  flameColor = '#ff7a1a',\n  intensity = 6,\n  on = true,\n  nightOnly = false,\n}: TikiTorchProps) {\n  const { unit, palette } = useWorld()\n  const sun = useSun()\n  const lit = on && (!nightOnly || !sun.day)\n  const pole = color ?? '#b89a5e'\n  const head = headColor ?? palette.bark\n  const H = height * unit\n  const flameRef = useRef<Group>(null)\n  const lightRef = useRef<PointLight>(null)\n\n  const look = useMemo(() => {\n    const r = rng(sub(seed, 0))\n    const headH = range(r, 0.22, 0.3) * unit\n    const poleH = H - headH\n    const joints = Math.max(2, Math.round(poleH / (range(r, 0.35, 0.5) * unit)))\n    return {\n      lean: [range(r, -0.04, 0.04), 0, range(r, -0.04, 0.04)] as [number, number, number],\n      headH,\n      headR: range(r, 0.07, 0.09) * unit,\n      flare: range(r, 1.1, 1.35),\n      poleH,\n      joints: Array.from({ length: joints - 1 }, (_, i) => ((i + 1) / joints) * poleH),\n      bands: 2 + Math.floor(r() * 2),\n      phase: r() * 10,\n    }\n  }, [seed, H, unit])\n\n  useFrame(({ clock }) => {\n    if (!lit) return\n    const t = clock.elapsedTime + look.phase\n    const n = Math.sin(t * 11) * 0.5 + Math.sin(t * 19 + 1) * 0.3 + Math.sin(t * 7 + 2) * 0.2\n    if (flameRef.current) {\n      flameRef.current.scale.set(1 - n * 0.08, 1 + n * 0.22, 1 - n * 0.08)\n      flameRef.current.rotation.z = n * 0.08\n    }\n    if (lightRef.current) lightRef.current.intensity = intensity * (1 + n * 0.25)\n  })\n\n  const poleR = 0.022 * unit\n  const { headH, headR, poleH } = look\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CuboidCollider args={[poleR * 2, H / 2, poleR * 2]} position={[0, H / 2, 0]} />\n      <group rotation={look.lean}>\n        <mesh position={[0, poleH / 2, 0]} castShadow>\n          <cylinderGeometry args={[poleR, poleR * 1.15, poleH, 10]} />\n          <meshStandardMaterial color={pole} roughness={0.7} />\n        </mesh>\n        {look.joints.map((y) => (\n          <mesh key={y} position={[0, y, 0]}>\n            <torusGeometry args={[poleR * 1.05, poleR * 0.25, 6, 12]} />\n            <meshStandardMaterial color={pole} roughness={0.8} />\n          </mesh>\n        ))}\n        {/* the woven head flares from the pole to its rim, bound with cord */}\n        <mesh position={[0, poleH + headH / 2, 0]} castShadow>\n          <cylinderGeometry args={[headR * look.flare, headR * 0.55, headH, 10, 1, true]} />\n          <meshStandardMaterial color={head} roughness={1} side={2} flatShading />\n        </mesh>\n        {Array.from({ length: look.bands }, (_, i) => {\n          const k = (i + 0.5) / look.bands\n          return (\n            <mesh key={k} position={[0, poleH + headH * k, 0]} rotation={[Math.PI / 2, 0, 0]}>\n              <torusGeometry\n                args={[headR * (0.55 + (look.flare - 0.55) * k) * 1.02, 0.006 * unit, 5, 14]}\n              />\n              <meshStandardMaterial color={pole} roughness={0.9} />\n            </mesh>\n          )\n        })}\n        <mesh position={[0, poleH + headH * 0.92, 0]}>\n          <cylinderGeometry\n            args={[headR * look.flare * 0.92, headR * look.flare * 0.92, 0.01 * unit, 10]}\n          />\n          <meshStandardMaterial color=\"#1a1410\" roughness={1} />\n        </mesh>\n        {lit && (\n          <>\n            <group ref={flameRef} position={[0, poleH + headH, 0]}>\n              <mesh position={[0, headR * 1.3, 0]}>\n                <coneGeometry args={[headR * 0.9, headR * 2.8, 9]} />\n                <meshBasicMaterial\n                  color={flameColor}\n                  transparent\n                  opacity={0.85}\n                  toneMapped={false}\n                />\n              </mesh>\n              <mesh position={[0, headR * 0.9, 0]}>\n                <coneGeometry args={[headR * 0.5, headR * 1.8, 9]} />\n                <meshBasicMaterial color=\"#ffd84d\" transparent opacity={0.9} toneMapped={false} />\n              </mesh>\n            </group>\n            <pointLight\n              ref={lightRef}\n              position={[0, poleH + headH + headR * 2, 0]}\n              color=\"#ff9a3c\"\n              intensity={intensity}\n              distance={8 * unit}\n              decay={2}\n            />\n          </>\n        )}\n      </group>\n    </RigidBody>\n  )\n}\n\nTikiTorch.groundSitting = true\n",
      "type": "registry:component"
    }
  ]
}
