audio streaming app plyr.fm

fix: redesign activity feed — better icons, visual hierarchy (#1003)

drop filled icons for outline-style strokes matching app conventions.
upload arrow for shares (not music note — could be any audio), heart
outline for likes, person+ for joins (not star). add per-type colored
left border accent for visual rhythm. larger avatars, two-line layout
with name/time header + action line below.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by zzstoatzz.io

Claude Opus 4.6 and committed by
GitHub
304458a6 2d6c4f90

+105 -88
+105 -88
frontend/src/routes/activity/+page.svelte
··· 102 102 {:else} 103 103 <div class="event-list"> 104 104 {#each events as event (event.created_at + event.actor.did + event.type)} 105 - <div class="event-item"> 105 + <div class="event-item {event.type}"> 106 106 <a href="/u/{event.actor.handle}" class="avatar-link"> 107 107 {#if event.actor.avatar_url} 108 108 <img ··· 116 116 </a> 117 117 118 118 <div class="event-body"> 119 - <div class="event-row"> 120 - <p class="event-description"> 121 - <span class="event-icon"> 122 - {#if event.type === 'like'} 123 - <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> 124 - {:else if event.type === 'track'} 125 - <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 3v10.55A4 4 0 1 0 14 17V7h4V3h-6zM10 19a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg> 126 - {:else if event.type === 'comment'} 127 - <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10z"/></svg> 128 - {:else if event.type === 'join'} 129 - <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.09 6.26L20.18 9l-5.09 3.74L17.18 19 12 15.27 6.82 19l2.09-6.26L3.82 9l6.09-.74L12 2z"/></svg> 130 - {/if} 131 - </span> 119 + <div class="event-header"> 120 + <a href="/u/{event.actor.handle}" class="handle-link"> 121 + {event.actor.display_name || event.actor.handle} 122 + </a> 123 + <span class="event-time">{timeAgo(event.created_at)}</span> 124 + </div> 132 125 133 - <span class="event-text"> 134 - <a href="/u/{event.actor.handle}" class="handle-link"> 135 - {event.actor.display_name || event.actor.handle} 136 - </a> 137 - 138 - {#if event.type === 'like' && event.track} 139 - <span class="action-verb">liked</span> 140 - <a href="/track/{event.track.id}" class="track-link"> 141 - {event.track.title} 142 - </a> 143 - {:else if event.type === 'track' && event.track} 144 - <span class="action-verb">posted</span> 145 - <a href="/track/{event.track.id}" class="track-link"> 146 - {event.track.title} 147 - </a> 148 - {:else if event.type === 'comment' && event.track} 149 - <span class="action-verb">commented on</span> 150 - <a href="/track/{event.track.id}" class="track-link"> 151 - {event.track.title} 152 - </a> 153 - {:else if event.type === 'join'} 154 - <span class="action-verb">joined plyr.fm</span> 155 - {/if} 156 - </span> 126 + {#if event.type === 'like' && event.track} 127 + <p class="event-action"> 128 + <svg class="action-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg> 129 + liked <a href="/track/{event.track.id}" class="track-link">{event.track.title}</a> 130 + </p> 131 + {:else if event.type === 'track' && event.track} 132 + <p class="event-action"> 133 + <svg class="action-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg> 134 + shared <a href="/track/{event.track.id}" class="track-link">{event.track.title}</a> 135 + </p> 136 + {:else if event.type === 'comment' && event.track} 137 + <p class="event-action"> 138 + <svg class="action-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> 139 + commented on <a href="/track/{event.track.id}" class="track-link">{event.track.title}</a> 140 + </p> 141 + {:else if event.type === 'join'} 142 + <p class="event-action"> 143 + <svg class="action-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg> 144 + joined plyr.fm 157 145 </p> 158 - 159 - <span class="event-time">{timeAgo(event.created_at)}</span> 160 - </div> 146 + {/if} 161 147 162 148 {#if event.type === 'comment' && event.comment_text} 163 149 <p class="comment-preview"> 164 - {event.comment_text.length > 100 165 - ? event.comment_text.slice(0, 100) + '...' 150 + {event.comment_text.length > 120 151 + ? event.comment_text.slice(0, 120) + '...' 166 152 : event.comment_text} 167 153 </p> 168 154 {/if} ··· 210 196 .event-list { 211 197 display: flex; 212 198 flex-direction: column; 213 - gap: 0.5rem; 199 + gap: 0.375rem; 214 200 } 215 201 216 202 .event-item { 217 203 display: flex; 218 - gap: 0.75rem; 219 - padding: 0.75rem; 204 + gap: 0.875rem; 205 + padding: 0.875rem 1rem; 220 206 background: var(--track-bg); 221 207 border: 1px solid var(--track-border); 222 208 border-radius: var(--radius-md); 209 + border-left: 3px solid var(--border-subtle); 223 210 transition: all 0.15s ease; 224 211 } 225 212 226 213 .event-item:hover { 227 214 background: var(--track-bg-hover); 228 - border-color: color-mix(in srgb, var(--accent) 25%, var(--track-border)); 215 + border-color: color-mix(in srgb, var(--accent) 20%, var(--track-border)); 216 + } 217 + 218 + /* per-type left accent — creates visual rhythm in the feed */ 219 + .event-item.like { 220 + border-left-color: #e0607e; 221 + } 222 + .event-item.like:hover { 223 + border-left-color: #e87a94; 224 + } 225 + .event-item.track { 226 + border-left-color: var(--accent); 227 + } 228 + .event-item.track:hover { 229 + border-left-color: var(--accent-hover, var(--accent)); 230 + } 231 + .event-item.comment { 232 + border-left-color: #a78bfa; 233 + } 234 + .event-item.comment:hover { 235 + border-left-color: #b9a2fb; 236 + } 237 + .event-item.join { 238 + border-left-color: #4ade80; 239 + } 240 + .event-item.join:hover { 241 + border-left-color: #6ee7a0; 229 242 } 230 243 231 244 .avatar-link { 232 245 flex-shrink: 0; 246 + align-self: center; 233 247 } 234 248 235 249 .avatar { 236 - width: 36px; 237 - height: 36px; 250 + width: 44px; 251 + height: 44px; 238 252 border-radius: 50%; 239 253 object-fit: cover; 240 - border: 2px solid var(--border-subtle); 241 254 } 242 255 243 256 .avatar.placeholder { 244 257 background: var(--bg-tertiary); 258 + border: 1px solid var(--border-subtle); 245 259 } 246 260 247 261 .event-body { ··· 249 263 min-width: 0; 250 264 } 251 265 252 - .event-row { 266 + .event-header { 253 267 display: flex; 254 268 align-items: baseline; 269 + justify-content: space-between; 255 270 gap: 0.75rem; 271 + margin-bottom: 0.125rem; 256 272 } 257 273 258 - .event-description { 259 - flex: 1; 274 + .handle-link { 275 + color: var(--text-primary); 276 + font-weight: 600; 260 277 font-size: var(--text-sm); 261 - color: var(--text-secondary); 262 - margin: 0; 263 - line-height: 1.4; 264 - min-width: 0; 265 - display: flex; 266 - align-items: baseline; 267 - gap: 0.375rem; 278 + text-decoration: none; 279 + white-space: nowrap; 280 + overflow: hidden; 281 + text-overflow: ellipsis; 268 282 } 269 283 270 - .event-icon { 271 - flex-shrink: 0; 272 - display: inline-flex; 273 - align-items: center; 274 - color: var(--text-tertiary); 275 - position: relative; 276 - top: 2px; 284 + .handle-link:hover { 285 + color: var(--accent); 277 286 } 278 287 279 - .event-text { 280 - min-width: 0; 288 + .event-time { 289 + flex-shrink: 0; 290 + font-size: var(--text-xs); 291 + color: var(--text-muted); 292 + white-space: nowrap; 281 293 } 282 294 283 - .action-verb { 295 + .event-action { 296 + font-size: var(--text-sm); 284 297 color: var(--text-tertiary); 298 + margin: 0; 299 + line-height: 1.4; 300 + display: flex; 301 + align-items: center; 302 + gap: 0.375rem; 285 303 } 286 304 287 - .handle-link { 288 - color: var(--text-primary); 289 - font-weight: 600; 290 - text-decoration: none; 305 + .action-icon { 306 + flex-shrink: 0; 307 + opacity: 0.5; 291 308 } 292 309 293 - .handle-link:hover { 294 - color: var(--accent); 295 - } 310 + /* tint icons to match their event type accent */ 311 + .like .action-icon { color: #e0607e; } 312 + .track .action-icon { color: var(--accent); } 313 + .comment .action-icon { color: #a78bfa; } 314 + .join .action-icon { color: #4ade80; } 296 315 297 316 .track-link { 298 - color: var(--text-primary); 317 + color: var(--text-secondary); 299 318 text-decoration: none; 300 319 font-weight: 500; 301 320 } ··· 307 326 .comment-preview { 308 327 font-size: var(--text-xs); 309 328 color: var(--text-tertiary); 310 - margin: 0.5rem 0 0 0; 329 + margin: 0.375rem 0 0 0; 311 330 line-height: 1.4; 312 331 font-style: italic; 313 332 background: color-mix(in srgb, var(--accent) 5%, transparent); 314 - border-left: 2px solid var(--accent); 315 - padding: 0.5rem 0.75rem; 333 + border-left: 2px solid color-mix(in srgb, #a78bfa 40%, transparent); 334 + padding: 0.375rem 0.625rem; 316 335 border-radius: var(--radius-sm); 317 336 } 318 337 319 - .event-time { 320 - flex-shrink: 0; 321 - font-size: var(--text-xs); 322 - color: var(--text-muted); 323 - white-space: nowrap; 324 - } 325 - 326 338 .scroll-sentinel { 327 339 display: flex; 328 340 justify-content: center; ··· 333 345 @media (max-width: 768px) { 334 346 main { 335 347 padding: 0 0.75rem calc(var(--player-height, 0px) + 1.25rem + env(safe-area-inset-bottom, 0px)); 348 + } 349 + 350 + .avatar { 351 + width: 38px; 352 + height: 38px; 336 353 } 337 354 } 338 355 </style>