vod frog, frog with the vods
5
fork

Configure Feed

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

wavy login input, fly splat SFX (preloaded), remove input stroke overlay

+60 -25
+12
src/lib/FlySpawner.svelte
··· 11 11 const MIN_SPAWN_MS = 10000; 12 12 const MAX_SPAWN_MS = 120000; 13 13 14 + // Preload splat sound so it plays instantly on click 15 + let splatAudio: HTMLAudioElement; 16 + if (typeof window !== 'undefined') { 17 + splatAudio = new Audio('/sfx/flysplat.mp3'); 18 + splatAudio.preload = 'auto'; 19 + splatAudio.load(); 20 + } 21 + 14 22 interface Fly { 15 23 id: number; 16 24 x: number; ··· 73 81 } 74 82 75 83 function splatFly(id: number) { 84 + // Play splat sound (clone so overlapping splats all play) 85 + const sfx = splatAudio.cloneNode() as HTMLAudioElement; 86 + sfx.volume = 0.5; 87 + sfx.play().catch(() => {}); 76 88 flies = flies.map(f => f.id === id && !f.splatted ? { ...f, splatted: true } : f); 77 89 78 90 setTimeout(() => {
+48 -25
src/lib/LoginButton.svelte
··· 17 17 const seed = 'login-btn'; 18 18 const { svgPath, clipPolygon } = generateWavyOval(seed); 19 19 const logoutShape = generateWavyOval('logout-btn'); 20 + const inputShape = generateWavyOval('login-input', 46, 38); 20 21 21 - function generateWavyOval(s: string): { svgPath: string; clipPolygon: string } { 22 + function generateWavyOval(s: string, rx = 44, ry = 36): { svgPath: string; clipPolygon: string } { 22 23 const cx = 50, cy = 50; 23 - const rx = 44; 24 - const ry = 36; 25 24 const amp = 3; 26 25 const points = 48; 27 26 ··· 113 112 </div> 114 113 {:else if showInput} 115 114 <form class="login-form" onsubmit={handleSubmit}> 116 - <input 117 - bind:this={inputEl} 118 - bind:value={handleValue} 119 - onkeydown={handleKeydown} 120 - type="text" 121 - placeholder="handle.bsky.social" 122 - class="handle-input" 123 - disabled={submitting} 124 - /> 115 + <div class="input-wavy"> 116 + <div class="input-clipped" style="clip-path: {inputShape.clipPolygon};"> 117 + <div class="input-fill"></div> 118 + <input 119 + bind:this={inputEl} 120 + bind:value={handleValue} 121 + onkeydown={handleKeydown} 122 + type="text" 123 + placeholder="handle.bsky.social" 124 + class="handle-input" 125 + disabled={submitting} 126 + /> 127 + </div> 128 + 129 + </div> 125 130 <button class="login-btn" type="submit" disabled={submitting}> 126 131 <div class="btn-clipped" style="clip-path: {clipPolygon};"> 127 132 <div class="btn-fill"></div> ··· 223 228 gap: 8px; 224 229 } 225 230 231 + .input-wavy { 232 + position: relative; 233 + width: clamp(160px, 18vw, 240px); 234 + height: clamp(44px, 5vw, 56px); 235 + } 236 + 237 + .input-clipped { 238 + position: relative; 239 + width: 100%; 240 + height: 100%; 241 + display: flex; 242 + align-items: center; 243 + justify-content: center; 244 + } 245 + 246 + .input-fill { 247 + position: absolute; 248 + inset: 0; 249 + background: #FFDEED; 250 + } 251 + 226 252 .handle-input { 253 + position: relative; 254 + z-index: 1; 227 255 font-family: 'Fang', system-ui, sans-serif; 228 - font-size: 0.9rem; 229 - padding: 8px 12px; 230 - border: 2px solid #0A182B; 231 - border-radius: 20px; 232 - background: #FFDEED; 256 + font-size: 0.85rem; 257 + padding: 0; 258 + border: none; 259 + background: transparent; 233 260 color: #0A182B; 234 - width: clamp(140px, 18vw, 220px); 261 + width: 75%; 235 262 outline: none; 236 - transition: border-color 0.15s; 263 + text-align: center; 237 264 } 238 265 239 266 .handle-input::placeholder { 240 267 color: #0A182B; 241 268 opacity: 0.4; 242 - } 243 - 244 - .handle-input:focus { 245 - border-color: #FF3992; 246 269 } 247 270 248 271 .login-error { ··· 259 282 align-items: flex-end; 260 283 } 261 284 262 - .handle-input { 263 - width: clamp(160px, 50vw, 220px); 285 + .input-wavy { 286 + width: clamp(180px, 55vw, 240px); 264 287 } 265 288 } 266 289 </style>
static/sfx/flysplat.mp3

This is a binary file and will not be displayed.