All checks were successful
		
		
	
	Deploy to SFTP Server / build (push) Successful in 21m14s
				
			
		
			
				
	
	
		
			180 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
| <script context="module" lang="ts">
 | |
|   let mouse = { x: 0, y: 0 };
 | |
| 
 | |
|   let visible = writable(false);
 | |
| 
 | |
|   const maxDistance = globalThis.innerWidth / 3;
 | |
|   let frameId: number;
 | |
|   function update() {
 | |
|     frameId = requestAnimationFrame(update);
 | |
|     for (const eye of eyes) {
 | |
|       const dx = eye.x - mouse.x;
 | |
|       const dy = eye.y - mouse.y;
 | |
|       const angle = Math.atan2(dy, dx);
 | |
| 
 | |
|       const distance = Math.hypot(dx, dy);
 | |
|       if (distance < maxDistance) {
 | |
|         eye.rotation = angle * (180 / Math.PI) - 90;
 | |
|         eye.distance = -Math.min(17, distance);
 | |
|         eye.el.style.setProperty("--distance", `${eye.distance}px`);
 | |
|         eye.el.style.setProperty(
 | |
|           "--rotation",
 | |
|           `${Math.floor(eye.rotation)}deg`,
 | |
|         );
 | |
|         eye.el.classList.add("active");
 | |
|       } else {
 | |
|         eye.el.classList.remove("active");
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const eyes: {
 | |
|     x: number;
 | |
|     y: number;
 | |
|     rotation: number;
 | |
|     distance: number;
 | |
|     el: HTMLDivElement;
 | |
|     pupil: HTMLDivElement;
 | |
|   }[] = [];
 | |
| 
 | |
|   function register(eye: HTMLDivElement) {
 | |
|     const { x, y, width, height } = eye.getBoundingClientRect();
 | |
|     eyes.push({
 | |
|       x: x + width / 2,
 | |
|       y: y + height / 2,
 | |
|       rotation: 0,
 | |
|       distance: 0,
 | |
|       el: eye,
 | |
|       pupil: eye.children[0] as HTMLDivElement,
 | |
|     });
 | |
|     if (frameId) cancelAnimationFrame(frameId);
 | |
|     update();
 | |
|   }
 | |
| 
 | |
|   function unregister(eye: HTMLDivElement) {
 | |
|     const index = eyes.findIndex((e) => e.el === eye);
 | |
|     eyes.splice(index, 1);
 | |
|     if (eyes.length === 0) cancelAnimationFrame(frameId);
 | |
|   }
 | |
| 
 | |
|   function handleMouseMove(ev: MouseEvent) {
 | |
|     mouse.x = ev.clientX;
 | |
|     mouse.y = ev.clientY;
 | |
|   }
 | |
| </script>
 | |
| 
 | |
| <script lang="ts">
 | |
|   import { onMount } from "svelte";
 | |
|   import { writable } from "svelte/store";
 | |
|   import { scale } from "svelte/transition";
 | |
| 
 | |
|   let eye: HTMLDivElement;
 | |
|   let _old_eye: HTMLDivElement;
 | |
|   $: if (eye !== _old_eye) {
 | |
|     if (_old_eye) {
 | |
|       unregister(_old_eye);
 | |
|     }
 | |
|     if (eye) {
 | |
|       register(eye);
 | |
|     }
 | |
|     _old_eye = eye;
 | |
|   }
 | |
| 
 | |
|   onMount(() => {
 | |
|     return () => {
 | |
|       unregister(eye);
 | |
|     };
 | |
|   });
 | |
| </script>
 | |
| 
 | |
| <svelte:window on:mousemove={handleMouseMove} />
 | |
| 
 | |
| <div
 | |
|   class="googley-eyes"
 | |
|   on:click={() => ($visible = !$visible)}
 | |
|   role="img"
 | |
|   aria-label="Toggle Googley Eyes"
 | |
|   aria-hidden="true"
 | |
|   on:keydown={(ev) => (ev.key === "Enter" ? ($visible = !$visible) : "")}>
 | |
|   {#if $visible}
 | |
|     <div class="eye" bind:this={eye} transition:scale>
 | |
|       <div class="pupil"></div>
 | |
|     </div>
 | |
|   {/if}
 | |
| </div>
 | |
| 
 | |
| <style>
 | |
|   .googley-eyes {
 | |
|     position: relative;
 | |
|     width: 100%;
 | |
|     height: 100%;
 | |
|     display: flex;
 | |
|     max-width: 50px;
 | |
|     max-height: 50px;
 | |
|     width: 10vw;
 | |
|     height: 10vw;
 | |
|     justify-content: center;
 | |
|     align-items: center;
 | |
|   }
 | |
| 
 | |
|   .eye {
 | |
|     position: relative;
 | |
|     overflow: hidden;
 | |
|     max-width: 50px;
 | |
|     max-height: 50px;
 | |
|     width: 10vw;
 | |
|     height: 10vw;
 | |
|     border-radius: 50%;
 | |
|     background-color: white;
 | |
|     display: flex;
 | |
|     justify-content: center;
 | |
|     align-items: center;
 | |
|     transform: rotate(var(--rotation));
 | |
|     box-shadow: 0px 0px 30px #00000094;
 | |
|     outline: solid 1px #737373;
 | |
|   }
 | |
| 
 | |
|   .eye::before {
 | |
|     content: "";
 | |
|     position: absolute;
 | |
|     width: 100%;
 | |
|     height: 100%;
 | |
|     border-radius: 50%;
 | |
|     background-color: rgba(200, 200, 200, 0.01);
 | |
|     transform: rotate(calc(var(--rotation) * -1));
 | |
|     box-shadow:
 | |
|       5px 5px 10px #ffffff70 inset,
 | |
|       1px 1px 4px #ffffff96 inset,
 | |
|       -2px -2px 10px black inset,
 | |
|       2px 2px 5px #00000078;
 | |
|   }
 | |
| 
 | |
|   .googley-eyes > :global(.active > .pupil) {
 | |
|     transform: translateY(var(--distance)) scale(0.7);
 | |
|   }
 | |
| 
 | |
|   .pupil {
 | |
|     position: absolute;
 | |
|     width: 50%;
 | |
|     height: 50%;
 | |
|     border-radius: 50%;
 | |
|     transition: transform cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.2s;
 | |
|   }
 | |
| 
 | |
|   .pupil::after {
 | |
|     content: "";
 | |
|     position: absolute;
 | |
|     width: 100%;
 | |
|     height: 100%;
 | |
|     border-radius: 50%;
 | |
|     background-color: black;
 | |
|     box-shadow:
 | |
|       -5px -5px 10px #ffffff70 inset,
 | |
|       -1px -1px 4px #ffffff96 inset,
 | |
|       2px 2px 2px black inset;
 | |
|     transform: translate(-50%, -50%) rotate(calc(var(--rotation) * -1 + 180deg));
 | |
|     top: 50%;
 | |
|     left: 50%;
 | |
|   }
 | |
| </style>
 |