Synced LRC highlights the playing line, keeps it centred and seeks on click. Plain text scrolls continuously with a nudge knob instead — a highlight on guessed timings turns guaranteed drift into what looks like a bug. Seuraa kappaletta turns following off without losing the highlight, and scrolling by hand turns it off too. Fixes the scroll landing in the wrong place (offsetTop measured from a different coordinate space than the box it was applied to) and the fader shifting the deck sideways at score 100 (auto-sized grid columns plus a readout spanning both).
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}}
|