From 04359ea03412f8b9f760d6e69c801f17744e5f2b Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Tue, 28 Jul 2026 15:36:31 +0700 Subject: [PATCH] feat: replace the FAB with a 7x44 edge tab The circular button covered artwork exactly while reading. The tab keeps the drag-and-snap behaviour and the mangabm:fabpos key; only its box changes. The dwell countdown becomes a bottom-up fill of the tab, since a sliver has no room for a ring. Also fixes a latent bug the new shape exposes: placeFab and makeDraggable used offsetWidth for the vertical clamp too. Invisible on a 52px circle, off by 37px on a 7x44 tab. Co-Authored-By: Claude Opus 5 --- userscript/manga-bookmark.user.js | 83 ++++++++++++++----------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/userscript/manga-bookmark.user.js b/userscript/manga-bookmark.user.js index 24b9866..b518396 100644 --- a/userscript/manga-bookmark.user.js +++ b/userscript/manga-bookmark.user.js @@ -897,7 +897,8 @@ // ============================================================ const FAB_KEY = "mangabm:fabpos"; - const FAB_MARGIN = 12; + const FAB_MARGIN = 12; // vertical breathing room at the top and bottom + const FAB_EDGE = 0; // horizontal: an edge tab sits flush against the side function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); @@ -906,11 +907,12 @@ function applyFabPos(fab, side, y) { fab.style.top = y + "px"; fab.style.bottom = "auto"; + fab.classList.toggle("left", side === "left"); if (side === "left") { - fab.style.left = FAB_MARGIN + "px"; + fab.style.left = FAB_EDGE + "px"; fab.style.right = "auto"; } else { - fab.style.right = FAB_MARGIN + "px"; + fab.style.right = FAB_EDGE + "px"; fab.style.left = "auto"; } } @@ -932,9 +934,9 @@ } function placeFab(fab) { - const s = fab.offsetWidth || 52; + const h = fab.offsetHeight || 44; const p = loadFabPos(); - const maxY = window.innerHeight - s - FAB_MARGIN; + const maxY = window.innerHeight - h - FAB_MARGIN; const side = p && p.side === "left" ? "left" : "right"; const y = p ? clamp(p.yRatio * window.innerHeight, FAB_MARGIN, maxY) : maxY - 8; applyFabPos(fab, side, y); @@ -958,10 +960,10 @@ if (!moved && (Math.abs(e.clientX - sx) > 6 || Math.abs(e.clientY - sy) > 6)) moved = true; if (!moved) return; e.preventDefault(); - const s = fab.offsetWidth; - fab.style.left = clamp(e.clientX - offX, 0, window.innerWidth - s) + "px"; + const w = fab.offsetWidth, h = fab.offsetHeight; + fab.style.left = clamp(e.clientX - offX, 0, window.innerWidth - w) + "px"; fab.style.right = "auto"; - fab.style.top = clamp(e.clientY - offY, 0, window.innerHeight - s) + "px"; + fab.style.top = clamp(e.clientY - offY, 0, window.innerHeight - h) + "px"; fab.style.bottom = "auto"; }); fab.addEventListener("pointerup", (e) => { @@ -970,10 +972,10 @@ fab.style.transition = ""; if (!moved) return; // tap โ€” let the click handler toggle the panel fab.dataset.dragged = "1"; // suppress the click that trails this drag - const s = fab.offsetWidth; + const w = fab.offsetWidth, h = fab.offsetHeight; const r = fab.getBoundingClientRect(); - const side = r.left + s / 2 < window.innerWidth / 2 ? "left" : "right"; - const y = clamp(r.top, FAB_MARGIN, window.innerHeight - s - FAB_MARGIN); + const side = r.left + w / 2 < window.innerWidth / 2 ? "left" : "right"; + const y = clamp(r.top, FAB_MARGIN, window.innerHeight - h - FAB_MARGIN); fab.style.transition = "left .2s ease, right .2s ease, top .2s ease"; applyFabPos(fab, side, y); saveFabPos(side, y / window.innerHeight); @@ -981,31 +983,26 @@ } // ============================================================ - // Countdown ring around the FAB (dwell-timer progress) + // Dwell-timer progress: the tab fills bottom-up. A 7px sliver has no room + // for the ring this replaced. // ============================================================ - const RING_C = 144.5; // circumference: 2ฯ€ยท23 - function startRing(ms) { if (!root) return; - const ring = root.getElementById("ring"); - const prog = ring.firstElementChild; - ring.classList.add("active"); - prog.style.transition = "none"; - prog.style.strokeDashoffset = RING_C; // empty - prog.getBoundingClientRect(); // reflow so the next assignment animates - prog.style.transition = "stroke-dashoffset " + ms + "ms linear"; - prog.style.strokeDashoffset = "0"; // fills over ms + const fill = root.getElementById("fill"); + fill.style.transition = "none"; + fill.style.height = "0"; + fill.getBoundingClientRect(); // reflow so the next assignment animates + fill.style.transition = "height " + ms + "ms linear"; + fill.style.height = "100%"; } function stopRing() { if (!root) return; - const ring = root.getElementById("ring"); - if (!ring) return; - const prog = ring.firstElementChild; - ring.classList.remove("active"); - prog.style.transition = "none"; - prog.style.strokeDashoffset = RING_C; + const fill = root.getElementById("fill"); + if (!fill) return; + fill.style.transition = "none"; + fill.style.height = "0"; } function togglePanel() { @@ -1227,10 +1224,7 @@ // ============================================================ const TEMPLATE = ` - +