({ position, onClick }: CoffeeMachineProps)
| 11 | } |
| 12 | |
| 13 | export default function CoffeeMachine({ position, onClick }: CoffeeMachineProps) { |
| 14 | const [hovered, setHovered] = useState(false); |
| 15 | const steamRef = useRef<Mesh>(null); |
| 16 | |
| 17 | // Animate steam |
| 18 | useFrame((state) => { |
| 19 | if (steamRef.current) { |
| 20 | steamRef.current.position.y = 1.3 + Math.sin(state.clock.elapsedTime * 2) * 0.05; |
| 21 | steamRef.current.scale.x = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.1; |
| 22 | steamRef.current.scale.z = 1 + Math.cos(state.clock.elapsedTime * 3) * 0.1; |
| 23 | } |
| 24 | }); |
| 25 | |
| 26 | return ( |
| 27 | <group position={position}> |
| 28 | {/* Main body */} |
| 29 | <Box |
| 30 | args={[0.5, 0.8, 0.4]} |
| 31 | position={[0, 0.4, 0]} |
| 32 | castShadow |
| 33 | receiveShadow |
| 34 | onClick={onClick} |
| 35 | onPointerOver={() => setHovered(true)} |
| 36 | onPointerOut={() => setHovered(false)} |
| 37 | > |
| 38 | <meshStandardMaterial |
| 39 | color={hovered ? '#dc2626' : '#991b1b'} |
| 40 | metalness={0.6} |
| 41 | roughness={0.3} |
| 42 | /> |
| 43 | </Box> |
| 44 | |
| 45 | {/* Water tank (back) */} |
| 46 | <Box |
| 47 | args={[0.35, 0.5, 0.1]} |
| 48 | position={[0, 0.55, -0.15]} |
| 49 | castShadow |
| 50 | > |
| 51 | <meshStandardMaterial |
| 52 | color="#93c5fd" |
| 53 | transparent |
| 54 | opacity={0.4} |
| 55 | metalness={0.1} |
| 56 | roughness={0.1} |
| 57 | /> |
| 58 | </Box> |
| 59 | |
| 60 | {/* Spout */} |
| 61 | <Box |
| 62 | args={[0.08, 0.15, 0.08]} |
| 63 | position={[0, 0.7, 0.2]} |
| 64 | castShadow |
| 65 | > |
| 66 | <meshStandardMaterial color="#1f2937" metalness={0.8} roughness={0.2} /> |
| 67 | </Box> |
| 68 | |
| 69 | {/* Drip tray */} |
| 70 | <Box |
nothing calls this directly
no outgoing calls
no test coverage detected