data endpoint for entity 90008 (aka. a website)
0
fork

Configure Feed

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

feat: add desktop pet thing

dusk ff390edd 39011085

+146 -1
+139
src/components/pet.svelte
··· 1 + <script lang="ts"> 2 + import { draggable } from '@neodrag/svelte'; 3 + 4 + let position = $state({ x: 0, y: 0 }); 5 + let rotation = $state(0.0); 6 + let sprite = $state('/pet/idle.webp'); 7 + let flip = $state(false); 8 + let dragged = $state(false); 9 + 10 + let targetX = 120; 11 + let speed = 10.0; 12 + let tickRate = 20; 13 + let delta = 1.0 / tickRate; 14 + 15 + let strideRadius = 4.0; 16 + let strideAngle = 0; 17 + 18 + const turnStrideWheel = (by: number) => { 19 + strideAngle += by / strideRadius; 20 + if (strideAngle > Math.PI * 2) { 21 + strideAngle -= Math.PI * 2; 22 + } else if (strideAngle < 0) { 23 + strideAngle += Math.PI * 2; 24 + } 25 + }; 26 + 27 + let targetRotation = $state(0.0); 28 + let rotationVelocity = $state(0.0); 29 + let springStiffness = 20.0; // How quickly rotation returns to target 30 + let springDamping = 0.2; // Damping factor to prevent oscillation 31 + 32 + const updateRotationSpring = () => { 33 + // Spring physics: calculate force based on distance from target 34 + const springForce = (targetRotation - rotation) * springStiffness; 35 + 36 + // Apply damping to velocity 37 + rotationVelocity = rotationVelocity * (1 - springDamping) + springForce * delta; 38 + 39 + // Update rotation based on velocity 40 + rotation += rotationVelocity * delta; 41 + 42 + // If we're very close to target and barely moving, just snap to target 43 + if (Math.abs(rotation - targetRotation) < 0.01 && Math.abs(rotationVelocity) < 0.01) { 44 + rotation = targetRotation; 45 + rotationVelocity = 0; 46 + } 47 + }; 48 + 49 + // Add spring update to the move function 50 + setInterval(updateRotationSpring, tickRate); 51 + 52 + const lerp = (from: number, to: number, weight: number) => { 53 + return from + (to - from) * weight; 54 + }; 55 + 56 + const moveTowards = (from: number, to: number, by: number) => { 57 + let d = (to - from) * 1.0; 58 + let l = Math.abs(d); 59 + let s = Math.sign(d); 60 + let moveBy = s * Math.min(l, by) * delta; 61 + return moveBy; 62 + }; 63 + 64 + const move = () => { 65 + if (dragged) { 66 + return; 67 + } 68 + 69 + if (position.y !== 0) { 70 + position.y = Math.ceil(lerp(position.y, 0.0, 0.2)); 71 + return; 72 + } 73 + 74 + let moveByX = moveTowards(position.x, targetX, speed); 75 + position.x += moveByX; 76 + 77 + turnStrideWheel(moveByX); 78 + 79 + flip = moveByX < 0.0; 80 + if (moveByX > 0.1 || moveByX < -0.1) { 81 + sprite = strideAngle % Math.PI < Math.PI * 0.5 ? '/pet/walk1.webp' : '/pet/walk2.webp'; 82 + } else { 83 + sprite = '/pet/idle.webp'; 84 + } 85 + }; 86 + 87 + setInterval(move, tickRate); 88 + 89 + const pickNewTargetX = () => { 90 + const viewportWidth = self.innerWidth || null; 91 + if (viewportWidth !== null && Math.abs(position.x - targetX) < 5) { 92 + targetX = Math.max( 93 + Math.min(targetX + (Math.random() - 0.5) * 500.0, viewportWidth * 0.9), 94 + viewportWidth * 0.1 95 + ); 96 + } 97 + // Set a random interval for the next target update (between 4-10 seconds) 98 + const randomDelay = Math.floor(Math.random() * 6000) + 4000; 99 + setTimeout(pickNewTargetX, randomDelay); 100 + }; 101 + 102 + // Start the process 103 + setTimeout(pickNewTargetX, 1000); 104 + </script> 105 + 106 + <!-- svelte-ignore a11y_missing_attribute --> 107 + <div 108 + use:draggable={{ 109 + position, 110 + applyUserSelectHack: true, 111 + handle: 'img', 112 + bounds: { 113 + bottom: (window.innerHeight / 100) * 5.5 114 + }, 115 + onDragStart: () => { 116 + sprite = '/pet/pick.webp'; 117 + dragged = true; 118 + }, 119 + onDrag: ({ offsetX, offsetY, event }) => { 120 + position.x = offsetX; 121 + position.y = offsetY; 122 + rotationVelocity += event.movementY * delta + event.movementX * delta; 123 + }, 124 + onDragEnd: () => { 125 + dragged = false; 126 + } 127 + }} 128 + class="absolute bottom-[5vh] z-[1000]" 129 + > 130 + <img 131 + draggable="false" 132 + class="invert" 133 + style=" 134 + image-rendering: pixelated !important; 135 + transform: rotate({rotation}rad) scaleX({flip ? -1 : 1}); 136 + " 137 + src={sprite} 138 + /> 139 + </div>
+7 -1
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import getTitle from '$lib/getTitle'; 3 + import { isMobile } from '$lib/window'; 3 4 import NavButton from '../components/navButton.svelte'; 5 + import Pet from '../components/pet.svelte'; 4 6 import Tooltip from '../components/tooltip.svelte'; 5 7 import '../styles/app.css'; 6 8 ··· 141 143 {@render children?.()} 142 144 </div> 143 145 144 - <nav class="w-full min-h-[5vh] max-h-[6vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible"> 146 + {#if !isMobile()} 147 + <Pet></Pet> 148 + {/if} 149 + 150 + <nav class="w-full min-h-[5vh] max-h-[5vh] fixed bottom-0 z-[999] bg-ralsei-black overflow-visible"> 145 151 <div 146 152 class=" 147 153 max-w-full max-h-fit p-1 z-[999]
static/pet/idle.webp

This is a binary file and will not be displayed.

static/pet/pick.webp

This is a binary file and will not be displayed.

static/pet/walk1.webp

This is a binary file and will not be displayed.

static/pet/walk2.webp

This is a binary file and will not be displayed.