tracks lexicons and how many times they appeared on the jetstream

feat(client): add dark mode

ptr.pet b7d77d22 e8cda421

verified
+80 -59
+9 -9
client/src/app.html
··· 1 1 <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="utf-8" /> 5 - <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 - %sveltekit.head% 8 - </head> 9 - <body data-sveltekit-preload-data="hover"> 10 - <div style="display: contents">%sveltekit.body%</div> 11 - </body> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 + %sveltekit.head% 8 + </head> 9 + <body class="bg-white dark:bg-gray-900" data-sveltekit-preload-data="hover"> 10 + <div style="display: contents">%sveltekit.body%</div> 11 + </body> 12 12 </html>
+2 -2
client/src/lib/components/BskyToggle.svelte
··· 11 11 <!-- svelte-ignore a11y_no_static_element_interactions --> 12 12 <button 13 13 onclick={onBskyToggle} 14 - class="wsbadge !mt-0 !font-normal bg-blue-100 hover:bg-blue-200 border-blue-300" 14 + class="wsbadge !mt-0 !font-normal bg-blue-100 dark:bg-blue-900 hover:bg-blue-200 dark:hover:bg-blue-800 border-blue-300 dark:border-blue-700" 15 15 > 16 16 <input checked={dontShowBsky} type="checkbox" /> 17 - <span class="ml-0.5"> hide app.bsky.* </span> 17 + <span class="ml-0.5 text-black dark:text-gray-200"> hide app.bsky.* </span> 18 18 </button>
+8 -5
client/src/lib/components/EventCard.svelte
··· 104 104 </script> 105 105 106 106 <div 107 - class="group flex flex-col gap-2 p-1.5 md:p-3 min-h-64 bg-white border border-gray-200 rounded-lg hover:shadow-lg md:hover:-translate-y-1 transition-all duration-200 transform" 107 + class="group flex flex-col gap-2 p-1.5 md:p-3 min-h-64 bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-gray-950 rounded-lg hover:shadow-lg md:hover:-translate-y-1 transition-all duration-200 transform" 108 108 class:has-activity={isAnimating} 109 109 style="--border-thickness: {borderThickness}px" 110 110 > 111 111 <div class="flex items-start gap-2"> 112 112 <div 113 - class="text-sm font-bold text-blue-600 bg-blue-100 px-3 py-1 rounded-full" 113 + class="text-sm font-bold text-blue-600 bg-blue-100 dark:bg-indigo-950 px-3 py-1 rounded-full" 114 114 > 115 115 #{index + 1} 116 116 </div> 117 117 <div 118 118 title={event.nsid} 119 - class="font-mono text-sm text-gray-700 mt-0.5 leading-relaxed rounded-full text-nowrap text-ellipsis overflow-hidden group-hover:overflow-visible group-hover:bg-gray-50 border-gray-100 group-hover:border transition-all px-1" 119 + class="font-mono text-sm text-gray-700 dark:text-gray-300 mt-0.5 leading-relaxed rounded-full text-nowrap text-ellipsis overflow-hidden group-hover:overflow-visible group-hover:bg-gray-50 dark:group-hover:bg-gray-700 border-gray-100 dark:border-gray-900 group-hover:border transition-all px-1" 120 120 > 121 121 {event.nsid} 122 122 </div> ··· 136 136 </div> 137 137 </div> 138 138 139 - <style> 139 + <style lang="postcss"> 140 140 .has-activity { 141 141 position: relative; 142 142 transition: all 0.2s ease-out; 143 143 } 144 144 145 145 .has-activity::before { 146 + @reference "../../app.css"; 147 + @apply border-blue-500 dark:border-blue-800; 146 148 content: ""; 147 149 position: absolute; 148 150 top: calc(-1 * var(--border-thickness)); 149 151 left: calc(-1 * var(--border-thickness)); 150 152 right: calc(-1 * var(--border-thickness)); 151 153 bottom: calc(-1 * var(--border-thickness)); 152 - border: var(--border-thickness) solid rgba(59, 130, 246, 0.8); 154 + border-width: var(--border-thickness); 155 + border-style: solid; 153 156 border-radius: calc(0.5rem + var(--border-thickness)); 154 157 pointer-events: none; 155 158 transition: all 0.3s ease-out;
+5 -3
client/src/lib/components/FilterControls.svelte
··· 8 8 </script> 9 9 10 10 <div 11 - class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-blue-100 hover:bg-blue-200 border-blue-300" 11 + class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-blue-100 dark:bg-blue-900 hover:bg-blue-200 dark:hover:bg-blue-800 border-blue-300 dark:border-blue-700" 12 12 > 13 - <label for="filter-regex" class="text-blue-800 mr-1"> filter: </label> 13 + <label for="filter-regex" class="text-blue-800 dark:text-gray-200 mr-1"> 14 + filter: 15 + </label> 14 16 <input 15 17 id="filter-regex" 16 18 value={filterRegex} 17 19 oninput={(e) => onFilterChange((e.target as HTMLInputElement).value)} 18 20 type="text" 19 21 placeholder="regex..." 20 - class="bg-blue-50 text-blue-900 placeholder-blue-400 border border-blue-200 rounded-full px-1 outline-none focus:bg-white focus:border-blue-400 min-w-0 w-24" 22 + class="bg-blue-50 dark:bg-blue-950 text-blue-900 dark:text-gray-400 placeholder-blue-400 dark:placeholder-blue-700 border border-blue-200 dark:border-blue-700 rounded-full px-1 outline-none focus:border-blue-400 min-w-0 w-24" 21 23 /> 22 24 </div>
+6 -4
client/src/lib/components/RefreshControl.svelte
··· 8 8 </script> 9 9 10 10 <div 11 - class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-lime-100 hover:bg-lime-200 border-lime-300" 11 + class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-lime-100 dark:bg-lime-900 dark:hover:bg-lime-800 hover:bg-lime-200 border-lime-300 dark:border-lime-700" 12 12 > 13 - <label for="refresh-rate" class="text-lime-800 mr-1">refresh:</label> 13 + <label for="refresh-rate" class="text-lime-800 dark:text-lime-200 mr-1" 14 + >refresh:</label 15 + > 14 16 <input 15 17 id="refresh-rate" 16 18 value={refreshRate} ··· 24 26 pattern="[0-9]*" 25 27 min="0" 26 28 placeholder="real-time" 27 - class="bg-green-50 text-lime-900 placeholder-lime-600 border border-lime-200 rounded-full px-1 outline-none focus:bg-white focus:border-lime-400 min-w-0 w-20" 29 + class="bg-green-50 dark:bg-green-900 text-lime-900 dark:text-lime-200 placeholder-lime-600 dark:placeholder-lime-400 border border-lime-200 dark:border-lime-700 rounded-full px-1 outline-none focus:border-lime-400 min-w-0 w-20" 28 30 /> 29 - <span class="text-lime-700">s</span> 31 + <span class="text-lime-800 dark:text-lime-200">s</span> 30 32 </div>
+5 -3
client/src/lib/components/ShowControls.svelte
··· 12 12 </script> 13 13 14 14 <div 15 - class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-pink-100 hover:bg-pink-200 border-pink-300" 15 + class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-pink-100 dark:bg-pink-800 hover:bg-pink-200 dark:hover:bg-pink-700 border-pink-300 dark:border-pink-700" 16 16 > 17 - <label for="show" class="text-pink-800 mr-1"> show since: </label> 17 + <label for="show" class="text-pink-800 dark:text-pink-100 mr-1"> 18 + show since: 19 + </label> 18 20 <select 19 21 id="show" 20 22 value={show} 21 23 onchange={(e) => 22 24 onShowChange((e.target as HTMLSelectElement).value as ShowOption)} 23 - class="bg-pink-50 text-pink-900 border border-pink-200 rounded-full px-1 outline-none focus:bg-white focus:border-pink-400 min-w-0" 25 + class="bg-pink-50 dark:bg-pink-900 text-pink-900 dark:text-pink-100 border border-pink-200 dark:border-pink-700 rounded-full px-1 outline-none focus:border-pink-400 min-w-0" 24 26 > 25 27 {#each showOptions as option} 26 28 <option value={option}>{option}</option>
+5 -3
client/src/lib/components/SortControls.svelte
··· 17 17 </script> 18 18 19 19 <div 20 - class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-purple-100 hover:bg-purple-200 border-purple-300" 20 + class="wsbadge !pl-2 !px-1 !mt-0 !font-normal bg-purple-100 dark:bg-purple-800 hover:bg-purple-200 dark:hover:bg-purple-700 border-purple-300 dark:border-purple-700" 21 21 > 22 - <label for="sort-by" class="text-purple-800 mr-1"> sort by: </label> 22 + <label for="sort-by" class="text-purple-800 dark:text-purple-300 mr-1"> 23 + sort by: 24 + </label> 23 25 <select 24 26 id="sort-by" 25 27 value={sortBy} 26 28 onchange={(e) => 27 29 onSortChange((e.target as HTMLSelectElement).value as SortOption)} 28 - class="bg-purple-50 text-purple-900 border border-purple-200 rounded-full px-1 outline-none focus:bg-white focus:border-purple-400 min-w-0" 30 + class="bg-purple-50 dark:bg-purple-900 text-purple-900 dark:text-purple-300 border border-purple-200 dark:border-purple-700 rounded-full px-1 outline-none focus:border-purple-400 min-w-0" 29 31 > 30 32 {#each sortOptions as option} 31 33 <option value={option.value}>{option.label}</option>
+16 -16
client/src/lib/components/StatsCard.svelte
··· 3 3 4 4 const colorClasses = { 5 5 green: { 6 - bg: "from-green-50 to-green-100", 7 - border: "border-green-200", 8 - titleText: "text-green-700", 9 - valueText: "text-green-900", 6 + bg: "from-green-50 to-green-100 dark:from-green-900 dark:to-green-800", 7 + border: "border-green-200 dark:border-green-800", 8 + titleText: "text-green-700 dark:text-green-400", 9 + valueText: "text-green-900 dark:text-green-200", 10 10 }, 11 11 red: { 12 - bg: "from-red-50 to-red-100", 13 - border: "border-red-200", 14 - titleText: "text-red-700", 15 - valueText: "text-red-900", 12 + bg: "from-red-50 to-red-100 dark:from-red-900 dark:to-red-800", 13 + border: "border-red-200 dark:border-red-800", 14 + titleText: "text-red-700 dark:text-red-400", 15 + valueText: "text-red-900 dark:text-red-200", 16 16 }, 17 17 turqoise: { 18 - bg: "from-teal-50 to-teal-100", 19 - border: "border-teal-200", 20 - titleText: "text-teal-700", 21 - valueText: "text-teal-900", 18 + bg: "from-teal-50 to-teal-100 dark:from-teal-900 dark:to-teal-800", 19 + border: "border-teal-200 dark:border-teal-800", 20 + titleText: "text-teal-700 dark:text-teal-400", 21 + valueText: "text-teal-900 dark:text-teal-200", 22 22 }, 23 23 orange: { 24 - bg: "from-orange-50 to-orange-100", 25 - border: "border-orange-200", 26 - titleText: "text-orange-700", 27 - valueText: "text-orange-900", 24 + bg: "from-orange-50 to-orange-100 dark:from-orange-900 dark:to-orange-800", 25 + border: "border-orange-200 dark:border-orange-800", 26 + titleText: "text-orange-700 dark:text-orange-400", 27 + valueText: "text-orange-900 dark:text-orange-200", 28 28 }, 29 29 }; 30 30
+9 -5
client/src/lib/components/StatusBadge.svelte
··· 8 8 const statusConfig = { 9 9 connected: { 10 10 text: "stream live", 11 - classes: "bg-green-100 text-green-800 border-green-200", 11 + classes: 12 + "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 border-green-200 dark:border-green-800", 12 13 }, 13 14 connecting: { 14 15 text: "stream connecting", 15 - classes: "bg-yellow-100 text-yellow-800 border-yellow-200", 16 + classes: 17 + "bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 border-yellow-200 dark:border-yellow-800", 16 18 }, 17 19 error: { 18 20 text: "stream errored", 19 - classes: "bg-red-100 text-red-800 border-red-200", 21 + classes: 22 + "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 border-red-200 dark:border-red-800", 20 23 }, 21 24 disconnected: { 22 25 text: "stream offline", 23 - classes: "bg-gray-100 text-gray-800 border-gray-200", 26 + classes: 27 + "bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 border-gray-200 dark:border-gray-800", 24 28 }, 25 29 }; 26 30 ··· 31 35 <!-- connecting spinner --> 32 36 {#if status === "connecting"} 33 37 <div 34 - class="animate-spin rounded-full h-4 w-4 border-b-2 border-yellow-800" 38 + class="animate-spin rounded-full h-4 w-4 border-b-2 border-yellow-800 dark:border-yellow-200" 35 39 ></div> 36 40 {/if} 37 41 <!-- status text -->
+15 -9
client/src/routes/+page.svelte
··· 267 267 /> 268 268 </svelte:head> 269 269 270 - <header class="border-gray-300 border-b mb-4 pb-2"> 270 + <header 271 + class="bg-white dark:bg-gray-900 border-gray-300 dark:border-gray-950 border-b mb-4 pb-2" 272 + > 271 273 <div 272 274 class="px-2 md:ml-[19vw] mx-auto flex flex-wrap items-center text-center" 273 275 > 274 - <h1 class="text-4xl font-bold mr-4 text-gray-900">lexicon tracker</h1> 275 - <p class="text-lg mt-1 text-gray-600"> 276 + <h1 class="text-4xl font-bold mr-4 text-gray-900 dark:text-gray-200"> 277 + lexicon tracker 278 + </h1> 279 + <p class="text-lg mt-1 text-gray-600 dark:text-gray-300"> 276 280 tracks lexicons seen on the jetstream {tracking_since === 0 277 281 ? "" 278 282 : `(since: ${formatTimestamp(tracking_since)})`} 279 283 </p> 280 284 </div> 281 285 </header> 282 - <div class="md:max-w-[61vw] mx-auto p-2"> 286 + <div class="bg-white dark:bg-gray-900 md:max-w-[61vw] mx-auto p-2"> 283 287 <div class="min-w-fit grid grid-cols-2 xl:grid-cols-4 gap-2 2xl:gap-6 mb-8"> 284 288 <StatsCard 285 289 title="total creation" ··· 305 309 306 310 {#if error} 307 311 <div 308 - class="bg-red-100 border border-red-300 text-red-700 px-4 py-3 rounded-lg mb-6" 312 + class="bg-red-100 dark:bg-red-900 border border-red-300 dark:border-red-700 text-red-700 dark:text-red-200 px-4 py-3 rounded-lg mb-6" 309 313 > 310 314 <p>Error: {error}</p> 311 315 </div> ··· 314 318 {#if eventsList.length > 0} 315 319 <div class="mb-8"> 316 320 <div class="flex flex-wrap items-center gap-3 mb-3"> 317 - <h2 class="text-2xl font-bold text-gray-900">seen lexicons</h2> 321 + <h2 class="text-2xl font-bold text-gray-900 dark:text-gray-200"> 322 + seen lexicons 323 + </h2> 318 324 <StatusBadge status={websocketStatus} /> 319 325 </div> 320 326 <div class="flex flex-wrap items-center gap-1.5 mb-6"> ··· 367 373 {/if} 368 374 </div> 369 375 370 - <footer class="py-2 border-t border-gray-200 text-center"> 371 - <p class="text-gray-600 text-sm"> 376 + <footer class="py-2 border-t border-gray-200 dark:border-gray-800 text-center"> 377 + <p class="text-gray-600 dark:text-gray-200 text-sm"> 372 378 source code <a 373 379 href="https://tangled.sh/@poor.dog/nsid-tracker" 374 380 target="_blank" 375 381 rel="noopener noreferrer" 376 - class="text-blue-600 hover:text-blue-800 underline" 382 + class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-600 underline" 377 383 >@poor.dog/nsid-tracker</a 378 384 > 379 385 </p>