diff --git a/js/lyrics.js b/js/lyrics.js
index 53ed59b..785429b 100644
--- a/js/lyrics.js
+++ b/js/lyrics.js
@@ -133,6 +133,40 @@ export class LyricsManager {
this.geniusManager = new GeniusManager();
this.isGeniusMode = false;
this.currentGeniusData = null;
+ this.timingOffset = 0; // Offset in milliseconds (positive = delay lyrics, negative = advance lyrics)
+ }
+
+ // Get timing offset for current track
+ getTimingOffset(trackId) {
+ try {
+ const key = `lyrics-offset-${trackId}`;
+ const stored = localStorage.getItem(key);
+ return stored ? parseInt(stored, 10) : 0;
+ } catch {
+ return 0;
+ }
+ }
+
+ // Set timing offset for current track
+ setTimingOffset(trackId, offsetMs) {
+ try {
+ const key = `lyrics-offset-${trackId}`;
+ localStorage.setItem(key, offsetMs.toString());
+ } catch (e) {
+ console.warn('Failed to save lyrics timing offset:', e);
+ }
+ }
+
+ // Reset timing offset for current track
+ resetTimingOffset(trackId) {
+ this.setTimingOffset(trackId, 0);
+ }
+
+ // Get formatted offset display string
+ getOffsetDisplayString(offsetMs) {
+ const sign = offsetMs >= 0 ? '+' : '';
+ const seconds = Math.abs(offsetMs) / 1000;
+ return `${sign}${seconds.toFixed(1)}s`;
}
// Load Kuroshiro from CDN (npm package uses Node.js path which doesn't work in browser)
@@ -715,15 +749,38 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
});
}
+ // Load saved timing offset for this track
+ manager.timingOffset = manager.getTimingOffset(track.id);
+
const renderControls = (container) => {
const isRomajiMode = manager.getRomajiMode();
manager.isRomajiMode = isRomajiMode;
const isGeniusMode = manager.isGeniusMode;
+ const offsetDisplay = manager.getOffsetDisplayString(manager.timingOffset);
container.innerHTML = `
+
+
+
${offsetDisplay}
+
+
+