feat: add lock icon to track detail page for gated tracks (#641)

- shows lock icon next to title when track.gated is true
- guards addToQueue with gated check + toast

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

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

authored by zzstoatzz.io Claude Opus 4.5 and committed by GitHub 45ac213e 058cede3

Changed files
+29 -2
frontend
src
routes
track
+29 -2
frontend/src/routes/track/[id]/+page.svelte
··· 12 12 import { checkImageSensitive } from '$lib/moderation.svelte'; 13 13 import { player } from '$lib/player.svelte'; 14 14 import { queue } from '$lib/queue.svelte'; 15 - import { playTrack } from '$lib/playback.svelte'; 15 + import { playTrack, guardGatedTrack } from '$lib/playback.svelte'; 16 16 import { auth } from '$lib/auth.svelte'; 17 17 import { toast } from '$lib/toast.svelte'; 18 18 import type { Track } from '$lib/types'; ··· 120 120 } 121 121 122 122 function addToQueue() { 123 + if (!guardGatedTrack(track, auth.isAuthenticated)) return; 123 124 queue.addTracks([track]); 124 125 toast.success(`queued ${track.title}`, 1800); 125 126 } ··· 429 430 <!-- track info wrapper --> 430 431 <div class="track-info-wrapper"> 431 432 <div class="track-info"> 432 - <h1 class="track-title">{track.title}</h1> 433 + <h1 class="track-title"> 434 + {track.title} 435 + {#if track.gated} 436 + <span class="gated-badge" title="supporters only"> 437 + <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> 438 + <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/> 439 + </svg> 440 + </span> 441 + {/if} 442 + </h1> 433 443 <div class="track-metadata"> 434 444 <a href="/u/{track.artist_handle}" class="artist-link"> 435 445 {track.artist} ··· 719 729 margin: 0; 720 730 line-height: 1.2; 721 731 text-align: center; 732 + display: inline-flex; 733 + align-items: center; 734 + justify-content: center; 735 + gap: 0.5rem; 736 + flex-wrap: wrap; 737 + } 738 + 739 + .gated-badge { 740 + display: inline-flex; 741 + align-items: center; 742 + justify-content: center; 743 + color: var(--accent); 744 + opacity: 0.8; 745 + } 746 + 747 + .gated-badge svg { 748 + display: block; 722 749 } 723 750 724 751 .track-metadata {