[READ-ONLY] Mirror of https://github.com/flo-bit/streamplace-overlay. flo-bit.dev/streamplace-overlay/
0

Configure Feed

Select the types of activity you want to include in your feed.

streamplace-overlay / pets.css
25 kB 1032 lines
1/* ===== CSS Custom Properties (Design Tokens) ===== */ 2 :root { 3 --font-system: 4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; 5 --font-mono: ui-monospace, "SF Mono", Menlo, monospace; 6 --font-comic: 7 "Comic Sans MS", "Comic Sans", "Chalkboard SE", "Marker Felt", 8 system-ui, sans-serif; 9 --color-text-dim: rgba(255, 255, 255, 0.45); 10 --color-text-faint: rgba(255, 255, 255, 0.35); 11 --color-bg-overlay: rgba(20, 20, 28, 0.85); 12 --color-bg-pill: rgba(0, 0, 0, 0.32); 13 --color-shadow-text: 0 0 3px rgba(0, 0, 0, 0.8); 14 --color-shadow-pet: 0 6px 8px rgba(0, 0, 0, 0.35); 15 --pet-size: 110px; 16 --pet-bottom: 14px; 17 --anim-speed-default: 1; 18 --rate-limit-glow: drop-shadow(0 0 10px rgba(255, 40, 40, 0.95)) 19 drop-shadow(0 0 22px rgba(255, 0, 0, 0.65)); 20 } 21 22 html, 23 body { 24 margin: 0; 25 padding: 0; 26 width: 100%; 27 height: 100%; 28 background: transparent; 29 overflow: hidden; 30 font-family: var(--font-system); 31 } 32 33 #stage { 34 position: fixed; 35 left: 0; 36 right: 0; 37 bottom: 0; 38 height: 200px; 39 pointer-events: none; 40 } 41 42 #status { 43 position: fixed; 44 top: 6px; 45 right: 8px; 46 font-size: 10px; 47 color: var(--color-text-dim); 48 font-family: var(--font-mono); 49 pointer-events: none; 50 text-shadow: var(--color-shadow-text); 51 } 52 #status.hidden { 53 display: none; 54 } 55 56 #version { 57 position: fixed; 58 bottom: 4px; 59 right: 8px; 60 font-size: 10px; 61 color: var(--color-text-faint); 62 font-family: var(--font-mono); 63 pointer-events: none; 64 text-shadow: var(--color-shadow-text); 65 } 66 67 #reset-btn { 68 position: fixed; 69 bottom: 4px; 70 left: 8px; 71 width: 24px; 72 height: 24px; 73 background: transparent; 74 border: none; 75 color: var(--color-text-faint); 76 font-size: 14px; 77 cursor: pointer; 78 opacity: 0; 79 transition: opacity 0.3s; 80 z-index: 999; 81 pointer-events: auto; 82 } 83 #reset-btn:hover { 84 opacity: 0.8; 85 color: rgba(255, 255, 255, 0.8); 86 } 87 #stage:hover ~ #reset-btn, 88 #reset-btn:hover, 89 #reset-btn:focus-visible { 90 opacity: 0.6; 91 } 92 93 #reload-toast { 94 position: fixed; 95 top: 24px; 96 left: 50%; 97 transform: translateX(-50%) translateY(-12px); 98 padding: 8px 18px; 99 background: var(--color-bg-overlay); 100 color: #fff; 101 font-size: 13px; 102 font-weight: 600; 103 font-family: var(--font-mono); 104 letter-spacing: 0.06em; 105 border-radius: 999px; 106 border: 1px solid rgba(255, 255, 255, 0.18); 107 box-shadow: 0 4px 18px rgba(0, 0, 0, 0.5); 108 pointer-events: none; 109 opacity: 0; 110 z-index: 500; 111 animation: reload-toast 3.6s ease-out forwards; 112 } 113 @keyframes reload-toast { 114 0% { 115 opacity: 0; 116 transform: translateX(-50%) translateY(-12px); 117 } 118 8% { 119 opacity: 1; 120 transform: translateX(-50%) translateY(0); 121 } 122 80% { 123 opacity: 1; 124 transform: translateX(-50%) translateY(0); 125 } 126 100% { 127 opacity: 0; 128 transform: translateX(-50%) translateY(-8px); 129 } 130 } 131 132 /* ===== Pet ===== */ 133 .pet { 134 position: absolute; 135 bottom: var(--pet-bottom); 136 left: 0; 137 width: var(--pet-size); 138 height: var(--pet-size); 139 transform-origin: 50% 100%; 140 will-change: transform; 141 filter: var(--color-shadow-pet); 142 --anim-speed: var(--anim-speed-default); 143 } 144 145 .pet-flip { 146 width: 100%; 147 height: 100%; 148 transform-origin: 50% 100%; 149 position: relative; 150 transition: filter 0.25s ease; 151 } 152 .pet-flip svg { 153 width: 100%; 154 height: 100%; 155 overflow: visible; 156 } 157 158 /* Rate-limit indicator — red glow around the pet */ 159 .pet.rate-limited .pet-flip { 160 filter: var(--rate-limit-glow); 161 } 162 163 .rpg-sprite { 164 position: absolute; 165 bottom: 0; 166 left: 50%; 167 background-repeat: no-repeat; 168 image-rendering: pixelated; 169 image-rendering: crisp-edges; 170 } 171 172 /* ===== Label (handle pill + chat bubble) ===== */ 173 .pet-label { 174 position: absolute; 175 left: 50%; 176 transform: translateX(-50%); 177 font-size: 11px; 178 font-weight: 700; 179 color: #fff; 180 text-shadow: 181 -1px -1px 0 #000, 182 1px -1px 0 #000, 183 -1px 1px 0 #000, 184 1px 1px 0 #000, 185 0 0 6px rgba(0, 0, 0, 0.85); 186 white-space: nowrap; 187 overflow: hidden; 188 text-overflow: ellipsis; 189 max-width: 240px; 190 pointer-events: none; 191 letter-spacing: 0.02em; 192 background: var(--color-bg-pill); 193 padding: 1px 6px; 194 border-radius: 999px; 195 backdrop-filter: blur(2px); 196 transition: 197 background-color 0.18s, 198 color 0.18s; 199 } 200 .pet-label.chatting { 201 background: rgba(255, 255, 255, 0.96); 202 color: #1a1a1a; 203 text-shadow: none; 204 font-weight: 600; 205 font-family: var(--font-comic); 206 border-radius: 6px; 207 padding: 3px 8px; 208 border: 2px solid #1a1a1a; 209 box-shadow: 2px 2px 0 #1a1a1a; 210 white-space: normal; 211 text-align: center; 212 line-height: 1.25; 213 min-width: 80px; 214 max-width: min(80ch, 540px); 215 display: -webkit-box; 216 -webkit-line-clamp: 3; 217 -webkit-box-orient: vertical; 218 overflow: hidden; 219 word-break: break-word; 220 } 221 222 /* ===== Animations (all use calc() with --anim-speed) ===== */ 223 .pet:not(.sprite) .body { 224 transform-origin: 50% 100%; 225 animation: bob calc(0.42s / var(--anim-speed)) ease-in-out infinite; 226 } 227 .pet:not(.sprite).idle .body { 228 animation: breathe calc(2.6s / var(--anim-speed)) ease-in-out infinite; 229 } 230 .pet:not(.sprite).sleep .body { 231 animation: sleep-breathe calc(3.4s / var(--anim-speed)) ease-in-out 232 infinite; 233 } 234 235 @keyframes bob { 236 0%, 237 100% { 238 transform: translateY(0); 239 } 240 50% { 241 transform: translateY(-3px); 242 } 243 } 244 @keyframes breathe { 245 0%, 246 100% { 247 transform: scaleY(1) scaleX(1); 248 } 249 50% { 250 transform: scaleY(0.97) scaleX(1.02); 251 } 252 } 253 @keyframes sleep-breathe { 254 0%, 255 100% { 256 transform: scaleY(0.96) scaleX(1.04) translateY(2px); 257 } 258 50% { 259 transform: scaleY(0.92) scaleX(1.06) translateY(4px); 260 } 261 } 262 263 .pet.bunny .body { 264 animation: hop calc(0.55s / var(--anim-speed)) 265 cubic-bezier(0.4, 0, 0.2, 1) infinite; 266 } 267 .pet.bunny.idle .body { 268 animation: breathe calc(2.6s / var(--anim-speed)) ease-in-out infinite; 269 } 270 .pet.bunny.sleep .body { 271 animation: sleep-breathe calc(3.4s / var(--anim-speed)) ease-in-out 272 infinite; 273 } 274 @keyframes hop { 275 0%, 276 100% { 277 transform: translateY(0) scaleY(1); 278 } 279 25% { 280 transform: translateY(-22px) scaleY(1.04); 281 } 282 50% { 283 transform: translateY(0) scaleY(0.94); 284 } 285 75% { 286 transform: translateY(0) scaleY(1.02); 287 } 288 } 289 290 .pet.duck .body { 291 animation: waddle calc(0.55s / var(--anim-speed)) ease-in-out infinite; 292 } 293 .pet.duck.idle .body { 294 animation: breathe calc(2.6s / var(--anim-speed)) ease-in-out infinite; 295 } 296 .pet.duck.sleep .body { 297 animation: sleep-breathe calc(3.4s / var(--anim-speed)) ease-in-out 298 infinite; 299 } 300 @keyframes waddle { 301 0%, 302 100% { 303 transform: rotate(-5deg) translateY(0); 304 } 305 50% { 306 transform: rotate(5deg) translateY(-1px); 307 } 308 } 309 310 .pet.goose .body { 311 animation: goose-strut calc(0.7s / var(--anim-speed)) ease-in-out 312 infinite; 313 } 314 .pet.goose.idle .body { 315 animation: breathe calc(2.6s / var(--anim-speed)) ease-in-out infinite; 316 } 317 .pet.goose.sleep .body { 318 animation: sleep-breathe calc(3.4s / var(--anim-speed)) ease-in-out 319 infinite; 320 } 321 @keyframes goose-strut { 322 0%, 323 100% { 324 transform: rotate(-3deg) translateY(0); 325 } 326 50% { 327 transform: rotate(3deg) translateY(-2px); 328 } 329 } 330 331 .tail-wag { 332 transform-origin: 0% 50%; 333 animation: wag calc(0.5s / var(--anim-speed)) ease-in-out infinite; 334 } 335 .tail-swish { 336 transform-origin: 0% 100%; 337 animation: swish calc(1.6s / var(--anim-speed)) ease-in-out infinite; 338 } 339 @keyframes wag { 340 0%, 341 100% { 342 transform: rotate(-15deg); 343 } 344 50% { 345 transform: rotate(15deg); 346 } 347 } 348 @keyframes swish { 349 0%, 350 100% { 351 transform: rotate(-8deg); 352 } 353 50% { 354 transform: rotate(20deg); 355 } 356 } 357 358 .leg-front { 359 transform-origin: 50% 0%; 360 animation: leg-a calc(0.42s / var(--anim-speed)) ease-in-out infinite; 361 } 362 .leg-back { 363 transform-origin: 50% 0%; 364 animation: leg-b calc(0.42s / var(--anim-speed)) ease-in-out infinite; 365 } 366 @keyframes leg-a { 367 0%, 368 100% { 369 transform: rotate(20deg); 370 } 371 50% { 372 transform: rotate(-20deg); 373 } 374 } 375 @keyframes leg-b { 376 0%, 377 100% { 378 transform: rotate(-20deg); 379 } 380 50% { 381 transform: rotate(20deg); 382 } 383 } 384 .pet.idle .leg-front, 385 .pet.idle .leg-back, 386 .pet.sleep .leg-front, 387 .pet.sleep .leg-back { 388 animation: none; 389 transform: rotate(0); 390 } 391 392 .eye-lid { 393 transform-origin: 50% 50%; 394 transform: scaleY(0); 395 animation: blink calc(4s / var(--anim-speed)) ease-in-out infinite; 396 } 397 @keyframes blink { 398 0%, 399 92%, 400 100% { 401 transform: scaleY(0); 402 } 403 94%, 404 96% { 405 transform: scaleY(1); 406 } 407 } 408 .pet.sleep .eye-lid { 409 transform: scaleY(1); 410 animation: none; 411 } 412 413 /* ===== Zzz, Heart, Hand ===== */ 414 .zzz { 415 position: absolute; 416 top: -8px; 417 right: 8px; 418 width: 36px; 419 height: 36px; 420 opacity: 0; 421 pointer-events: none; 422 } 423 .pet.sleep .zzz { 424 animation: zzz-float 2.2s ease-in-out infinite; 425 } 426 @keyframes zzz-float { 427 0% { 428 opacity: 0; 429 transform: translate(0, 0) scale(0.6); 430 } 431 25% { 432 opacity: 1; 433 } 434 100% { 435 opacity: 0; 436 transform: translate(14px, -28px) scale(1.1); 437 } 438 } 439 440 .heart { 441 position: absolute; 442 top: 8px; 443 left: 50%; 444 width: 26px; 445 height: 26px; 446 transform: translateX(-50%) scale(0); 447 pointer-events: none; 448 } 449 .pet.happy .heart { 450 animation: heart-pop 1.2s ease-out forwards; 451 } 452 @keyframes heart-pop { 453 0% { 454 transform: translate(-50%, 0) scale(0); 455 opacity: 0; 456 } 457 20% { 458 transform: translate(-50%, -10px) scale(1.3); 459 opacity: 1; 460 } 461 100% { 462 transform: translate(-50%, -44px) scale(0.85); 463 opacity: 0; 464 } 465 } 466 467 .pet-rub { 468 position: absolute; 469 top: -22px; 470 left: 50%; 471 width: 36px; 472 height: 36px; 473 margin-left: -18px; 474 pointer-events: none; 475 opacity: 0; 476 will-change: transform; 477 filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)); 478 } 479 .pet.petted .pet-rub { 480 animation: pet-rub 1.8s ease-in-out forwards; 481 } 482 @keyframes pet-rub { 483 0% { 484 opacity: 0; 485 transform: translateY(8px) rotate(-12deg); 486 } 487 10% { 488 opacity: 1; 489 transform: translateY(2px) rotate(-12deg); 490 } 491 25% { 492 transform: translateY(-4px) rotate(8deg); 493 } 494 40% { 495 transform: translateY(2px) rotate(-12deg); 496 } 497 55% { 498 transform: translateY(-4px) rotate(8deg); 499 } 500 70% { 501 transform: translateY(2px) rotate(-12deg); 502 } 503 85% { 504 transform: translateY(-4px) rotate(8deg); 505 opacity: 1; 506 } 507 100% { 508 transform: translateY(-12px) rotate(0deg); 509 opacity: 0; 510 } 511 } 512 513 .pet.spawning { 514 animation: spawn-in 0.6s ease-out; 515 } 516 @keyframes spawn-in { 517 0% { 518 opacity: 0; 519 } 520 100% { 521 opacity: 1; 522 } 523 } 524 525 /* ===== Spawn star burst ===== */ 526 .fx-spawn-stars { 527 position: fixed; 528 pointer-events: none; 529 z-index: 300; 530 overflow: visible; 531 } 532 .star-particle { 533 position: absolute; 534 animation: star-burst 0.9s ease-out forwards; 535 pointer-events: none; 536 line-height: 1; 537 user-select: none; 538 } 539 @keyframes star-burst { 540 0% { 541 opacity: 1; 542 transform: translate(0, 0) scale(1); 543 } 544 100% { 545 opacity: 0; 546 transform: translate(var(--star-dx, 0), var(--star-dy, -80px)) 547 scale(0.2); 548 } 549 } 550 551 /* ===== Bang command effects (scene-wide overlays) ===== */ 552 @keyframes fx-fade-out { 553 0% { 554 opacity: 1; 555 } 556 100% { 557 opacity: 0; 558 } 559 } 560 561 .fx-party { 562 position: fixed; 563 inset: 0; 564 pointer-events: none; 565 z-index: 200; 566 mix-blend-mode: screen; 567 animation: fx-fade-out 0.6s ease-out 7s forwards; 568 } 569 .party-light { 570 position: absolute; 571 width: 60vw; 572 height: 60vw; 573 border-radius: 50%; 574 filter: blur(48px); 575 opacity: 0.85; 576 will-change: transform; 577 } 578 .party-1 { 579 background: radial-gradient(circle, #ff3a8e 0%, transparent 60%); 580 animation: party-orbit 3.6s linear infinite; 581 } 582 .party-2 { 583 background: radial-gradient(circle, #3affc8 0%, transparent 60%); 584 animation: party-orbit 3.6s linear infinite -0.9s; 585 } 586 .party-3 { 587 background: radial-gradient(circle, #ffd93d 0%, transparent 60%); 588 animation: party-orbit 3.6s linear infinite -1.8s; 589 } 590 .party-4 { 591 background: radial-gradient(circle, #6a8eff 0%, transparent 60%); 592 animation: party-orbit 3.6s linear infinite -2.7s; 593 } 594 @keyframes party-orbit { 595 0% { 596 transform: translate(-30vw, -20vh); 597 } 598 25% { 599 transform: translate(70vw, -30vh); 600 } 601 50% { 602 transform: translate(80vw, 60vh); 603 } 604 75% { 605 transform: translate(-30vw, 50vh); 606 } 607 100% { 608 transform: translate(-30vw, -20vh); 609 } 610 } 611 612 .fx-snow, 613 .fx-confetti, 614 .fx-hearts, 615 .fx-rain { 616 position: fixed; 617 inset: 0; 618 pointer-events: none; 619 overflow: hidden; 620 z-index: 150; 621 animation: fx-fade-out 1s ease-out forwards; 622 } 623 .fx-snow { 624 animation-delay: 13s; 625 } 626 .fx-confetti { 627 animation-delay: 7s; 628 } 629 .fx-hearts { 630 animation-delay: 6s; 631 } 632 .fx-rain { 633 animation-delay: 11s; 634 } 635 636 .raindrop { 637 position: absolute; 638 top: -30px; 639 width: 2px; 640 background: linear-gradient(transparent, #6cb8ff 60%, #b3dbff); 641 border-radius: 1px; 642 animation-name: rain-fall; 643 animation-timing-function: linear; 644 animation-iteration-count: 1; 645 animation-fill-mode: forwards; 646 } 647 @keyframes rain-fall { 648 0% { 649 transform: translateY(0); 650 } 651 100% { 652 transform: translateY(110vh); 653 } 654 } 655 656 .snowflake { 657 position: absolute; 658 top: -30px; 659 color: white; 660 text-shadow: 661 0 0 6px rgba(150, 200, 255, 0.7), 662 0 0 12px rgba(255, 255, 255, 0.4); 663 animation-name: snow-fall; 664 animation-timing-function: linear; 665 animation-iteration-count: 1; 666 animation-fill-mode: forwards; 667 user-select: none; 668 } 669 @keyframes snow-fall { 670 0% { 671 transform: translateY(0) translateX(0); 672 } 673 100% { 674 transform: translateY(110vh) translateX(var(--sway, 0)); 675 } 676 } 677 678 .confetti-piece { 679 position: absolute; 680 top: -30px; 681 border-radius: 2px; 682 animation-name: confetti-fall; 683 animation-timing-function: linear; 684 animation-iteration-count: 1; 685 animation-fill-mode: forwards; 686 } 687 @keyframes confetti-fall { 688 0% { 689 transform: translate(0, 0) rotate(0deg); 690 } 691 100% { 692 transform: translate(var(--sway, 0), 110vh) 693 rotate(var(--spin, 720deg)); 694 } 695 } 696 697 .heart-piece { 698 position: absolute; 699 bottom: -40px; 700 font-size: 24px; 701 animation-name: heart-rise; 702 animation-timing-function: ease-out; 703 animation-iteration-count: 1; 704 animation-fill-mode: forwards; 705 filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4)); 706 user-select: none; 707 } 708 @keyframes heart-rise { 709 0% { 710 transform: translate(0, 0) scale(0.4); 711 opacity: 0; 712 } 713 15% { 714 opacity: 1; 715 } 716 100% { 717 transform: translate(var(--sway, 0), -110vh) 718 scale(var(--end-scale, 1.1)); 719 opacity: 0; 720 } 721 } 722 723 .fx-barrelroll { 724 transform-origin: 50% 50%; 725 animation: barrelroll 1.4s cubic-bezier(0.5, 0, 0.5, 1) forwards; 726 } 727 @keyframes barrelroll { 728 0% { 729 transform: rotate(0deg); 730 } 731 100% { 732 transform: rotate(360deg); 733 } 734 } 735 736 /* !fireworks — scene-wide bursts */ 737 .fx-fireworks { 738 position: fixed; 739 inset: 0; 740 pointer-events: none; 741 overflow: hidden; 742 z-index: 160; 743 animation: fx-fade-out 1s ease-out 7.5s forwards; 744 } 745 .firework-particle { 746 position: absolute; 747 width: 6px; 748 height: 6px; 749 border-radius: 50%; 750 box-shadow: 0 0 12px 2px rgba(255, 255, 255, 0.55); 751 opacity: 0; 752 animation-name: firework-burst; 753 animation-timing-function: cubic-bezier(0.25, 0.8, 0.35, 1); 754 animation-fill-mode: forwards; 755 } 756 @keyframes firework-burst { 757 0% { 758 transform: translate(0, 0) scale(0.5); 759 opacity: 0; 760 } 761 8% { 762 opacity: 1; 763 } 764 100% { 765 transform: translate(var(--dx), var(--dy)) scale(0.4); 766 opacity: 0; 767 } 768 } 769 770 .fx-quake { 771 animation: quake 0.55s linear; 772 } 773 @keyframes quake { 774 0%, 775 100% { 776 transform: translate(0, 0); 777 } 778 8% { 779 transform: translate(-12px, 5px); 780 } 781 18% { 782 transform: translate(10px, -4px); 783 } 784 28% { 785 transform: translate(-8px, 6px); 786 } 787 38% { 788 transform: translate(7px, -3px); 789 } 790 48% { 791 transform: translate(-6px, 5px); 792 } 793 58% { 794 transform: translate(5px, -4px); 795 } 796 68% { 797 transform: translate(-4px, 3px); 798 } 799 78% { 800 transform: translate(3px, -2px); 801 } 802 88% { 803 transform: translate(-1px, 1px); 804 } 805 } 806 807 /* Jump */ 808 .pet.jumping .pet-flip { 809 animation: jump 0.75s cubic-bezier(0.3, 0, 0.3, 1); 810 } 811 @keyframes jump { 812 0% { 813 translate: 0 0; 814 } 815 45% { 816 translate: 0 -58px; 817 } 818 100% { 819 translate: 0 0; 820 } 821 } 822 823 /* Dance */ 824 .pet.dancing .pet-flip { 825 animation: dance 3s cubic-bezier(0.45, 0, 0.45, 1); 826 } 827 @keyframes dance { 828 0% { 829 translate: 0 0; 830 rotate: 0deg; 831 scale: 1; 832 } 833 5% { 834 translate: -22px -2px; 835 rotate: -32deg; 836 scale: 1; 837 } 838 10% { 839 translate: 18px -16px; 840 rotate: 28deg; 841 scale: 1.06; 842 } 843 15% { 844 translate: -22px -2px; 845 rotate: -32deg; 846 scale: 1; 847 } 848 20% { 849 translate: 18px -16px; 850 rotate: 28deg; 851 scale: 1.06; 852 } 853 25% { 854 translate: 0 0; 855 rotate: 0deg; 856 scale: 1; 857 } 858 35% { 859 translate: 0 -55px; 860 rotate: 360deg; 861 scale: 1.12; 862 } 863 45% { 864 translate: 0 -25px; 865 rotate: 720deg; 866 scale: 1; 867 } 868 52% { 869 translate: -22px 0; 870 rotate: -32deg; 871 scale: 1; 872 } 873 58% { 874 translate: 20px -14px; 875 rotate: 30deg; 876 scale: 1.06; 877 } 878 64% { 879 translate: -22px 0; 880 rotate: -32deg; 881 scale: 1; 882 } 883 70% { 884 translate: 18px -14px; 885 rotate: 28deg; 886 scale: 1.06; 887 } 888 76% { 889 translate: 0 -2px; 890 rotate: 0deg; 891 scale: 1; 892 } 893 86% { 894 translate: 0 -40px; 895 rotate: 380deg; 896 scale: 1.18; 897 } 898 96% { 899 translate: 0 -2px; 900 rotate: 0deg; 901 scale: 0.94; 902 } 903 100% { 904 translate: 0 0; 905 rotate: 0deg; 906 scale: 1; 907 } 908 } 909 910 /* Barrel roll — Star Fox style: arc up while rotating 360 */ 911 .pet.rolling .pet-flip { 912 animation: barrel-roll 0.75s cubic-bezier(0.4, 0, 0.4, 1); 913 } 914 @keyframes barrel-roll { 915 0% { 916 translate: 0 0; 917 rotate: 0deg; 918 } 919 35% { 920 translate: 0 -32px; 921 rotate: 130deg; 922 } 923 65% { 924 translate: 0 -32px; 925 rotate: 230deg; 926 } 927 100% { 928 translate: 0 0; 929 rotate: 360deg; 930 } 931 } 932 933 /* Teleport — fade out, JS moves the pet, fade in */ 934 .pet.teleporting { 935 animation: teleport 0.7s ease-in-out; 936 } 937 @keyframes teleport { 938 0% { 939 opacity: 1; 940 } 941 35% { 942 opacity: 0; 943 } 944 65% { 945 opacity: 0; 946 } 947 100% { 948 opacity: 1; 949 } 950 } 951 952 /* Fart cloud */ 953 .fart { 954 position: absolute; 955 bottom: 4px; 956 width: 90px; 957 height: 70px; 958 opacity: 0; 959 pointer-events: none; 960 } 961 .fart svg { 962 width: 100%; 963 height: 100%; 964 overflow: visible; 965 } 966 .fart-puff { 967 transform-box: fill-box; 968 transform-origin: center; 969 animation-name: fart-puff; 970 animation-timing-function: ease-out; 971 animation-fill-mode: forwards; 972 opacity: 0; 973 } 974 .pet.farting .fart { 975 opacity: 1; 976 } 977 @keyframes fart-puff { 978 0% { 979 transform: translate(0, 0) scale(var(--fart-start, 0.2)); 980 opacity: 0; 981 } 982 18% { 983 opacity: var(--fart-peak, 0.85); 984 } 985 100% { 986 transform: translate(var(--fart-dx, 0), var(--fart-dy, -25px)) 987 scale(var(--fart-end, 1.4)); 988 opacity: 0; 989 } 990 } 991 992 /* Per-pet smoke — wispy gray puffs from upper face */ 993 .smoke { 994 position: absolute; 995 top: -14px; 996 left: 50%; 997 margin-left: -35px; 998 width: 70px; 999 height: 90px; 1000 opacity: 0; 1001 pointer-events: none; 1002 } 1003 .smoke svg { 1004 width: 100%; 1005 height: 100%; 1006 overflow: visible; 1007 } 1008 .smoke-puff { 1009 transform-box: fill-box; 1010 transform-origin: center; 1011 animation-name: smoke-puff; 1012 animation-timing-function: ease-out; 1013 animation-fill-mode: forwards; 1014 opacity: 0; 1015 } 1016 .pet.smoking .smoke { 1017 opacity: 1; 1018 } 1019 @keyframes smoke-puff { 1020 0% { 1021 transform: translate(0, 0) scale(var(--smoke-start, 0.2)); 1022 opacity: 0; 1023 } 1024 20% { 1025 opacity: var(--smoke-peak, 0.7); 1026 } 1027 100% { 1028 transform: translate(var(--smoke-dx, 0), var(--smoke-dy, -55px)) 1029 scale(var(--smoke-end, 1.8)); 1030 opacity: 0; 1031 } 1032 }