Lyrics, versioning, and the song page's fact line. - Lyrics are suggested at submission and never imposed: the worker makes one LRCLIB lookup, and Hae sanoitukset re-queries with whatever title and artist are typed. Neither overwrites what the submitter wrote. They live on the submission, travel to the song at publish, and stay editable after the song locks — the lock freezes what a song claims to be, and nobody reviewed the lyrics - The review strip reads and writes side by side: lyrics left, review right. Synced LRC highlights the playing line and seeks on click; plain text scrolls continuously with a nudge knob. Following can be turned off - CalVer YYYY.MM.DD-N, injected from the git tag with -ldflags, shown in the footer, the startup log and /healthz - The song page's metadata became four labelled cells instead of one flat run of five different kinds of fact Fixes: publishing wiped lyrics the worker had just fetched (a request that omitted a field cleared it), lyric auto-scroll landed in the wrong place, and the fader shifted the deck sideways at score 100.
66 lines
2.7 KiB
HTML
66 lines
2.7 KiB
HTML
{{/* Two shapes, because timed lyrics and guessed lyrics deserve different treatment.
|
||
Synced: one element per line with its own timestamp, highlighted as it comes.
|
||
Plain: one block that scrolls continuously, with a nudge knob, because a highlight on evenly
|
||
guessed timings turns guaranteed drift into what looks like a bug. */}}
|
||
{{define "lyricsview"}}
|
||
<span class="cap">Sanoitukset</span>
|
||
{{$lines := .LyricLines}}
|
||
{{if $lines}}
|
||
<div class="lyricsbox synced" data-song="{{.ID}}">
|
||
{{range $lines}}<p class="lline" data-t="{{.At}}">{{if .Text}}{{.Text}}{{else}} {{end}}</p>{{end}}
|
||
</div>
|
||
<label class="follow">
|
||
<input type="checkbox" checked> Seuraa kappaletta
|
||
<span class="muted small">— korostus jatkuu, sivu ei vieri</span>
|
||
</label>
|
||
{{else}}
|
||
<div class="lyricsbox plain" data-duration="{{.Duration}}" data-song="{{.ID}}">
|
||
<div class="lscroll">{{lyricstext .Lyrics}}</div>
|
||
</div>
|
||
<label class="nudge">
|
||
Ajoitus
|
||
<input type="range" min="-10" max="10" step="0.5" value="0" aria-label="Ajoituksen siirto sekunteina">
|
||
<output>0 s</output>
|
||
</label>
|
||
{{end}}
|
||
{{end}}
|
||
|
||
{{define "player"}}
|
||
<!-- Ships with native controls; player.js removes them and drives the same element. No JS means
|
||
the browser's own player, which is plain but complete. -->
|
||
<div class="playerwrap" data-duration="{{.Duration}}">
|
||
<audio controls preload="none" src="/audio/{{.ID}}" class="player"></audio>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{define "songcard"}}
|
||
<a class="songcard{{if and (not .Own) (not .Reviewed)}} unreviewed{{end}}" href="/songs/{{.ID}}">
|
||
{{if .Average}}<span class="scorebadge">{{score .Average}}</span>
|
||
{{else if .ReviewCount}}<span class="scorebadge sealed" title="Pisteet paljastuvat kun arvostelet"></span>{{end}}
|
||
<span class="title">{{.Title}}</span>
|
||
<span class="artist">{{.Artist}}</span>
|
||
<span class="meta">
|
||
<span class="badge genre">{{.GenreLabel}}</span>
|
||
<span>{{.Length}}</span>
|
||
<span>{{.Submitter}}</span>
|
||
{{if .Own}}<span class="badge admin">oma</span>
|
||
{{else if .Reviewed}}<span class="badge reviewed">arvosteltu</span>
|
||
{{else}}<span class="badge pending">arvostelematta</span>{{end}}
|
||
{{if .ReviewCount}}<span>{{.ReviewCount}} arvostelua</span>{{end}}
|
||
</span>
|
||
</a>
|
||
{{end}}
|
||
|
||
{{define "fader"}}
|
||
<!-- A native range input turned vertical: keyboard, form submission and the value all stay free. -->
|
||
<div class="fader">
|
||
<span class="ticks" aria-hidden="true">
|
||
<span>100</span><span>75</span><span>50</span><span>25</span><span>1</span>
|
||
</span>
|
||
<input type="range" name="score" min="1" max="100" value="{{.}}"
|
||
aria-label="Pisteet 1–100"
|
||
oninput="this.closest('.fader').querySelector('output').value = this.value">
|
||
<output class="readout">{{.}}</output>
|
||
</div>
|
||
{{end}}
|