{
  "name": "coffeemachine",
  "title": "CoffeeMachine",
  "type": "registry:component",
  "description": "A countertop coffee machine, espresso or drip, with a cup or carafe and a few puffs of steam. Sits on a Counter.",
  "dependencies": [
    "@react-three/fiber@^9.6.1",
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.13.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "CoffeeMachine.tsx",
      "content": "import { useFrame } from '@react-three/fiber'\nimport { CuboidCollider, RigidBody } from '@react-three/rapier'\nimport { pick, rng, sub, useWorld, type WorldComponentProps } from '@runek/core'\nimport { useMemo, useRef } from 'react'\nimport type { Group } from 'three'\n\n/** An `espresso` machine with a cup under the group head, or a `drip` brewer with a carafe. */\nexport type CoffeeMachineKind = 'espresso' | 'drip'\n\nexport interface CoffeeMachineProps extends WorldComponentProps {\n  /** Seeded when unset. */\n  kind?: CoffeeMachineKind\n  /** Body color. Seeded from a few appliance finishes when unset. */\n  color?: string\n  /** A few puffs of steam rising from the cup or carafe. */\n  steam?: boolean\n}\n\nconst FINISHES = ['#2b2b2e', '#b9bdc1', '#a8352b', '#efece4']\nconst CUP = '#f3efe7'\nconst GLASS = '#c8dce6'\nconst COFFEE = '#3b2417'\n\n/**\n * A countertop coffee machine, its origin at its feet so it sits on a `Counter`: an espresso\n * machine (group head, cup on the drip tray, dials) or a drip brewer (water tank, carafe on a hot\n * plate). `steam` sends a few puffs up from the coffee. One small cuboid collider; it stands on a\n * counter, not the ground.\n */\nexport function CoffeeMachine({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  kind,\n  color,\n  steam = true,\n}: CoffeeMachineProps) {\n  const { unit, palette } = useWorld()\n  const u = unit\n  const look = useMemo(() => {\n    const r = rng(sub(seed, 0))\n    const rolled = {\n      kind: pick<CoffeeMachineKind>(r, ['espresso', 'drip']),\n      finish: pick(r, FINISHES),\n    }\n    return { kind: kind ?? rolled.kind, body: color ?? rolled.finish }\n  }, [seed, kind, color])\n\n  const metal = palette.metal\n  const puffs = useRef<(Group | null)[]>([])\n  const espresso = look.kind === 'espresso'\n  // Where the steam starts: the cup's or the carafe's mouth.\n  const mouth: [number, number, number] = espresso ? [0, 0.1 * u, 0.1 * u] : [0, 0.17 * u, 0.09 * u]\n\n  useFrame(({ clock }) => {\n    if (!steam) return\n    const t = clock.elapsedTime\n    puffs.current.forEach((g, i) => {\n      if (!g) return\n      const k = (t * 0.6 + i / 3) % 1\n      g.position.set(Math.sin(k * 6 + i * 2) * 0.012 * u, k * 0.16 * u, 0)\n      g.scale.setScalar(0.4 + k * 1.2)\n      g.visible = k < 0.9\n    })\n  })\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CuboidCollider args={[0.14 * u, 0.17 * u, 0.15 * u]} position={[0, 0.17 * u, 0]} />\n      {espresso ? (\n        <>\n          <mesh position={[0, 0.2 * u, -0.04 * u]} castShadow receiveShadow>\n            <boxGeometry args={[0.28 * u, 0.32 * u, 0.24 * u]} />\n            <meshStandardMaterial color={look.body} roughness={0.35} metalness={0.3} />\n          </mesh>\n          <mesh position={[0, 0.02 * u, 0.04 * u]} castShadow receiveShadow>\n            <boxGeometry args={[0.28 * u, 0.04 * u, 0.26 * u]} />\n            <meshStandardMaterial color={look.body} roughness={0.35} metalness={0.3} />\n          </mesh>\n          {/* drip tray grille, group head, portafilter */}\n          <mesh position={[0, 0.042 * u, 0.1 * u]}>\n            <boxGeometry args={[0.22 * u, 0.006 * u, 0.12 * u]} />\n            <meshStandardMaterial color={metal} roughness={0.3} metalness={0.8} />\n          </mesh>\n          <mesh position={[0, 0.25 * u, 0.1 * u]} castShadow>\n            <cylinderGeometry args={[0.035 * u, 0.04 * u, 0.04 * u, 16]} />\n            <meshStandardMaterial color={metal} roughness={0.25} metalness={0.85} />\n          </mesh>\n          <mesh position={[0, 0.215 * u, 0.13 * u]} rotation={[Math.PI / 2, 0, 0]} castShadow>\n            <cylinderGeometry args={[0.009 * u, 0.009 * u, 0.12 * u, 8]} />\n            <meshStandardMaterial color=\"#1c1c1e\" roughness={0.6} />\n          </mesh>\n          {/* the cup */}\n          <mesh position={[0, 0.075 * u, 0.1 * u]} castShadow>\n            <cylinderGeometry args={[0.03 * u, 0.025 * u, 0.06 * u, 14]} />\n            <meshStandardMaterial color={CUP} roughness={0.3} />\n          </mesh>\n          <mesh position={[0, 0.104 * u, 0.1 * u]} rotation={[-Math.PI / 2, 0, 0]}>\n            <circleGeometry args={[0.026 * u, 14]} />\n            <meshStandardMaterial color={COFFEE} roughness={0.4} />\n          </mesh>\n          {/* dials and a lit button */}\n          {[-1, 1].map((s) => (\n            <mesh\n              key={s}\n              position={[s * 0.09 * u, 0.3 * u, 0.082 * u]}\n              rotation={[Math.PI / 2, 0, 0]}\n            >\n              <cylinderGeometry args={[0.018 * u, 0.018 * u, 0.012 * u, 14]} />\n              <meshStandardMaterial color={metal} roughness={0.3} metalness={0.7} />\n            </mesh>\n          ))}\n          <mesh position={[0, 0.31 * u, 0.0811 * u]}>\n            <circleGeometry args={[0.008 * u, 10]} />\n            <meshBasicMaterial color=\"#5be08a\" toneMapped={false} />\n          </mesh>\n        </>\n      ) : (\n        <>\n          {/* back column with the tank on top, a head reaching over the carafe, a base plate */}\n          <mesh position={[0, 0.17 * u, -0.08 * u]} castShadow receiveShadow>\n            <boxGeometry args={[0.2 * u, 0.34 * u, 0.12 * u]} />\n            <meshStandardMaterial color={look.body} roughness={0.4} metalness={0.2} />\n          </mesh>\n          <mesh position={[0, 0.31 * u, 0.04 * u]} castShadow>\n            <boxGeometry args={[0.2 * u, 0.06 * u, 0.14 * u]} />\n            <meshStandardMaterial color={look.body} roughness={0.4} metalness={0.2} />\n          </mesh>\n          <mesh position={[0, 0.015 * u, 0.02 * u]} castShadow receiveShadow>\n            <boxGeometry args={[0.2 * u, 0.03 * u, 0.26 * u]} />\n            <meshStandardMaterial color={look.body} roughness={0.4} metalness={0.2} />\n          </mesh>\n          <mesh position={[0, 0.032 * u, 0.06 * u]}>\n            <cylinderGeometry args={[0.065 * u, 0.065 * u, 0.006 * u, 20]} />\n            <meshStandardMaterial color=\"#1c1c1e\" roughness={0.5} />\n          </mesh>\n          {/* the carafe: glass, half full */}\n          <mesh position={[0, 0.1 * u, 0.06 * u]} castShadow>\n            <cylinderGeometry args={[0.05 * u, 0.06 * u, 0.13 * u, 18]} />\n            <meshPhysicalMaterial color={GLASS} roughness={0.05} transparent opacity={0.4} />\n          </mesh>\n          <mesh position={[0, 0.07 * u, 0.06 * u]}>\n            <cylinderGeometry args={[0.055 * u, 0.058 * u, 0.06 * u, 18]} />\n            <meshStandardMaterial color={COFFEE} roughness={0.3} />\n          </mesh>\n          <mesh position={[0.07 * u, 0.1 * u, 0.06 * u]}>\n            <boxGeometry args={[0.015 * u, 0.08 * u, 0.02 * u]} />\n            <meshStandardMaterial color=\"#1c1c1e\" roughness={0.6} />\n          </mesh>\n          <mesh position={[0, 0.24 * u, -0.0195 * u]}>\n            <circleGeometry args={[0.008 * u, 10]} />\n            <meshBasicMaterial color=\"#e05a3a\" toneMapped={false} />\n          </mesh>\n        </>\n      )}\n      {steam && (\n        <group position={mouth}>\n          {[0, 1, 2].map((i) => (\n            <group\n              key={i}\n              ref={(g) => {\n                puffs.current[i] = g\n              }}\n            >\n              <mesh>\n                <sphereGeometry args={[0.012 * u, 8, 6]} />\n                <meshBasicMaterial color=\"#eef2f4\" transparent opacity={0.5} depthWrite={false} />\n              </mesh>\n            </group>\n          ))}\n        </group>\n      )}\n    </RigidBody>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
