vod frog, frog with the vods
5
fork

Configure Feed

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

mobile frog menu: tap frog to reveal lilypad menu items with staggered animation

+292 -6
+6
src/lib/CreditsButton.svelte
··· 76 76 transform: scale(1.1) rotate(-5deg); 77 77 } 78 78 79 + @media (max-width: 600px) { 80 + .credits-btn { 81 + display: none; 82 + } 83 + } 84 + 79 85 .lilypad-img { 80 86 width: clamp(70px, 10vw, 100px); 81 87 height: auto;
+286 -6
src/lib/FrogHeader.svelte
··· 1 + <!-- 2 + FrogHeader: Site header with the "vod frog" logo and frog mascot. 3 + On mobile, the frog acts as a menu toggle — tapping it reveals lilypad 4 + menu items (credits, login) that drift down with staggered animation. 5 + --> 1 6 <script lang="ts"> 2 7 import LoginButton from './LoginButton.svelte'; 3 8 import { playCroak } from './croak'; 4 9 5 10 let { onHomeClick }: { onHomeClick?: () => void } = $props(); 11 + let menuOpen = $state(false); 12 + let showCreditsModal = $state(false); 6 13 7 14 function handleClick(e: MouseEvent) { 8 15 if (onHomeClick) { ··· 10 17 onHomeClick(); 11 18 } 12 19 } 20 + 21 + function onFrogClick() { 22 + playCroak(); 23 + // On mobile, toggle the menu 24 + if (window.innerWidth <= 600) { 25 + menuOpen = !menuOpen; 26 + } 27 + } 28 + 29 + function onFrogTouch(e: TouchEvent) { 30 + e.preventDefault(); 31 + onFrogClick(); 32 + } 33 + 34 + function openCredits() { 35 + playCroak(); 36 + showCreditsModal = true; 37 + menuOpen = false; 38 + } 39 + 40 + function closeCredits() { 41 + showCreditsModal = false; 42 + } 43 + 44 + function onBackdropClick(e: MouseEvent) { 45 + if (e.target === e.currentTarget) closeCredits(); 46 + } 47 + 48 + function onKeyDown(e: KeyboardEvent) { 49 + if (e.key === 'Escape') closeCredits(); 50 + } 13 51 </script> 52 + 53 + <svelte:window onkeydown={onKeyDown} /> 14 54 15 55 <header class="frog-header"> 16 - <div class="login-area"> 56 + <!-- Desktop login area --> 57 + <div class="login-area desktop-only"> 17 58 <LoginButton /> 18 59 </div> 60 + 19 61 <div class="title-area"> 20 62 <a href="/" class="logo-link" onclick={handleClick}><h1 class="logo-text">vod frog</h1></a> 21 63 <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> 22 - <img src="/froggie.png" alt="A cute frog" class="header-frog" onclick={playCroak} ontouchend={(e) => { e.preventDefault(); playCroak(); }} /> 64 + <img 65 + src="/froggie.png" 66 + alt="A cute frog" 67 + class="header-frog" 68 + class:menu-open={menuOpen} 69 + onclick={onFrogClick} 70 + ontouchend={onFrogTouch} 71 + /> 23 72 </div> 73 + 74 + <!-- Mobile lilypad menu --> 75 + <div class="mobile-menu" class:open={menuOpen}> 76 + <button 77 + class="lily-item" 78 + style="animation-delay: 0.05s;" 79 + onclick={openCredits} 80 + > 81 + <img src="/lilymenu.svg" alt="" class="lily-img" /> 82 + <span class="lily-label">credits</span> 83 + </button> 84 + <div class="lily-item" style="animation-delay: 0.15s;"> 85 + <LoginButton /> 86 + </div> 87 + </div> 88 + 24 89 <div class="subtitle-lines"> 25 90 <p class="subtitle">an exploration by</p> 26 91 <p class="subtitle"><a href="https://witchsky.app/profile/goose.art" target="_blank" class="subtitle-link">@goose.art</a> using <a href="https://witchsky.app/profile/stream.place" target="_blank" class="subtitle-link">@stream.place</a></p> 27 92 </div> 28 93 </header> 29 94 95 + <!-- Credits modal (shared between desktop CreditsButton and mobile menu) --> 96 + {#if showCreditsModal} 97 + <!-- svelte-ignore a11y_no_static_element_interactions --> 98 + <div class="modal-backdrop" onclick={onBackdropClick}> 99 + <div class="modal-content"> 100 + <div class="credits-body"> 101 + <h2 class="credits-title">credits</h2> 102 + <div class="credit-item"> 103 + <p class="credit-label">Frog Croaking</p> 104 + <p class="credit-author">by DrinkingWindGames</p> 105 + <a href="https://freesound.org/s/848549/" target="_blank" class="credit-link"> 106 + freesound.org/s/848549/ 107 + </a> 108 + <p class="credit-license">License: Attribution 4.0</p> 109 + </div> 110 + <button class="close-btn" onclick={closeCredits}>close</button> 111 + </div> 112 + </div> 113 + </div> 114 + {/if} 115 + 30 116 <style> 31 117 .frog-header { 32 118 padding: 30px 20px 50px; ··· 68 154 top: -60px; 69 155 width: clamp(120px, 18vw, 220px); 70 156 height: auto; 71 - z-index: 2; 157 + z-index: 12; 72 158 transform: rotate(-10deg); 159 + transform-origin: center center; 73 160 filter: drop-shadow(2px 4px 6px rgba(10, 24, 43, 0.3)); 74 - transition: transform 0.3s ease; 161 + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); 75 162 } 76 163 77 164 .header-frog:hover { 78 165 transform: rotate(5deg) scale(1.1); 79 166 } 80 167 168 + /* Mobile menu — hidden by default */ 169 + .mobile-menu { 170 + display: none; 171 + } 172 + 173 + .desktop-only { 174 + display: block; 175 + } 176 + 177 + /* Credits modal */ 178 + .modal-backdrop { 179 + position: fixed; 180 + inset: 0; 181 + background: rgba(10, 24, 43, 0.6); 182 + z-index: 1000; 183 + display: flex; 184 + align-items: center; 185 + justify-content: center; 186 + padding: 20px; 187 + } 188 + 189 + .modal-content { 190 + width: min(500px, 90vw); 191 + background: #39FF44; 192 + border: 3px solid #0A182B; 193 + border-radius: 24px; 194 + padding: 32px 24px; 195 + } 196 + 197 + .credits-body { 198 + display: flex; 199 + flex-direction: column; 200 + align-items: center; 201 + text-align: center; 202 + gap: 12px; 203 + } 204 + 205 + .credits-title { 206 + font-family: 'PicNic', cursive, system-ui; 207 + font-size: clamp(1.6rem, 3.5vw, 2.2rem); 208 + color: #0A182B; 209 + margin: 0; 210 + } 211 + 212 + .credit-item { 213 + display: flex; 214 + flex-direction: column; 215 + gap: 4px; 216 + } 217 + 218 + .credit-label { 219 + font-family: 'PicNic', cursive, system-ui; 220 + font-size: 1.1rem; 221 + color: #0A182B; 222 + margin: 0; 223 + } 224 + 225 + .credit-author { 226 + font-family: 'Fang', system-ui, sans-serif; 227 + font-size: 0.95rem; 228 + color: #0A182B; 229 + margin: 0; 230 + opacity: 0.8; 231 + } 232 + 233 + .credit-link { 234 + font-family: 'Fang', system-ui, sans-serif; 235 + font-size: 0.9rem; 236 + color: #0A182B; 237 + text-decoration: underline; 238 + text-decoration-color: #FF3992; 239 + transition: color 0.15s; 240 + } 241 + 242 + .credit-link:hover { 243 + color: #FF3992; 244 + } 245 + 246 + .credit-license { 247 + font-family: 'Fang', system-ui, sans-serif; 248 + font-size: 0.8rem; 249 + color: #0A182B; 250 + opacity: 0.65; 251 + margin: 0; 252 + font-style: italic; 253 + } 254 + 255 + .close-btn { 256 + all: unset; 257 + font-family: 'PicNic', cursive, system-ui; 258 + font-size: 1rem; 259 + color: #FFDEED; 260 + background: #0A182B; 261 + padding: 8px 28px; 262 + border-radius: 30px; 263 + cursor: pointer; 264 + margin-top: 8px; 265 + transition: transform 0.15s ease; 266 + } 267 + 268 + .close-btn:hover { 269 + transform: scale(1.05); 270 + } 271 + 81 272 .subtitle-lines { 82 273 margin-top: 6px; 83 274 position: relative; ··· 104 295 opacity: 0.85; 105 296 } 106 297 298 + /* ---- Mobile ---- */ 107 299 @media (max-width: 600px) { 108 300 .frog-header { 109 301 padding: 20px 16px 8px; 110 302 } 111 303 112 304 .header-frog { 113 - right: -100px; 305 + right: -60px; 114 306 top: -20px; 307 + width: clamp(90px, 22vw, 130px); 308 + } 309 + 310 + /* When menu is open, frog rotates down to "look at" the menu */ 311 + .header-frog.menu-open { 312 + transform: rotate(80deg) scale(1.05); 313 + } 314 + 315 + .desktop-only { 316 + display: none; 317 + } 318 + 319 + .mobile-menu { 320 + display: flex; 321 + flex-direction: column; 322 + align-items: flex-start; 323 + gap: 6px; 324 + overflow: hidden; 325 + max-height: 0; 326 + opacity: 0; 327 + transition: max-height 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), 328 + opacity 0.3s ease; 329 + position: relative; 330 + z-index: 11; 331 + padding-left: 8px; 332 + } 333 + 334 + .mobile-menu.open { 335 + max-height: 300px; 336 + opacity: 1; 337 + } 338 + 339 + .lily-item { 340 + all: unset; 341 + position: relative; 342 + display: flex; 343 + align-items: center; 344 + justify-content: center; 345 + opacity: 0; 346 + transform: translateY(-20px) rotate(-8deg); 347 + transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); 348 + } 349 + 350 + .mobile-menu.open .lily-item { 351 + opacity: 1; 352 + transform: translateY(0) rotate(0deg); 353 + } 354 + 355 + /* Stagger the animation per item */ 356 + .mobile-menu.open .lily-item:nth-child(1) { 357 + transition-delay: 0.05s; 358 + } 359 + .mobile-menu.open .lily-item:nth-child(2) { 360 + transition-delay: 0.15s; 361 + } 362 + .mobile-menu.open .lily-item:nth-child(3) { 363 + transition-delay: 0.25s; 364 + } 365 + 366 + /* When closing, reverse stagger */ 367 + .mobile-menu:not(.open) .lily-item:nth-child(1) { 368 + transition-delay: 0.1s; 369 + } 370 + .mobile-menu:not(.open) .lily-item:nth-child(2) { 371 + transition-delay: 0.05s; 372 + } 373 + .mobile-menu:not(.open) .lily-item:nth-child(3) { 374 + transition-delay: 0s; 375 + } 376 + 377 + .lily-img { 378 + width: 80px; 379 + height: auto; 380 + filter: drop-shadow(1px 2px 3px rgba(10, 24, 43, 0.25)); 381 + } 382 + 383 + .lily-label { 384 + position: absolute; 385 + font-family: 'PicNic', cursive, system-ui; 386 + font-size: 0.75rem; 387 + color: #0A182B; 388 + pointer-events: none; 389 + } 390 + } 391 + 392 + /* Desktop: hide mobile menu items, show lily label for credits btn */ 393 + @media (min-width: 601px) { 394 + .lily-item, .lily-img, .lily-label { 395 + display: none; 115 396 } 116 397 } 117 398 </style> 118 -