my website at ewancroft.uk
6
fork

Configure Feed

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

at main 248 lines 7.9 kB view raw
1<script lang="ts"> 2 import { happyMacStore } from '$lib/stores'; 3 4 let isVisible = $state(false); 5 let position = $state(-100); 6 7 // Watch the store for when it's triggered (24 clicks) 8 $effect(() => { 9 const state = $happyMacStore; 10 if (state.isTriggered && !isVisible) { 11 startAnimation(); 12 } 13 }); 14 15 function playBeep() { 16 try { 17 const audioContext = new AudioContext(); 18 const now = audioContext.currentTime; 19 20 // Tributary recreation of the classic Mac startup chord 21 // This is NOT the original sound - it's an approximation using Web Audio API 22 // The original Mac beep was a major chord: F4, A4, C5 23 // Frequencies: ~349 Hz, ~440 Hz, ~523 Hz 24 const frequencies = [349, 440, 523]; 25 const masterGain = audioContext.createGain(); 26 masterGain.connect(audioContext.destination); 27 masterGain.gain.value = 0.15; 28 29 // Create three oscillators for the chord 30 frequencies.forEach((freq) => { 31 const oscillator = audioContext.createOscillator(); 32 const gainNode = audioContext.createGain(); 33 34 oscillator.type = 'sine'; // Original Mac used sine waves 35 oscillator.frequency.value = freq; 36 37 // ADSR envelope for a more authentic sound 38 gainNode.gain.setValueAtTime(0, now); 39 gainNode.gain.linearRampToValueAtTime(0.3, now + 0.02); // Attack 40 gainNode.gain.exponentialRampToValueAtTime(0.01, now + 1.0); // Decay 41 42 oscillator.connect(gainNode); 43 gainNode.connect(masterGain); 44 45 oscillator.start(now); 46 oscillator.stop(now + 1.0); 47 }); 48 } catch (e) { 49 // Fail silently if audio context isn't available 50 console.log('Audio playback not available'); 51 } 52 } 53 54 function startAnimation() { 55 // Play the beep first 56 playBeep(); 57 58 isVisible = true; 59 position = -100; 60 61 // Animate across screen (takes about 15 seconds) 62 const duration = 15000; 63 const startTime = Date.now(); 64 65 function animate() { 66 const elapsed = Date.now() - startTime; 67 const progress = Math.min(elapsed / duration, 1); 68 69 // Move from -100 to window width + 100 70 position = -100 + (window.innerWidth + 200) * progress; 71 72 if (progress < 1) { 73 requestAnimationFrame(animate); 74 } else { 75 isVisible = false; 76 // Reset the store so it can be triggered again 77 happyMacStore.reset(); 78 } 79 } 80 81 requestAnimationFrame(animate); 82 } 83</script> 84 85{#if isVisible} 86 <div class="happy-mac" style="left: {position}px"> 87 <!-- 88 Happy Mac SVG 89 Original by NiloGlock at Italian Wikipedia 90 License: CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/) 91 Source: https://commons.wikimedia.org/wiki/File:Happy_Mac.svg 92 --> 93 <svg 94 width="60" 95 height="78" 96 viewBox="0 0 8.4710464 10.9614" 97 xmlns="http://www.w3.org/2000/svg" 98 class="mac-icon" 99 > 100 <g transform="translate(-5.3090212,-4.3002038)"> 101 <g transform="matrix(0.06455006,0,0,0.06455006,7.6050574,7.0900779)"> 102 <path 103 d="m -30.937651,99.78759 h 122 v 26.80449 h -122 z" 104 style="fill:#000000;fill-opacity:1;stroke-width:2.38412714" 105 /> 106 <g transform="translate(-56.456402,-31.41017)"> 107 <path 108 style="fill:#555555;fill-opacity:1;stroke:none;stroke-width:0.17674622" 109 d="m 33.668747,136.75006 v 4.69998 h 31.950504 v -4.69998 z m 41.740088,4.69998 V 146.15 h 11.145573 v -4.69996 z M 91.152059,146.15 v 6.29987 H 102.47075 V 146.15 Z" 110 /> 111 <path 112 style="fill:#444444;fill-opacity:1;stroke:none;stroke-width:0.15800072" 113 d="m 65.619251,136.75006 v 4.69998 H 86.554408 V 146.15 h 15.916342 v 6.29987 h 20.86023 V 146.15 h -15.87449 v -4.69996 H 91.152059 v -4.69998 z" 114 /> 115 <path 116 style="fill:#222222;fill-opacity:1;stroke:none;stroke-width:0.21712606" 117 d="m 91.152059,136.75006 v 4.69998 H 107.45649 V 146.15 h 15.87449 v 6.29987 h 16.03777 v -6.29987 -4.69996 -4.69998 z" 118 /> 119 <path 120 style="fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.20201708" 121 d="M 33.668747,141.45004 V 146.15 h 41.740088 v -4.69996 z M 75.408835,146.15 v 6.29987 H 91.152059 V 146.15 Z" 122 /> 123 <path 124 d="m 33.668823,146.14999 h 41.74001 v 6.3 h -41.74001 z" 125 style="fill:#888888;fill-opacity:1;stroke:none;stroke-width:0.23388879" 126 /> 127 </g> 128 <path 129 d="M -30.969854,-37.120319 H 91.062349 V 99.787579 H -30.969854 Z" 130 style="fill:#cccccc;fill-opacity:1;stroke-width:0.26458332" 131 /> 132 <path 133 d="M -15.075892,-21.040775 H 74.98512 v 67.75 h -90.061012 z" 134 style="fill:#ccccff;fill-opacity:1;stroke-width:0.26458332" 135 /> 136 <path 137 transform="scale(0.26458333)" 138 d="M 102.17383,-23.402344 V 59.882812 H 83.148438 V 78.779297 H 102.17383 120 120.0508 V -23.402344 Z" 139 style="fill:#000000;fill-opacity:1;stroke-width:0.93718952" 140 /> 141 <path 142 d="M -30.969856,-43.220318 H 91.062347 v 6.1 H -30.969856 Z" 143 style="fill:#000000;fill-opacity:1;stroke-width:1.13749063" 144 /> 145 <path 146 d="M -15.075892,-27.140776 H 74.98512 v 6.1 h -90.061012 z" 147 style="fill:#444444;fill-opacity:1;stroke-width:0.97719014" 148 /> 149 <path 150 d="m -21.040775,15.075892 h 67.75 v 6.1 h -67.75 z" 151 style="fill:#444444;fill-opacity:1;stroke-width:0.84755003" 152 transform="rotate(90)" 153 /> 154 <path 155 d="m -21.040775,-81.085121 h 67.75 v 6.1 h -67.75 z" 156 style="fill:#ffffff;fill-opacity:1;stroke-width:0.84755009" 157 transform="rotate(90)" 158 /> 159 <path 160 d="m -15.07589,46.709225 h 90.061013 v 6.1 H -15.07589 Z" 161 style="fill:#ffffff;fill-opacity:1;stroke-width:0.9771902" 162 /> 163 <path 164 d="m 31.655506,73.81324 h 43.400002 v 5 H 31.655506 Z" 165 style="fill:#000000;fill-opacity:1;stroke-width:0.26445001" 166 /> 167 <path 168 d="m 31.655506,78.81324 h 43.400005 v 6 H 31.655506 Z" 169 style="fill:#ffffff;fill-opacity:1;stroke-width:0.28969046" 170 /> 171 <path 172 d="m -21.133041,73.785721 h 11.060395 v 5 h -11.060395 z" 173 style="fill:#00bb00;fill-opacity:1;stroke-width:0.13350084" 174 /> 175 <path 176 d="m -21.133041,78.785721 h 11.060396 v 6 h -11.060396 z" 177 style="fill:#dd0000;fill-opacity:1;stroke-width:0.14624284" 178 /> 179 <path 180 d="M 5.8799295,-6.1919641 H 10.87993 V 5.0080357 H 5.8799295 Z" 181 style="fill:#000000;fill-opacity:1;stroke-width:0.26576424" 182 /> 183 <path 184 d="m 47.880306,-6.1919641 h 6.1 V 5.0080357 h -6.1 z" 185 style="fill:#000000;fill-opacity:1;stroke-width:0.29354623" 186 /> 187 <path 188 d="m 10.8871,25.947487 h 5 v 6 h -5 z" 189 style="fill:#000000;fill-opacity:1;stroke-width:0.19451953" 190 /> 191 <path 192 d="m 38.149635,25.944651 h 4.75 v 6.002836 h -4.75 z" 193 style="fill:#000000;fill-opacity:1;stroke-width:0.18963902" 194 /> 195 <path 196 d="m 15.8871,31.947487 h 22.262533 v 5.011021 H 15.8871 Z" 197 style="fill:#000000;fill-opacity:1;stroke-width:11.12128639" 198 /> 199 <path 200 d="M -37.120319,30.969854 H 99.787579 v 4.6 H -37.120319 Z" 201 style="fill:#000000;fill-opacity:1;stroke-width:1.04625833" 202 transform="rotate(90)" 203 /> 204 <path 205 d="M -37.120331,-95.662346 H 99.787582 v 4.6 H -37.120331 Z" 206 style="fill:#000000;fill-opacity:1;stroke-width:1.04625833" 207 transform="rotate(90)" 208 /> 209 </g> 210 </g> 211 </svg> 212 </div> 213{/if} 214 215<style> 216 .happy-mac { 217 position: fixed; 218 bottom: 0; 219 z-index: 9999; 220 pointer-events: none; 221 animation: hop 0.6s ease-in-out infinite; 222 } 223 224 .mac-icon { 225 filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)); 226 } 227 228 @keyframes hop { 229 0%, 230 100% { 231 transform: translateY(0) rotate(0deg) scaleY(1) scaleX(1); 232 } 233 25% { 234 transform: translateY(-10px) rotate(2deg) scaleY(1.15) scaleX(0.9); 235 } 236 50% { 237 transform: translateY(-20px) rotate(5deg) scaleY(1) scaleX(1); 238 } 239 75% { 240 transform: translateY(-10px) rotate(2deg) scaleY(0.85) scaleX(1.1); 241 } 242 } 243 244 /* Add a little tilt alternation */ 245 .happy-mac:hover { 246 animation: hop 0.3s ease-in-out infinite; 247 } 248</style>