vod frog, frog with the vods
5
fork

Configure Feed

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

pagination: 10 per page with prev/next buttons, bold titles

goose.art 72986d27 add8b89f

+67 -23
+9 -2
src/lib/FrogHeader.svelte
··· 1 1 <script lang="ts"> 2 - // Header component with "vod frog" title and frog illustration 2 + let { onHomeClick }: { onHomeClick?: () => void } = $props(); 3 + 4 + function handleClick(e: MouseEvent) { 5 + if (onHomeClick) { 6 + e.preventDefault(); 7 + onHomeClick(); 8 + } 9 + } 3 10 </script> 4 11 5 12 <header class="frog-header"> 6 13 <div class="title-area"> 7 - <a href="/" class="logo-link"><h1 class="logo-text">vod frog</h1></a> 14 + <a href="/" class="logo-link" onclick={handleClick}><h1 class="logo-text">vod frog</h1></a> 8 15 <img src="/froggie.png" alt="A cute frog" class="header-frog" /> 9 16 </div> 10 17 <div class="subtitle-lines">
+1
src/lib/VideoCard.svelte
··· 325 325 margin: 0; 326 326 font-family: 'Fang', system-ui, sans-serif; 327 327 font-size: 0.9rem; 328 + font-weight: 700; 328 329 color: #0A182B; 329 330 line-height: 1.3; 330 331 display: -webkit-box;
+57 -21
src/routes/+page.svelte
··· 13 13 import FrogHeader from "$lib/FrogHeader.svelte"; 14 14 import WavyBorder from "$lib/WavyBorder.svelte"; 15 15 16 + const PAGE_SIZE = 10; 17 + 16 18 let videos: VideoRecord[] = $state([]); 17 - let cursor: string | undefined = $state(); 18 19 let loading = $state(false); 19 - let hasMore = $state(true); 20 20 let selectedVideo: VideoRecord | null = $state(null); 21 21 let selectedHandle = $state(""); 22 22 let error = $state(""); 23 23 24 + // Pagination state 25 + let cursorHistory: (string | undefined)[] = $state([undefined]); 26 + let pageIndex = $state(0); 27 + let hasMore = $state(true); 28 + 24 29 async function loadPage() { 25 - if (loading || !hasMore) return; 26 30 loading = true; 27 31 error = ""; 28 32 try { 29 - const res = await listVideos(cursor, 24); 30 - videos = [...videos, ...res.records]; 31 - cursor = res.cursor; 32 - hasMore = !!res.cursor && res.records.length > 0; 33 + const res = await listVideos(cursorHistory[pageIndex], PAGE_SIZE); 34 + videos = res.records; 35 + hasMore = !!res.cursor && res.records.length === PAGE_SIZE; 36 + // Store the next cursor if we haven't been to this page before 37 + if (hasMore && cursorHistory.length <= pageIndex + 1) { 38 + cursorHistory = [...cursorHistory, res.cursor]; 39 + } 33 40 } catch (e: any) { 34 41 error = e.message; 35 42 } 36 43 loading = false; 37 44 } 38 45 46 + function nextPage() { 47 + if (!hasMore || loading) return; 48 + pageIndex++; 49 + loadPage(); 50 + window.scrollTo({ top: 0, behavior: "smooth" }); 51 + } 52 + 53 + function prevPage() { 54 + if (pageIndex <= 0 || loading) return; 55 + pageIndex--; 56 + loadPage(); 57 + window.scrollTo({ top: 0, behavior: "smooth" }); 58 + } 59 + 39 60 function selectVideo(video: VideoRecord) { 40 61 selectedVideo = video; 41 62 resolveHandle(video.value.creator).then((h) => (selectedHandle = h)); ··· 53 74 window.history.pushState({}, "", url.toString()); 54 75 } 55 76 56 - // On mount: load videos, then check URL for a selected video 77 + // On mount: load first page, then check URL for a selected video 57 78 onMount(async () => { 58 79 await loadPage(); 59 80 const params = new URLSearchParams(window.location.search); ··· 91 112 </svelte:head> 92 113 93 114 <div class="app"> 94 - <FrogHeader /> 115 + <FrogHeader onHomeClick={closePlayer} /> 95 116 96 117 {#if selectedVideo} 97 118 <section class="player-section"> ··· 124 145 {/each} 125 146 </section> 126 147 127 - {#if hasMore} 128 - <div class="load-more"> 129 - <button onclick={loadPage} disabled={loading}> 130 - {loading ? "loading..." : "load more"} 148 + <div class="pagination"> 149 + {#if pageIndex > 0} 150 + <button class="page-btn" onclick={prevPage} disabled={loading}> 151 + ← previous 131 152 </button> 132 - </div> 133 - {/if} 153 + {/if} 154 + <span class="page-num">page {pageIndex + 1}</span> 155 + {#if hasMore} 156 + <button class="page-btn" onclick={nextPage} disabled={loading}> 157 + next → 158 + </button> 159 + {/if} 160 + </div> 134 161 </div> 135 162 136 163 <style> ··· 205 232 padding: 20px clamp(16px, 3vw, 24px); 206 233 } 207 234 208 - .load-more { 209 - text-align: center; 235 + .pagination { 236 + display: flex; 237 + justify-content: center; 238 + align-items: center; 239 + gap: 20px; 210 240 padding: 30px; 211 241 } 212 242 213 - .load-more button { 243 + .page-btn { 214 244 background: #0a182b; 215 245 color: #39ff44; 216 246 border: 3px solid #0a182b; 217 - padding: 12px 36px; 247 + padding: 12px 32px; 218 248 border-radius: 40px; 219 249 font-family: "PicNic", cursive, system-ui; 220 250 font-size: 1.1rem; ··· 223 253 letter-spacing: 0.5px; 224 254 } 225 255 226 - .load-more button:hover { 256 + .page-btn:hover { 227 257 background: #39ff44; 228 258 color: #0a182b; 229 259 border-color: #0a182b; 230 260 } 231 261 232 - .load-more button:disabled { 262 + .page-btn:disabled { 233 263 background: #1a8c22; 234 264 color: #0a182b; 235 265 border-color: #1a8c22; 236 266 cursor: wait; 237 267 opacity: 0.6; 268 + } 269 + 270 + .page-num { 271 + font-family: "PicNic", cursive, system-ui; 272 + font-size: 1.1rem; 273 + color: #0a182b; 238 274 } 239 275 240 276 .error {