improve dashboard ux

- add bluesky butterfly icon to open feeds
- show "<1%" for platforms with non-zero but sub-1% share

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+31 -15
src
+31 -15
src/dashboard.zig
··· 70 70 \\ border-bottom: 1px solid #1a1a1a; 71 71 \\ } 72 72 \\ .feed-item:last-child { border-bottom: none; } 73 + \\ .feed-info { flex: 1; } 73 74 \\ .feed-name { color: #ccc; } 74 75 \\ .feed-desc { color: #555; font-size: 11px; } 76 + \\ .feed-link { display: flex; align-items: center; padding: 0.4rem 0.6rem; background: #1a1a1a; border-radius: 4px; } 77 + \\ .feed-link:hover { background: #252525; } 78 + \\ .feed-link svg { width: 16px; height: 16px; fill: #1185fe; } 75 79 \\ .status-bar { 76 80 \\ display: flex; 77 81 \\ align-items: center; ··· 126 130 \\ 127 131 \\ <div class="feeds"> 128 132 \\ <div class="feed-item"> 129 - \\ <div> 130 - \\ <a href="https://bsky.app/profile/did:plc:vs3hnzq2daqbszxlysywzy54/feed/music-atmosphere" class="feed-name">music atmosphere</a> 133 + \\ <div class="feed-info"> 134 + \\ <div class="feed-name">music atmosphere</div> 131 135 \\ <div class="feed-desc">all music posts</div> 132 136 \\ </div> 137 + \\ <a href="https://bsky.app/profile/did:plc:vs3hnzq2daqbszxlysywzy54/feed/music-atmosphere" class="feed-link" title="open in bluesky"><svg viewBox="0 0 568 501"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.07-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/></svg></a> 133 138 \\ </div> 134 139 \\ <div class="feed-item"> 135 - \\ <div> 136 - \\ <a href="https://bsky.app/profile/did:plc:vs3hnzq2daqbszxlysywzy54/feed/music-following" class="feed-name">music (following)</a> 140 + \\ <div class="feed-info"> 141 + \\ <div class="feed-name">music (following)</div> 137 142 \\ <div class="feed-desc">only from people you follow</div> 138 143 \\ </div> 144 + \\ <a href="https://bsky.app/profile/did:plc:vs3hnzq2daqbszxlysywzy54/feed/music-following" class="feed-link" title="open in bluesky"><svg viewBox="0 0 568 501"><path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.07-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/></svg></a> 139 145 \\ </div> 140 146 \\ </div> 141 147 \\ ··· 171 177 const total = platforms.soundcloud + platforms.bandcamp + platforms.spotify + platforms.plyr; 172 178 173 179 if (total > 0) { 174 - // calculate percentages 175 - const sc_pct = (platforms.soundcloud * 100) / total; 176 - const bc_pct = (platforms.bandcamp * 100) / total; 177 - const sp_pct = (platforms.spotify * 100) / total; 178 - const pl_pct = (platforms.plyr * 100) / total; 180 + // helper to format percentage - shows "<1%" for non-zero values that round to 0 181 + const PlatformData = struct { name: []const u8, count: u64, color: []const u8 }; 182 + const platform_list = [_]PlatformData{ 183 + .{ .name = "soundcloud", .count = platforms.soundcloud, .color = "#ff5500" }, 184 + .{ .name = "bandcamp", .count = platforms.bandcamp, .color = "#1da0c3" }, 185 + .{ .name = "spotify", .count = platforms.spotify, .color = "#1db954" }, 186 + .{ .name = "plyr.fm", .count = platforms.plyr, .color = "#7c3aed" }, 187 + }; 179 188 180 - try w.print( 181 - \\ <div class="platform-row"><span class="platform-name">soundcloud</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#ff5500"></div></div><span class="platform-pct">{d}%</span></div> 182 - \\ <div class="platform-row"><span class="platform-name">bandcamp</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#1da0c3"></div></div><span class="platform-pct">{d}%</span></div> 183 - \\ <div class="platform-row"><span class="platform-name">spotify</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#1db954"></div></div><span class="platform-pct">{d}%</span></div> 184 - \\ <div class="platform-row"><span class="platform-name">plyr.fm</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:#7c3aed"></div></div><span class="platform-pct">{d}%</span></div> 185 - , .{ sc_pct, sc_pct, bc_pct, bc_pct, sp_pct, sp_pct, pl_pct, pl_pct }); 189 + for (platform_list) |p| { 190 + const pct = (p.count * 100) / total; 191 + const bar_width = if (p.count > 0 and pct == 0) @as(u64, 1) else pct; // min 1% width if non-zero 192 + try w.print( 193 + \\ <div class="platform-row"><span class="platform-name">{s}</span><div class="bar"><div class="bar-fill" style="width:{d}%;background:{s}"></div></div><span class="platform-pct"> 194 + , .{ p.name, bar_width, p.color }); 195 + if (p.count > 0 and pct == 0) { 196 + try w.writeAll("&lt;1%"); 197 + } else { 198 + try w.print("{d}%", .{pct}); 199 + } 200 + try w.writeAll("</span></div>\n"); 201 + } 186 202 } else { 187 203 try w.writeAll( 188 204 \\ <div class="platform-row"><span class="criteria-list">collecting data...</span></div>