Follow the song in the lyrics

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).
This commit is contained in:
Esa Kataja
2026-08-01 00:45:40 +03:00
parent 4f337b6202
commit a9776c6dde
8 changed files with 281 additions and 8 deletions
+1
View File
@@ -9,6 +9,7 @@
<link rel="stylesheet" href="/static/style.css">
<script src="/static/htmx.min.js" defer></script>
<script src="/static/player.js" defer></script>
<script src="/static/lyrics.js" defer></script>
</head>
<body>
<header class="topbar">
+27
View File
@@ -1,3 +1,30 @@
{{/* 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}}&nbsp;{{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. -->
+2 -5
View File
@@ -53,11 +53,8 @@
<!-- Two panes when the song has lyrics: read on the left, write on the right, so following
the words costs no scrolling. Without lyrics the pane is absent, not empty. -->
<div class="panes{{if not $s.Lyrics}} solo{{end}}">
{{with $s.Lyrics}}
<div class="lyricspane">
<span class="cap">Sanoitukset</span>
<div class="lyricsbox">{{lyricstext .}}</div>
</div>
{{if $s.Lyrics}}
<div class="lyricspane">{{template "lyricsview" $s}}</div>
{{end}}
<div class="writepane">
<label class="grow">Arvostelu