| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export const drawLandmarks = (ctx, landmarks, canvas, isMobile) => { |
| | |
| | const connections = [ |
| | |
| | [0, 1], [1, 2], [2, 3], [3, 4], |
| | |
| | [0, 5], [5, 6], [6, 7], [7, 8], |
| | |
| | [9, 10], [10, 11], [11, 12], [5, 9], |
| | |
| | [9, 13], [13, 14], [14, 15], [15, 16], |
| | |
| | [13, 17], [17, 18], [18, 19], [19, 20], |
| | |
| | [0, 17] |
| | ]; |
| |
|
| | |
| | ctx.lineWidth = isMobile ? 2 : 3; |
| | ctx.strokeStyle = '#217BFE'; |
| | |
| | for (const [start, end] of connections) { |
| | ctx.beginPath(); |
| | ctx.moveTo(landmarks[start].x * canvas.width, landmarks[start].y * canvas.height); |
| | ctx.lineTo(landmarks[end].x * canvas.width, landmarks[end].y * canvas.height); |
| | ctx.stroke(); |
| | } |
| | |
| | |
| | ctx.fillStyle = '#AC87EB'; |
| | landmarks.forEach(landmark => { |
| | ctx.beginPath(); |
| | ctx.arc(landmark.x * canvas.width, landmark.y * canvas.height, isMobile ? 3 : 4, 0, 2 * Math.PI); |
| | ctx.fill(); |
| | }); |
| | |
| | |
| | const thumbTip = landmarks[4]; |
| | const indexTip = landmarks[8]; |
| | |
| | ctx.fillStyle = '#217BFE'; |
| | [thumbTip, indexTip].forEach(tip => { |
| | ctx.beginPath(); |
| | ctx.arc(tip.x * canvas.width, tip.y * canvas.height, isMobile ? 4 : 6, 0, 2 * Math.PI); |
| | ctx.fill(); |
| | }); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export const analyzeHandGesture = (landmarks) => { |
| | const thumbTip = landmarks[4]; |
| | const indexTip = landmarks[8]; |
| | const wrist = landmarks[0]; |
| | |
| | |
| | |
| | const isLeftHand = !(thumbTip.x < wrist.x); |
| | |
| | |
| | const distance = Math.sqrt( |
| | Math.pow(thumbTip.x - indexTip.x, 2) + |
| | Math.pow(thumbTip.y - indexTip.y, 2) + |
| | Math.pow(thumbTip.z - indexTip.z, 2) |
| | ); |
| | |
| | |
| | const isOpen = distance > 0.1; |
| | |
| | return { |
| | isOpen, |
| | isLeftHand, |
| | thumbPosition: { |
| | x: thumbTip.x, |
| | y: thumbTip.y |
| | } |
| | }; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | export const getAnimationKeyframes = () => { |
| | return ` |
| | @keyframes thinking-blink { |
| | 0%, 100% { opacity: 1; transform: scale(1); } |
| | 50% { opacity: 0.7; transform: scale(0.95); } |
| | } |
| | |
| | @keyframes spring-out { |
| | 0% { |
| | transform: scale(0) translateY(0); |
| | opacity: 0; |
| | } |
| | 20% { |
| | transform: scale(0.3) translateY(-5px); |
| | opacity: 0.7; |
| | } |
| | 40% { |
| | transform: scale(1.5) translateY(-30px); |
| | } |
| | 60% { |
| | transform: scale(0.8) translateY(15px); |
| | } |
| | 75% { |
| | transform: scale(1.2) translateY(-10px); |
| | } |
| | 90% { |
| | transform: scale(0.95) translateY(5px); |
| | } |
| | 100% { |
| | transform: scale(1) translateY(0); |
| | opacity: 1; |
| | } |
| | } |
| | |
| | @keyframes wiggle { |
| | 0% { transform: rotate(0deg); } |
| | 15% { transform: rotate(-15deg); } |
| | 30% { transform: rotate(12deg); } |
| | 45% { transform: rotate(-8deg); } |
| | 60% { transform: rotate(5deg); } |
| | 75% { transform: rotate(-2deg); } |
| | 100% { transform: rotate(0deg); } |
| | } |
| | |
| | /* Combined animation that handles both scale and rotation */ |
| | @keyframes spring-wiggle { |
| | 0% { |
| | transform: scale(0) rotate(0deg) translateY(0); |
| | opacity: 0; |
| | } |
| | 15% { |
| | transform: scale(0.2) rotate(-5deg) translateY(-5px); |
| | opacity: 0.5; |
| | } |
| | 30% { |
| | transform: scale(1.5) rotate(12deg) translateY(-30px); |
| | opacity: 1; |
| | } |
| | 45% { |
| | transform: scale(0.8) rotate(-8deg) translateY(15px); |
| | } |
| | 60% { |
| | transform: scale(1.2) rotate(5deg) translateY(-10px); |
| | } |
| | 75% { |
| | transform: scale(0.95) rotate(-2deg) translateY(5px); |
| | } |
| | 90% { |
| | transform: scale(1.05) rotate(1deg) translateY(-2px); |
| | } |
| | 100% { |
| | transform: scale(1) rotate(0deg) translateY(0); |
| | } |
| | } |
| | |
| | /* Add new animations for particles and popping */ |
| | @keyframes float-particle { |
| | 0% { |
| | transform: translate(0, 0) rotate(0deg); |
| | opacity: 1; |
| | } |
| | 100% { |
| | transform: translate(var(--tx), var(--ty)) rotate(var(--r)); |
| | opacity: 0; |
| | } |
| | } |
| | |
| | @keyframes pop-out { |
| | 0% { |
| | transform: scale(1); |
| | opacity: 1; |
| | } |
| | 50% { |
| | transform: scale(1.2); |
| | } |
| | 100% { |
| | transform: scale(0); |
| | opacity: 0; |
| | } |
| | } |
| | `; |
| | }; |