vod frog, frog with the vods
5
fork

Configure Feed

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

add wavy oval login button in header, reduce fly spawn to 10s-2min

goose.art e633fbda 044e544b

+137 -2
+2 -2
src/lib/FlySpawner.svelte
··· 8 8 import { onMount, onDestroy } from 'svelte'; 9 9 10 10 const MAX_FLIES = 5; 11 - const MIN_SPAWN_MS = 1000; 12 - const MAX_SPAWN_MS = 5000; 11 + const MIN_SPAWN_MS = 10000; 12 + const MAX_SPAWN_MS = 120000; 13 13 14 14 interface Fly { 15 15 id: number;
+12
src/lib/FrogHeader.svelte
··· 1 1 <script lang="ts"> 2 + import LoginButton from './LoginButton.svelte'; 3 + 2 4 let { onHomeClick }: { onHomeClick?: () => void } = $props(); 3 5 4 6 function handleClick(e: MouseEvent) { ··· 10 12 </script> 11 13 12 14 <header class="frog-header"> 15 + <div class="login-area"> 16 + <LoginButton onclick={() => { /* TODO: implement login */ }} /> 17 + </div> 13 18 <div class="title-area"> 14 19 <a href="/" class="logo-link" onclick={handleClick}><h1 class="logo-text">vod frog</h1></a> 15 20 <img src="/froggie.png" alt="A cute frog" class="header-frog" /> ··· 24 29 .frog-header { 25 30 padding: 30px 20px 50px; 26 31 position: relative; 32 + } 33 + 34 + .login-area { 35 + position: absolute; 36 + top: 30px; 37 + right: 20px; 38 + z-index: 10; 27 39 } 28 40 29 41 .title-area {
+123
src/lib/LoginButton.svelte
··· 1 + <!-- 2 + LoginButton: An oval wavy button with "login" text. 3 + Uses the same sine-wave wobble approach as WavyCircle but stretched into an oval. 4 + --> 5 + <script lang="ts"> 6 + import { seededRandom } from './theme'; 7 + 8 + let { onclick }: { onclick?: () => void } = $props(); 9 + 10 + const seed = 'login-btn'; 11 + const { svgPath, clipPolygon } = generateWavyOval(seed); 12 + 13 + function generateWavyOval(s: string): { svgPath: string; clipPolygon: string } { 14 + const cx = 50, cy = 50; 15 + const rx = 44; // horizontal radius 16 + const ry = 36; // vertical radius — squished for oval 17 + const amp = 3; 18 + const points = 48; 19 + 20 + const phase1 = seededRandom(s, 1) * Math.PI * 2; 21 + const phase2 = seededRandom(s, 2) * Math.PI * 2; 22 + const freq1 = 3 + seededRandom(s, 3) * 3; 23 + const freq2 = 6 + seededRandom(s, 4) * 4; 24 + const ampScale = 0.7 + seededRandom(s, 5) * 0.5; 25 + 26 + const pts: [number, number][] = []; 27 + 28 + for (let i = 0; i < points; i++) { 29 + const angle = (i / points) * Math.PI * 2; 30 + const w1 = Math.sin(angle * freq1 + phase1) * amp * 0.7; 31 + const w2 = Math.sin(angle * freq2 + phase2) * amp * 0.3; 32 + const wobble = (w1 + w2) * ampScale; 33 + pts.push([ 34 + cx + Math.cos(angle) * (rx + wobble), 35 + cy + Math.sin(angle) * (ry + wobble) 36 + ]); 37 + } 38 + 39 + // SVG path with Catmull-Rom splines 40 + let d = `M ${pts[0][0].toFixed(1)} ${pts[0][1].toFixed(1)}`; 41 + for (let i = 0; i < pts.length; i++) { 42 + const p0 = pts[(i - 1 + pts.length) % pts.length]; 43 + const p1 = pts[i]; 44 + const p2 = pts[(i + 1) % pts.length]; 45 + const p3 = pts[(i + 2) % pts.length]; 46 + const tension = 0.25; 47 + const cp1x = p1[0] + (p2[0] - p0[0]) * tension; 48 + const cp1y = p1[1] + (p2[1] - p0[1]) * tension; 49 + const cp2x = p2[0] - (p3[0] - p1[0]) * tension; 50 + const cp2y = p2[1] - (p3[1] - p1[1]) * tension; 51 + d += ` C ${cp1x.toFixed(1)} ${cp1y.toFixed(1)}, ${cp2x.toFixed(1)} ${cp2y.toFixed(1)}, ${p2[0].toFixed(1)} ${p2[1].toFixed(1)}`; 52 + } 53 + d += ' Z'; 54 + 55 + const clipPts = pts.map(([x, y]) => `${x.toFixed(2)}% ${y.toFixed(2)}%`).join(', '); 56 + return { svgPath: d, clipPolygon: `polygon(${clipPts})` }; 57 + } 58 + </script> 59 + 60 + <button class="login-btn" {onclick}> 61 + <div class="btn-clipped" style="clip-path: {clipPolygon};"> 62 + <div class="btn-fill"></div> 63 + <span class="btn-text">login</span> 64 + </div> 65 + <svg class="btn-stroke" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" overflow="visible"> 66 + <path 67 + d={svgPath} 68 + fill="none" 69 + stroke="#0A182B" 70 + stroke-width="2" 71 + stroke-linejoin="round" 72 + /> 73 + </svg> 74 + </button> 75 + 76 + <style> 77 + .login-btn { 78 + all: unset; 79 + position: relative; 80 + width: clamp(100px, 12vw, 140px); 81 + height: clamp(44px, 5vw, 56px); 82 + cursor: pointer; 83 + transition: transform 0.15s ease; 84 + } 85 + 86 + .login-btn:hover { 87 + transform: scale(1.05); 88 + } 89 + 90 + .btn-clipped { 91 + position: relative; 92 + width: 100%; 93 + height: 100%; 94 + display: flex; 95 + align-items: center; 96 + justify-content: center; 97 + } 98 + 99 + .btn-fill { 100 + position: absolute; 101 + inset: 0; 102 + background: #0A182B; 103 + } 104 + 105 + .btn-text { 106 + position: relative; 107 + z-index: 1; 108 + font-family: 'PicNic', cursive, system-ui; 109 + font-size: clamp(1rem, 1.5vw, 1.3rem); 110 + color: #FFDEED; 111 + letter-spacing: 1px; 112 + } 113 + 114 + .btn-stroke { 115 + position: absolute; 116 + inset: 0; 117 + width: 100%; 118 + height: 100%; 119 + z-index: 2; 120 + pointer-events: none; 121 + overflow: visible; 122 + } 123 + </style>