Search
- Albums and songs search Deezer and MusicBrainz at once and merge the
results: duplicates keep the Deezer copy, best word matches first.
Deezer's search misses albums it carries (Death Magnetic) and most game
soundtracks; MusicBrainz finds them
- MusicBrainz secondary types are shown ("Soundtrack", "Live")
- Settings: Deezer + MusicBrainz (default), Deezer only, MusicBrainz only
- Search results show which service they came from
Previews
- Songs and albums get a play button (cards, rows, detail view, editor,
and Deezer search results) for a 30-second Deezer clip; albums play
their most popular track
- Clips are looked up fresh (Deezer URLs expire), cached 10 minutes, and
streamed through Canon with Range support; hidden lists stay private
- Entries from MusicBrainz or added by hand are matched to Deezer on save
so they can preview too
- "Play previews" toggle in Settings → Profile
Tests cover merging and title/artist matching; `-tags live` runs checks
against the real services.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
30 lines
1.6 KiB
HTML
30 lines
1.6 KiB
HTML
{{define "items"}}
|
|
<ol class="items items--{{.Layout}} aspect-{{.Category.Aspect}}" data-items data-list-title="{{.Title}}" start="{{with index .Items 0}}{{.Rank}}{{end}}">
|
|
{{- range .Items}}
|
|
<li class="item{{if le .Rank 3}} item--top item--top{{.Rank}}{{end}}">
|
|
<button type="button" class="item-btn" data-item="{{.ID}}">
|
|
<span class="item-art">
|
|
{{- if .Image}}<img src="{{.Image}}" alt="" loading="lazy" decoding="async">{{else}}<span class="art-fallback">{{icon $.Category.Icon}}</span>{{end -}}
|
|
<span class="rank-badge" aria-hidden="true">{{.Rank}}</span>
|
|
</span>
|
|
<span class="item-rank" aria-hidden="true">{{.Rank}}</span>
|
|
<span class="item-body">
|
|
<span class="item-title"><span class="sr-only">#{{.Rank}}: </span>{{.Title}}</span>
|
|
{{- if .Badge}}
|
|
{{- with .Subtitle}}<span class="item-sub">{{.}}</span>{{end}}
|
|
<span class="item-tags"><span class="badge">{{.Badge}}</span>{{with .Year}}<span class="item-year">{{.}}</span>{{end}}</span>
|
|
{{- else if or .Subtitle .Year}}
|
|
<span class="item-sub">{{.Subtitle}}{{if and .Subtitle .Year}}<span class="dot" aria-hidden="true">·</span>{{end}}{{.Year}}</span>
|
|
{{- end}}
|
|
{{- with .Note}}<span class="item-note">{{.}}</span>{{end}}
|
|
</span>
|
|
</button>
|
|
{{- if .Preview}}
|
|
<button type="button" class="play-btn" data-preview-src="/preview/{{.ID}}" aria-pressed="false" aria-label="Play a preview of {{.Title}}" title="Play 30-second preview">{{icon "play"}}{{icon "pause"}}</button>
|
|
{{- end}}
|
|
</li>
|
|
{{- end}}
|
|
</ol>
|
|
<script type="application/json" class="items-data">{{.Items}}</script>
|
|
{{end}}
|