fix(plex): seek tooltip tracks cursor under scale; tidy the tuning menu

- The seek-bar hover tooltip positioned itself with a px `left` derived
  from the scaled getBoundingClientRect, so inside the transform:scale
  player it got re-scaled and drifted right of the cursor (only 0 was
  right). Position it by fraction (% of the bar) instead — it now sits
  under the cursor at any point, like the volume tooltip already did.
- The tuning (gear) menu tabs wrapped to two lines at the +25% scale.
  Widen the menu, keep tab labels on one line (whitespace-nowrap), and
  shorten the labels: "Sync offset" → "Offsets", "Subtitle style" →
  "Subtitles" (all three languages).
This commit is contained in:
npeter83 2026-07-09 01:32:04 +02:00
parent 23070279a3
commit 2659991d84
4 changed files with 17 additions and 14 deletions

View file

@ -860,8 +860,11 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeMarker?.type, activeMarker?.start_s, prefs.autoSkipIntro, prefs.autoSkipCredits, prefs.autoSkipDelay, doSkip]);
// Seekbar hover: a timestamp tooltip that follows the cursor.
const [hover, setHover] = useState<{ x: number; t: number } | null>(null);
// Seekbar hover: a timestamp tooltip that follows the cursor. Store the position as a FRACTION
// (0..1), not pixels: the bar sits inside the transform:scale(1.25) subtree, so a px `left` would be
// re-scaled and drift right of the cursor (only 0 stayed correct). A % `left` is relative to the
// bar's own width and scales cleanly.
const [hover, setHover] = useState<{ frac: number; t: number } | null>(null);
// Seekbar scrubbing: the absolute time under the dragging finger. While non-null the head/thumb
// preview this position; the real seek fires once on release (avoids restarting the session on
// every out-of-buffer step of the drag). A plain click is a down+up at one spot → seeks there.
@ -1037,9 +1040,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
}}
onPointerMove={(e) => {
const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
const x = Math.max(0, Math.min(e.clientX - r.left, r.width));
const tt = (x / r.width) * duration;
setHover({ x, t: tt });
const frac = Math.max(0, Math.min((e.clientX - r.left) / r.width, 1));
const tt = frac * duration;
setHover({ frac, t: tt });
if (e.buttons & 1) setScrub(tt); // dragging with the primary button held
}}
onPointerUp={() => {
@ -1054,7 +1057,7 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
{hover && duration > 0 && (
<div
className="pointer-events-none absolute bottom-full mb-2 -translate-x-1/2 rounded bg-black/90 px-1.5 py-0.5 text-[11px] tabular-nums text-white"
style={{ left: `${hover.x}px` }}
style={{ left: `${hover.frac * 100}%` }}
>
{fmt(hover.t)}
</div>
@ -1236,14 +1239,14 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
ref={menuRef}
onWheel={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
className="glass-menu absolute bottom-full right-0 mb-2 w-72 rounded-xl p-2 text-sm text-white shadow-2xl"
className="glass-menu absolute bottom-full right-0 mb-2 w-80 rounded-xl p-2 text-sm text-white shadow-2xl"
>
<div className="mb-1.5 flex gap-1">
{settingsTabs.map((tab) => (
<button
key={tab.key}
onClick={() => setSettingsTab(tab.key)}
className={`flex-1 rounded px-2 py-1 text-[11px] ${
className={`flex-1 whitespace-nowrap rounded px-2 py-1 text-[11px] ${
activeTab === tab.key ? "bg-white/15 text-accent" : "text-white/70 hover:bg-white/10"
}`}
>

View file

@ -126,14 +126,14 @@
"fineSeek": "Feines Springen"
},
"settings": "Einstellungen",
"sync": "Synchronität",
"sync": "Offsets",
"subOffset": "Untertitel-Verzögerung",
"audioOffset": "Audio-Verzögerung",
"playback": "Wiedergabe",
"seekStep": "Sprungweite",
"fineSeek": "Feines Springen (Strg)",
"autoHide": "Steuerung ausblenden",
"subStyle": "Untertitel-Stil",
"subStyle": "Untertitel",
"subSize": "Schriftgröße",
"subPos": "Position",
"subColor": "Textfarbe",

View file

@ -126,14 +126,14 @@
"fineSeek": "Fine seek"
},
"settings": "Settings",
"sync": "Sync offset",
"sync": "Offsets",
"subOffset": "Subtitle delay",
"audioOffset": "Audio delay",
"playback": "Playback",
"seekStep": "Seek step",
"fineSeek": "Fine seek (Ctrl)",
"autoHide": "Auto-hide controls",
"subStyle": "Subtitle style",
"subStyle": "Subtitles",
"subSize": "Text size",
"subPos": "Position",
"subColor": "Text color",

View file

@ -126,14 +126,14 @@
"fineSeek": "Finom léptetés"
},
"settings": "Beállítások",
"sync": "Szinkron-eltolás",
"sync": "Eltolások",
"subOffset": "Felirat-eltolás",
"audioOffset": "Hang-eltolás",
"playback": "Lejátszás",
"seekStep": "Léptetés",
"fineSeek": "Finom léptetés (Ctrl)",
"autoHide": "Vezérlők elrejtése",
"subStyle": "Felirat stílusa",
"subStyle": "Feliratok",
"subSize": "Szövegméret",
"subPos": "Pozíció",
"subColor": "Szövegszín",