this repo has no description
at fix-ts-uint8array 372 lines 10 kB view raw
1import { keyframes } from '@emotion/react'; 2import Box from '@mui/material/Box'; 3 4export type Mood = 'idle' | 'happy' | 'sad' | 'celebrate' | 'peek' | 'wave' | 'encourage'; 5 6interface RambiProps { 7 mood?: Mood; 8 size?: number; 9} 10 11const bounce = keyframes` 12 0%, 100% { transform: translateY(0); } 13 50% { transform: translateY(-6px); } 14`; 15 16const jump = keyframes` 17 0%, 100% { transform: translateY(0) scale(1); } 18 30% { transform: translateY(-18px) scale(1.05); } 19 50% { transform: translateY(-20px) scale(1.05); } 20 70% { transform: translateY(-10px) scale(1); } 21`; 22 23const droop = keyframes` 24 0%, 100% { transform: translateY(0) rotate(0deg); } 25 50% { transform: translateY(4px) rotate(-3deg); } 26`; 27 28const celebrate = keyframes` 29 0% { transform: rotate(0deg) scale(1); } 30 25% { transform: rotate(10deg) scale(1.1); } 31 50% { transform: rotate(-10deg) scale(1.1); } 32 75% { transform: rotate(5deg) scale(1.05); } 33 100% { transform: rotate(0deg) scale(1); } 34`; 35 36const peekUp = keyframes` 37 0% { transform: translateY(60%); opacity: 0; } 38 100% { transform: translateY(0); opacity: 1; } 39`; 40 41const waveArm = keyframes` 42 0%, 100% { transform: rotate(0deg); } 43 25% { transform: rotate(-20deg); } 44 50% { transform: rotate(20deg); } 45 75% { transform: rotate(-10deg); } 46`; 47 48const nod = keyframes` 49 0%, 100% { transform: translateY(0) rotate(0deg); } 50 30% { transform: translateY(3px) rotate(5deg); } 51 60% { transform: translateY(1px) rotate(-3deg); } 52`; 53 54function getAnimation(mood: Mood) { 55 switch (mood) { 56 case 'idle': 57 return `${bounce} 2s ease-in-out infinite`; 58 case 'happy': 59 return `${jump} 0.8s ease-in-out infinite`; 60 case 'sad': 61 return `${droop} 2s ease-in-out infinite`; 62 case 'celebrate': 63 return `${celebrate} 0.6s ease-in-out infinite`; 64 case 'peek': 65 return `${peekUp} 0.5s ease-out forwards`; 66 case 'wave': 67 return `${bounce} 2s ease-in-out infinite`; 68 case 'encourage': 69 return `${nod} 1.2s ease-in-out infinite`; 70 default: 71 return 'none'; 72 } 73} 74 75function getEyes(mood: Mood): [string, string] { 76 switch (mood) { 77 case 'happy': 78 case 'celebrate': 79 return ['\u2303', '\u2303']; // ^ ^ 80 case 'sad': 81 return [';', ';']; 82 case 'peek': 83 return ['\u25CF', '\u25CF']; // big round eyes 84 case 'encourage': 85 return ['\u25CF', '\u25CF']; 86 case 'wave': 87 return ['\u25CF', '\u2012']; // wink 88 default: 89 return ['\u25CF', '\u25CF']; 90 } 91} 92 93function getMouth(mood: Mood): string { 94 switch (mood) { 95 case 'happy': 96 case 'celebrate': 97 case 'wave': 98 return 'D'; 99 case 'sad': 100 return '\u2323'; // frown 101 case 'encourage': 102 return '\u25E1'; // warm smile 103 default: 104 return '\u25E1'; // smile 105 } 106} 107 108export function Rambi({ mood = 'idle', size = 120 }: RambiProps) { 109 const [leftEye, rightEye] = getEyes(mood); 110 const mouth = getMouth(mood); 111 const s = size; 112 const cx = s / 2; 113 const cy = s / 2; 114 const r = s * 0.3; 115 116 // Spike positions around the circle 117 const spikeCount = 12; 118 const spikes = Array.from({ length: spikeCount }, (_, i) => { 119 const angle = (i / spikeCount) * Math.PI * 2 - Math.PI / 2; 120 const innerR = r + 2; 121 const outerR = r + s * 0.13; 122 const x1 = cx + Math.cos(angle - 0.15) * innerR; 123 const y1 = cy + Math.sin(angle - 0.15) * innerR; 124 const x2 = cx + Math.cos(angle) * outerR; 125 const y2 = cy + Math.sin(angle) * outerR; 126 const x3 = cx + Math.cos(angle + 0.15) * innerR; 127 const y3 = cy + Math.sin(angle + 0.15) * innerR; 128 return `M${x1},${y1} Q${x2},${y2} ${x3},${y3}`; 129 }); 130 131 // Arm positions 132 const armLength = s * 0.15; 133 const leftArmStart = { x: cx - r * 0.8, y: cy + r * 0.4 }; 134 const rightArmStart = { x: cx + r * 0.8, y: cy + r * 0.4 }; 135 136 // Leg positions 137 const legLength = s * 0.13; 138 const leftLegStart = { x: cx - r * 0.35, y: cy + r }; 139 const rightLegStart = { x: cx + r * 0.35, y: cy + r }; 140 141 const isWaving = mood === 'wave'; 142 143 return ( 144 <Box 145 sx={{ 146 display: 'inline-flex', 147 animation: getAnimation(mood), 148 }} 149 > 150 <svg 151 width={s} 152 height={s} 153 viewBox={`0 0 ${s} ${s}`} 154 xmlns="http://www.w3.org/2000/svg" 155 > 156 {/* Spikes / hair */} 157 {spikes.map((d, i) => ( 158 <path 159 key={i} 160 d={d} 161 fill="none" 162 stroke={i % 2 === 0 ? '#E03030' : '#FF6B35'} 163 strokeWidth={s * 0.035} 164 strokeLinecap="round" 165 /> 166 ))} 167 168 {/* Body */} 169 <circle cx={cx} cy={cy} r={r} fill="#E03030" /> 170 171 {/* Highlight */} 172 <circle 173 cx={cx - r * 0.25} 174 cy={cy - r * 0.3} 175 r={r * 0.15} 176 fill="rgba(255,255,255,0.25)" 177 /> 178 179 {/* Eyes */} 180 <text 181 x={cx - r * 0.3} 182 y={cy - r * 0.05} 183 textAnchor="middle" 184 fontSize={s * 0.1} 185 fill="white" 186 fontFamily="sans-serif" 187 > 188 {leftEye} 189 </text> 190 <text 191 x={cx + r * 0.3} 192 y={cy - r * 0.05} 193 textAnchor="middle" 194 fontSize={s * 0.1} 195 fill="white" 196 fontFamily="sans-serif" 197 > 198 {rightEye} 199 </text> 200 201 {/* Eye pupils (for non-kaomoji eyes) */} 202 {mood !== 'happy' && mood !== 'celebrate' && mood !== 'sad' && ( 203 <> 204 <circle 205 cx={cx - r * 0.3} 206 cy={cy - r * 0.1} 207 r={s * 0.025} 208 fill="white" 209 /> 210 <circle 211 cx={cx + r * 0.3} 212 cy={cy - r * 0.1} 213 r={s * 0.025} 214 fill="white" 215 /> 216 </> 217 )} 218 219 {/* Mouth */} 220 <text 221 x={cx} 222 y={cy + r * 0.4} 223 textAnchor="middle" 224 fontSize={s * 0.12} 225 fill="white" 226 fontFamily="sans-serif" 227 > 228 {mouth} 229 </text> 230 231 {/* Cheeks (blush) */} 232 <circle 233 cx={cx - r * 0.6} 234 cy={cy + r * 0.15} 235 r={s * 0.04} 236 fill="rgba(255,150,150,0.5)" 237 /> 238 <circle 239 cx={cx + r * 0.6} 240 cy={cy + r * 0.15} 241 r={s * 0.04} 242 fill="rgba(255,150,150,0.5)" 243 /> 244 245 {/* Left arm */} 246 <line 247 x1={leftArmStart.x} 248 y1={leftArmStart.y} 249 x2={leftArmStart.x - armLength} 250 y2={leftArmStart.y + armLength * 0.6} 251 stroke="#E03030" 252 strokeWidth={s * 0.03} 253 strokeLinecap="round" 254 /> 255 256 {/* Right arm (waves when mood is 'wave') */} 257 <line 258 x1={rightArmStart.x} 259 y1={rightArmStart.y} 260 x2={rightArmStart.x + armLength} 261 y2={ 262 isWaving 263 ? rightArmStart.y - armLength 264 : rightArmStart.y + armLength * 0.6 265 } 266 stroke="#E03030" 267 strokeWidth={s * 0.03} 268 strokeLinecap="round" 269 style={ 270 isWaving 271 ? { 272 transformOrigin: `${rightArmStart.x}px ${rightArmStart.y}px`, 273 animation: `${waveArm} 0.6s ease-in-out infinite`, 274 } 275 : undefined 276 } 277 /> 278 279 {/* Left leg */} 280 <line 281 x1={leftLegStart.x} 282 y1={leftLegStart.y} 283 x2={leftLegStart.x - s * 0.03} 284 y2={leftLegStart.y + legLength} 285 stroke="#E03030" 286 strokeWidth={s * 0.03} 287 strokeLinecap="round" 288 /> 289 290 {/* Right leg */} 291 <line 292 x1={rightLegStart.x} 293 y1={rightLegStart.y} 294 x2={rightLegStart.x + s * 0.03} 295 y2={rightLegStart.y + legLength} 296 stroke="#E03030" 297 strokeWidth={s * 0.03} 298 strokeLinecap="round" 299 /> 300 301 {/* Confetti for celebrate mood */} 302 {mood === 'celebrate' && ( 303 <> 304 <circle cx={cx - r * 1.2} cy={cy - r * 0.8} r={3} fill="#FFC800"> 305 <animate 306 attributeName="cy" 307 values={`${cy - r * 0.8};${cy + r * 1.2}`} 308 dur="1s" 309 repeatCount="indefinite" 310 /> 311 <animate 312 attributeName="opacity" 313 values="1;0" 314 dur="1s" 315 repeatCount="indefinite" 316 /> 317 </circle> 318 <circle cx={cx + r * 1.1} cy={cy - r} r={3} fill="#CE82FF"> 319 <animate 320 attributeName="cy" 321 values={`${cy - r};${cy + r * 1.3}`} 322 dur="1.2s" 323 repeatCount="indefinite" 324 /> 325 <animate 326 attributeName="opacity" 327 values="1;0" 328 dur="1.2s" 329 repeatCount="indefinite" 330 /> 331 </circle> 332 <circle cx={cx} cy={cy - r * 1.3} r={2.5} fill="#4CAF50"> 333 <animate 334 attributeName="cy" 335 values={`${cy - r * 1.3};${cy + r}`} 336 dur="0.9s" 337 repeatCount="indefinite" 338 /> 339 <animate 340 attributeName="opacity" 341 values="1;0" 342 dur="0.9s" 343 repeatCount="indefinite" 344 /> 345 </circle> 346 <rect 347 x={cx - r * 0.9} 348 y={cy - r * 1.1} 349 width={4} 350 height={4} 351 fill="#FF4B4B" 352 transform={`rotate(45 ${cx - r * 0.9} ${cy - r * 1.1})`} 353 > 354 <animate 355 attributeName="y" 356 values={`${cy - r * 1.1};${cy + r * 1.2}`} 357 dur="1.1s" 358 repeatCount="indefinite" 359 /> 360 <animate 361 attributeName="opacity" 362 values="1;0" 363 dur="1.1s" 364 repeatCount="indefinite" 365 /> 366 </rect> 367 </> 368 )} 369 </svg> 370 </Box> 371 ); 372}