Spaces:
Running
Running
| """ | |
| Pip Character - Cute animated blob with emotional states. | |
| Kawaii-style SVG character with expressive animations. | |
| """ | |
| from typing import Literal | |
| PipState = Literal[ | |
| "neutral", "happy", "sad", "thinking", "concerned", | |
| "excited", "sleepy", "listening", "attentive", "speaking" | |
| ] | |
| # Cute pastel color palettes for different emotional states | |
| COLORS = { | |
| "neutral": { | |
| "body": "#A8D8EA", | |
| "body_dark": "#7EC8E3", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "happy": { | |
| "body": "#B5EAD7", | |
| "body_dark": "#8FD8B8", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "sad": { | |
| "body": "#C7CEEA", | |
| "body_dark": "#A8B2D8", | |
| "cheek": "#DDA0DD", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "thinking": { | |
| "body": "#E2D1F9", | |
| "body_dark": "#C9B1E8", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "concerned": { | |
| "body": "#FFDAC1", | |
| "body_dark": "#FFB89A", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "excited": { | |
| "body": "#FFEAA7", | |
| "body_dark": "#FFD93D", | |
| "cheek": "#FF9999", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "sleepy": { | |
| "body": "#DCD6F7", | |
| "body_dark": "#C4BBF0", | |
| "cheek": "#E8C5D6", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "listening": { | |
| "body": "#A8E6CF", | |
| "body_dark": "#88D8B0", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "attentive": { | |
| "body": "#95E1D3", | |
| "body_dark": "#75D1C3", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| "speaking": { | |
| "body": "#B5EAD7", | |
| "body_dark": "#8FD8B8", | |
| "cheek": "#FFB5C5", | |
| "highlight": "#FFFFFF", | |
| "eye": "#2C3E50" | |
| }, | |
| } | |
| def get_pip_svg(state: PipState = "neutral", size: int = 200) -> str: | |
| """ | |
| Generate cute SVG for Pip in the specified emotional state. | |
| """ | |
| colors = COLORS.get(state, COLORS["neutral"]) | |
| # Get components | |
| eyes = _get_cute_eyes(state, colors) | |
| mouth = _get_cute_mouth(state, colors) | |
| extras = _get_cute_extras(state, colors) | |
| animation_class = _get_animation_class(state) | |
| svg = f''' | |
| <div class="pip-container" style="display: flex; justify-content: center; align-items: center; padding: 20px;"> | |
| <style> | |
| {_get_css_animations()} | |
| </style> | |
| <svg width="{size}" height="{size}" viewBox="0 0 200 200" class="pip-svg"> | |
| <defs> | |
| <!-- Cute gradient for body --> | |
| <radialGradient id="bodyGrad-{state}" cx="35%" cy="25%" r="65%"> | |
| <stop offset="0%" style="stop-color:{colors['highlight']};stop-opacity:0.4" /> | |
| <stop offset="30%" style="stop-color:{colors['body']};stop-opacity:1" /> | |
| <stop offset="100%" style="stop-color:{colors['body_dark']};stop-opacity:1" /> | |
| </radialGradient> | |
| <!-- Soft shadow --> | |
| <filter id="softShadow-{state}" x="-20%" y="-20%" width="140%" height="140%"> | |
| <feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="{colors['body_dark']}" flood-opacity="0.3"/> | |
| </filter> | |
| <!-- Glow for happy states --> | |
| <filter id="glow-{state}"> | |
| <feGaussianBlur stdDeviation="4" result="coloredBlur"/> | |
| <feMerge> | |
| <feMergeNode in="coloredBlur"/> | |
| <feMergeNode in="SourceGraphic"/> | |
| </feMerge> | |
| </filter> | |
| <!-- Eye sparkle gradient --> | |
| <radialGradient id="eyeGrad-{state}" cx="30%" cy="30%" r="70%"> | |
| <stop offset="0%" style="stop-color:#FFFFFF;stop-opacity:0.9" /> | |
| <stop offset="100%" style="stop-color:#FFFFFF;stop-opacity:0" /> | |
| </radialGradient> | |
| </defs> | |
| <!-- Main blob body - organic shape --> | |
| <g class="pip-body {animation_class}"> | |
| <ellipse | |
| cx="100" | |
| cy="108" | |
| rx="68" | |
| ry="58" | |
| fill="url(#bodyGrad-{state})" | |
| filter="url(#softShadow-{state})" | |
| /> | |
| <!-- Highlight shine on body --> | |
| <ellipse | |
| cx="75" | |
| cy="85" | |
| rx="20" | |
| ry="12" | |
| fill="{colors['highlight']}" | |
| opacity="0.35" | |
| /> | |
| </g> | |
| <!-- Cute rosy cheeks --> | |
| <ellipse cx="52" cy="115" rx="15" ry="10" fill="{colors['cheek']}" opacity="0.5" class="cheek-left"/> | |
| <ellipse cx="148" cy="115" rx="15" ry="10" fill="{colors['cheek']}" opacity="0.5" class="cheek-right"/> | |
| <!-- Eyes --> | |
| {eyes} | |
| <!-- Mouth --> | |
| {mouth} | |
| <!-- Extras (sparkles, effects, etc.) --> | |
| {extras} | |
| </svg> | |
| </div> | |
| ''' | |
| return svg | |
| def _get_cute_eyes(state: PipState, colors: dict) -> str: | |
| """Generate kawaii-style eyes based on emotional state.""" | |
| eye_color = colors['eye'] | |
| if state in ["happy", "excited"]: | |
| # Happy curved eyes (^_^) - kawaii style | |
| return f''' | |
| <!-- Left happy eye --> | |
| <path d="M 65 95 Q 75 80 85 95" stroke="{eye_color}" stroke-width="4" fill="none" stroke-linecap="round"/> | |
| <!-- Right happy eye --> | |
| <path d="M 115 95 Q 125 80 135 95" stroke="{eye_color}" stroke-width="4" fill="none" stroke-linecap="round"/> | |
| ''' | |
| elif state == "sad": | |
| # Sad eyes with tears | |
| return f''' | |
| <!-- Left sad eye --> | |
| <ellipse cx="75" cy="92" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="78" cy="87" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="79" cy="86" rx="2" ry="2.5" fill="white"/> | |
| <!-- Right sad eye --> | |
| <ellipse cx="125" cy="92" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="128" cy="87" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="129" cy="86" rx="2" ry="2.5" fill="white"/> | |
| <!-- Sad eyebrows --> | |
| <path d="M 58 78 Q 70 82 88 82" stroke="{eye_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/> | |
| <path d="M 142 78 Q 130 82 112 82" stroke="{eye_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/> | |
| ''' | |
| elif state == "thinking": | |
| # Looking up/to side eyes | |
| return f''' | |
| <!-- Left thinking eye --> | |
| <ellipse cx="75" cy="90" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="72" cy="84" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="71" cy="83" rx="2" ry="2.5" fill="white"/> | |
| <!-- Right thinking eye --> | |
| <ellipse cx="125" cy="90" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="122" cy="84" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="121" cy="83" rx="2" ry="2.5" fill="white"/> | |
| ''' | |
| elif state == "concerned": | |
| # Worried eyes | |
| return f''' | |
| <!-- Left worried eye --> | |
| <ellipse cx="75" cy="95" rx="11" ry="13" fill="{eye_color}"/> | |
| <ellipse cx="77" cy="90" rx="4" ry="5" fill="white" opacity="0.9"/> | |
| <ellipse cx="78" cy="89" rx="1.5" ry="2" fill="white"/> | |
| <!-- Right worried eye --> | |
| <ellipse cx="125" cy="95" rx="11" ry="13" fill="{eye_color}"/> | |
| <ellipse cx="127" cy="90" rx="4" ry="5" fill="white" opacity="0.9"/> | |
| <ellipse cx="128" cy="89" rx="1.5" ry="2" fill="white"/> | |
| <!-- Worried eyebrows --> | |
| <path d="M 60 82 Q 68 78 88 88" stroke="{eye_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/> | |
| <path d="M 140 82 Q 132 78 112 88" stroke="{eye_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/> | |
| ''' | |
| elif state == "sleepy": | |
| # Half-closed sleepy eyes | |
| return f''' | |
| <!-- Left sleepy eye --> | |
| <path d="M 63 95 Q 75 102 87 95" stroke="{eye_color}" stroke-width="4" fill="none" stroke-linecap="round"/> | |
| <!-- Right sleepy eye --> | |
| <path d="M 113 95 Q 125 102 137 95" stroke="{eye_color}" stroke-width="4" fill="none" stroke-linecap="round"/> | |
| ''' | |
| elif state in ["listening", "attentive"]: | |
| # Big sparkly attentive eyes | |
| return f''' | |
| <!-- Left big eye --> | |
| <ellipse cx="75" cy="93" rx="14" ry="16" fill="{eye_color}" class="eye-blink"/> | |
| <ellipse cx="79" cy="87" rx="6" ry="7" fill="white" opacity="0.95"/> | |
| <ellipse cx="80" cy="86" rx="2.5" ry="3" fill="white"/> | |
| <ellipse cx="70" cy="96" rx="3" ry="3" fill="white" opacity="0.6"/> | |
| <!-- Right big eye --> | |
| <ellipse cx="125" cy="93" rx="14" ry="16" fill="{eye_color}" class="eye-blink"/> | |
| <ellipse cx="129" cy="87" rx="6" ry="7" fill="white" opacity="0.95"/> | |
| <ellipse cx="130" cy="86" rx="2.5" ry="3" fill="white"/> | |
| <ellipse cx="120" cy="96" rx="3" ry="3" fill="white" opacity="0.6"/> | |
| ''' | |
| elif state == "speaking": | |
| # Animated speaking eyes | |
| return f''' | |
| <!-- Left speaking eye --> | |
| <ellipse cx="75" cy="93" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="78" cy="88" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="79" cy="87" rx="2" ry="2.5" fill="white"/> | |
| <!-- Right speaking eye --> | |
| <ellipse cx="125" cy="93" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="128" cy="88" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="129" cy="87" rx="2" ry="2.5" fill="white"/> | |
| ''' | |
| else: # neutral | |
| # Normal cute eyes with sparkle | |
| return f''' | |
| <!-- Left eye --> | |
| <ellipse cx="75" cy="93" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="78" cy="88" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="79" cy="87" rx="2" ry="2.5" fill="white"/> | |
| <!-- Right eye --> | |
| <ellipse cx="125" cy="93" rx="12" ry="14" fill="{eye_color}"/> | |
| <ellipse cx="128" cy="88" rx="5" ry="6" fill="white" opacity="0.9"/> | |
| <ellipse cx="129" cy="87" rx="2" ry="2.5" fill="white"/> | |
| ''' | |
| def _get_cute_mouth(state: PipState, colors: dict) -> str: | |
| """Generate cute mouth based on emotional state.""" | |
| mouth_color = colors['eye'] | |
| if state == "happy": | |
| # Big happy smile | |
| return f'<path d="M 82 120 Q 100 140 118 120" stroke="{mouth_color}" stroke-width="3" fill="none" stroke-linecap="round"/>' | |
| elif state == "excited": | |
| # Open excited smile | |
| return f''' | |
| <path d="M 78 118 Q 100 145 122 118" stroke="{mouth_color}" stroke-width="3" fill="#FF9999" stroke-linecap="round"/> | |
| <ellipse cx="100" cy="130" rx="8" ry="3" fill="#FF6B6B" opacity="0.5"/> | |
| ''' | |
| elif state == "sad": | |
| # Sad frown | |
| return f'<path d="M 85 130 Q 100 120 115 130" stroke="{mouth_color}" stroke-width="3" fill="none" stroke-linecap="round"/>' | |
| elif state == "thinking": | |
| # Small 'o' thinking mouth | |
| return f'<ellipse cx="100" cy="125" rx="6" ry="5" fill="{mouth_color}" opacity="0.7"/>' | |
| elif state == "concerned": | |
| # Wavy worried mouth | |
| return f'<path d="M 88 125 Q 94 130 100 125 Q 106 120 112 125" stroke="{mouth_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/>' | |
| elif state == "sleepy": | |
| # Small relaxed smile | |
| return f'<path d="M 92 122 Q 100 127 108 122" stroke="{mouth_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/>' | |
| elif state in ["listening", "attentive"]: | |
| # Small attentive 'o' | |
| return f'<ellipse cx="100" cy="123" rx="5" ry="4" fill="{mouth_color}" opacity="0.6"/>' | |
| elif state == "speaking": | |
| # Animated speaking mouth | |
| return f'<ellipse cx="100" cy="123" rx="10" ry="7" fill="{mouth_color}" class="mouth-animate" opacity="0.8"/>' | |
| else: # neutral | |
| # Gentle smile | |
| return f'<path d="M 90 120 Q 100 128 110 120" stroke="{mouth_color}" stroke-width="2.5" fill="none" stroke-linecap="round"/>' | |
| def _get_cute_extras(state: PipState, colors: dict) -> str: | |
| """Generate extra cute decorations based on emotional state.""" | |
| if state == "excited": | |
| # Cute sparkles | |
| return ''' | |
| <g class="sparkles"> | |
| <path d="M 40 60 L 42 68 L 50 68 L 44 73 L 46 81 L 40 76 L 34 81 L 36 73 L 30 68 L 38 68 Z" fill="#FFD700" class="sparkle"/> | |
| <path d="M 160 55 L 162 63 L 170 63 L 164 68 L 166 76 L 160 71 L 154 76 L 156 68 L 150 63 L 158 63 Z" fill="#FFD700" class="sparkle" style="animation-delay: 0.2s"/> | |
| <circle cx="45" cy="45" r="3" fill="#FF69B4" class="sparkle" style="animation-delay: 0.4s"/> | |
| <circle cx="155" cy="40" r="3" fill="#FF69B4" class="sparkle" style="animation-delay: 0.1s"/> | |
| </g> | |
| ''' | |
| elif state == "sad": | |
| # Tear drops | |
| return ''' | |
| <g class="tears"> | |
| <path d="M 68 108 Q 65 118 68 123 Q 71 118 68 108" fill="#89CFF0" class="tear" opacity="0.8"/> | |
| </g> | |
| ''' | |
| elif state == "thinking": | |
| # Thought bubbles | |
| return ''' | |
| <g class="thought-bubbles"> | |
| <circle cx="150" cy="65" r="6" fill="#DDD" opacity="0.8"/> | |
| <circle cx="162" cy="50" r="8" fill="#DDD" opacity="0.8"/> | |
| <circle cx="175" cy="32" r="12" fill="#DDD" opacity="0.8"/> | |
| </g> | |
| ''' | |
| elif state == "concerned": | |
| # Sweat drop | |
| return ''' | |
| <path d="M 145 70 Q 150 82 145 88 Q 140 82 145 70" fill="#89CFF0" opacity="0.7" class="sweat"/> | |
| ''' | |
| elif state == "sleepy": | |
| # Z's floating | |
| return ''' | |
| <g class="zzz"> | |
| <text x="148" y="68" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#9999CC" class="z1">Z</text> | |
| <text x="160" y="52" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#AAAADD" class="z2">z</text> | |
| <text x="168" y="38" font-family="Arial, sans-serif" font-size="11" font-weight="bold" fill="#BBBBEE" class="z3">z</text> | |
| </g> | |
| ''' | |
| elif state in ["listening", "attentive"]: | |
| # Sound/attention waves | |
| return ''' | |
| <g class="attention-waves" opacity="0.4"> | |
| <path d="M 165 90 Q 175 90 175 105 Q 175 120 165 120" stroke="#666" stroke-width="2" fill="none" class="wave1"/> | |
| <path d="M 170 85 Q 185 85 185 105 Q 185 125 170 125" stroke="#666" stroke-width="2" fill="none" class="wave2"/> | |
| </g> | |
| ''' | |
| elif state == "happy": | |
| # Small hearts or sparkles | |
| return ''' | |
| <g class="happy-sparkles"> | |
| <circle cx="50" cy="55" r="2" fill="#FFB5C5"/> | |
| <circle cx="150" cy="50" r="2" fill="#FFB5C5"/> | |
| </g> | |
| ''' | |
| return "" | |
| def _get_animation_class(state: PipState) -> str: | |
| """Get animation class for the blob body.""" | |
| animations = { | |
| "neutral": "anim-gentle", | |
| "happy": "anim-bounce", | |
| "sad": "anim-droop", | |
| "thinking": "anim-sway", | |
| "concerned": "anim-shake", | |
| "excited": "anim-excited", | |
| "sleepy": "anim-breathe", | |
| "listening": "anim-pulse", | |
| "attentive": "anim-lean", | |
| "speaking": "anim-speak", | |
| } | |
| return animations.get(state, "anim-gentle") | |
| def _get_css_animations() -> str: | |
| """Get all CSS animations for Pip.""" | |
| return ''' | |
| /* Base animations */ | |
| @keyframes gentle-wobble { | |
| 0%, 100% { transform: translateY(0) rotate(0deg); } | |
| 25% { transform: translateY(-3px) rotate(-1deg); } | |
| 75% { transform: translateY(-3px) rotate(1deg); } | |
| } | |
| @keyframes happy-bounce { | |
| 0%, 100% { transform: translateY(0) scale(1); } | |
| 50% { transform: translateY(-10px) scale(1.03); } | |
| } | |
| @keyframes excited-bounce { | |
| 0%, 100% { transform: translateY(0) scale(1) rotate(0deg); } | |
| 20% { transform: translateY(-12px) scale(1.05) rotate(-4deg); } | |
| 40% { transform: translateY(-6px) scale(1.02) rotate(0deg); } | |
| 60% { transform: translateY(-12px) scale(1.05) rotate(4deg); } | |
| 80% { transform: translateY(-6px) scale(1.02) rotate(0deg); } | |
| } | |
| @keyframes sad-droop { | |
| 0%, 100% { transform: translateY(0) scaleY(1); } | |
| 50% { transform: translateY(4px) scaleY(0.97); } | |
| } | |
| @keyframes thinking-sway { | |
| 0%, 100% { transform: rotate(0deg) translateX(0); } | |
| 25% { transform: rotate(-4deg) translateX(-3px); } | |
| 75% { transform: rotate(4deg) translateX(3px); } | |
| } | |
| @keyframes worried-shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 20% { transform: translateX(-2px); } | |
| 40% { transform: translateX(2px); } | |
| 60% { transform: translateX(-2px); } | |
| 80% { transform: translateX(2px); } | |
| } | |
| @keyframes sleepy-breathe { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.02); } | |
| } | |
| @keyframes listen-pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.04); } | |
| } | |
| @keyframes attentive-lean { | |
| 0%, 100% { transform: translateY(0) rotate(0deg); } | |
| 50% { transform: translateY(-4px) rotate(3deg); } | |
| } | |
| @keyframes speak-pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 25% { transform: scale(1.02); } | |
| 50% { transform: scale(1); } | |
| 75% { transform: scale(1.02); } | |
| } | |
| /* Decoration animations */ | |
| @keyframes sparkle { | |
| 0%, 100% { opacity: 1; transform: scale(1) rotate(0deg); } | |
| 50% { opacity: 0.6; transform: scale(1.3) rotate(15deg); } | |
| } | |
| @keyframes tear-fall { | |
| 0% { transform: translateY(0); opacity: 0.8; } | |
| 100% { transform: translateY(25px); opacity: 0; } | |
| } | |
| @keyframes float-z { | |
| 0% { opacity: 0; transform: translateY(0) translateX(0); } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0; transform: translateY(-15px) translateX(5px); } | |
| } | |
| @keyframes wave-pulse { | |
| 0%, 100% { opacity: 0.3; transform: scale(1); } | |
| 50% { opacity: 0.6; transform: scale(1.1); } | |
| } | |
| @keyframes blink { | |
| 0%, 90%, 100% { transform: scaleY(1); } | |
| 95% { transform: scaleY(0.1); } | |
| } | |
| @keyframes mouth-speak { | |
| 0%, 100% { transform: scaleY(1) scaleX(1); } | |
| 25% { transform: scaleY(0.6) scaleX(1.1); } | |
| 50% { transform: scaleY(1.1) scaleX(0.9); } | |
| 75% { transform: scaleY(0.7) scaleX(1.05); } | |
| } | |
| @keyframes sweat-drop { | |
| 0%, 100% { transform: translateY(0); opacity: 0.7; } | |
| 50% { transform: translateY(3px); opacity: 0.5; } | |
| } | |
| /* Apply animations */ | |
| .pip-body.anim-gentle { animation: gentle-wobble 3s ease-in-out infinite; } | |
| .pip-body.anim-bounce { animation: happy-bounce 0.7s ease-in-out infinite; } | |
| .pip-body.anim-excited { animation: excited-bounce 0.5s ease-in-out infinite; } | |
| .pip-body.anim-droop { animation: sad-droop 4s ease-in-out infinite; } | |
| .pip-body.anim-sway { animation: thinking-sway 3s ease-in-out infinite; } | |
| .pip-body.anim-shake { animation: worried-shake 0.4s ease-in-out infinite; } | |
| .pip-body.anim-breathe { animation: sleepy-breathe 4s ease-in-out infinite; } | |
| .pip-body.anim-pulse { animation: listen-pulse 1.5s ease-in-out infinite; } | |
| .pip-body.anim-lean { animation: attentive-lean 2s ease-in-out infinite; } | |
| .pip-body.anim-speak { animation: speak-pulse 0.35s ease-in-out infinite; } | |
| /* Decoration animations */ | |
| .sparkle { animation: sparkle 0.8s ease-in-out infinite; } | |
| .tear { animation: tear-fall 2.5s ease-in infinite; } | |
| .z1 { animation: float-z 2s ease-in-out infinite; } | |
| .z2 { animation: float-z 2s ease-in-out infinite 0.4s; } | |
| .z3 { animation: float-z 2s ease-in-out infinite 0.8s; } | |
| .wave1 { animation: wave-pulse 1.2s ease-in-out infinite; } | |
| .wave2 { animation: wave-pulse 1.2s ease-in-out infinite 0.3s; } | |
| .eye-blink { animation: blink 4s ease-in-out infinite; } | |
| .mouth-animate { animation: mouth-speak 0.3s ease-in-out infinite; } | |
| .sweat { animation: sweat-drop 1s ease-in-out infinite; } | |
| /* Cheek hover effect */ | |
| .cheek-left, .cheek-right { | |
| transition: opacity 0.3s ease; | |
| } | |
| ''' | |
| def get_all_states_preview() -> str: | |
| """Generate a preview of all Pip states for testing.""" | |
| states = ["neutral", "happy", "sad", "thinking", "concerned", | |
| "excited", "sleepy", "listening", "attentive", "speaking"] | |
| html = '<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; padding: 20px; background: #1a1a2e; border-radius: 12px;">' | |
| for state in states: | |
| html += f''' | |
| <div style="text-align: center;"> | |
| {get_pip_svg(state, 100)} | |
| <p style="margin-top: 8px; font-size: 12px; color: #888; font-family: sans-serif;">{state}</p> | |
| </div> | |
| ''' | |
| html += '</div>' | |
| html += '<p style="text-align: center; margin-top: 16px; color: #666; font-size: 14px;"><em>Built with 💙 for MCP\'s 1st Birthday Hackathon | Powered by Anthropic, ElevenLabs, OpenAI, Gemini, and HuggingFace</em></p>' | |
| return html | |
| # Map emotions to Pip states | |
| EMOTION_TO_STATE = { | |
| "happy": "happy", | |
| "joy": "happy", | |
| "excited": "excited", | |
| "enthusiastic": "excited", | |
| "proud": "happy", | |
| "grateful": "happy", | |
| "hopeful": "happy", | |
| "content": "happy", | |
| "sad": "sad", | |
| "melancholy": "sad", | |
| "grief": "sad", | |
| "lonely": "sad", | |
| "disappointed": "sad", | |
| "anxious": "concerned", | |
| "worried": "concerned", | |
| "nervous": "concerned", | |
| "stressed": "concerned", | |
| "overwhelmed": "concerned", | |
| "confused": "thinking", | |
| "curious": "thinking", | |
| "thoughtful": "thinking", | |
| "uncertain": "thinking", | |
| "tired": "sleepy", | |
| "exhausted": "sleepy", | |
| "peaceful": "sleepy", | |
| "relaxed": "sleepy", | |
| "calm": "neutral", | |
| "neutral": "neutral", | |
| "angry": "concerned", | |
| "frustrated": "concerned", | |
| "love": "excited", | |
| } | |
| def emotion_to_pip_state(emotions: list, intensity: int = 5) -> PipState: | |
| """ | |
| Convert detected emotions to appropriate Pip visual state. | |
| """ | |
| if not emotions: | |
| return "neutral" | |
| # Get the primary emotion | |
| primary = emotions[0].lower() | |
| # Check for high intensity emotions | |
| if intensity >= 8: | |
| if primary in ["happy", "joy", "enthusiastic", "proud", "grateful"]: | |
| return "excited" | |
| elif primary in ["sad", "grief", "despair", "lonely"]: | |
| return "sad" | |
| elif primary in ["anxious", "worried", "scared", "stressed"]: | |
| return "concerned" | |
| return EMOTION_TO_STATE.get(primary, "neutral") | |