add second speed up button for pure convenience

This commit is contained in:
2026-05-15 03:08:04 +02:00
parent f72e4a75e1
commit 3015fd18aa

View File

@@ -1336,7 +1336,11 @@
}</div>` }</div>`
: ''; : '';
meta.innerHTML = ` meta.innerHTML = `
<div class="scroll-meta-inner"> <div class="scroll-meta-inner" style="position:relative;">
<button class="js-speed-hold-btn js-meta-speed-btn" aria-hidden="true" tabindex="-1"
style="position:absolute; bottom:100%; left:-16px; width:100px; height:100vh;
background:none; border:none; padding:0; cursor:pointer; opacity:0;
pointer-events:all;"></button>
${badgesHtml} ${badgesHtml}
<div class="scroll-meta-top"> <div class="scroll-meta-top">
<a href="/user/${esc(item.username)}" class="scroll-user-link"> <a href="/user/${esc(item.username)}" class="scroll-user-link">
@@ -1431,28 +1435,29 @@
slide.appendChild(pBar); slide.appendChild(pBar);
setupTapOverlay(slide); setupTapOverlay(slide);
// ── Hold-to-2× speed logic (shared by multiple triggers) ──
let speedTimer = null;
let speedActive = false;
let speedEndedAt = 0;
// ── Invisible speed-hold button (sits above the heart in the actions column) ── const endSpeed = () => {
const speedHoldBtn = actions.querySelector('.js-speed-hold-btn'); clearTimeout(speedTimer);
if (speedHoldBtn) { speedTimer = null;
let speedTimer = null; document.removeEventListener('pointerup', endSpeed);
let speedActive = false; document.removeEventListener('pointercancel',endSpeed);
if (speedActive) {
speedActive = false;
speedEndedAt = Date.now();
const media = slide.querySelector('video') || slide.querySelector('audio');
if (media) media.playbackRate = 1;
const ind = document.getElementById('speed-indicator');
if (ind) ind.classList.remove('show');
}
};
const endSpeed = () => { const wireSpeedHold = (el) => {
clearTimeout(speedTimer); if (!el) return;
speedTimer = null; el.addEventListener('pointerdown', () => {
document.removeEventListener('pointerup', endSpeed);
document.removeEventListener('pointercancel',endSpeed);
if (speedActive) {
speedActive = false;
const media = slide.querySelector('video') || slide.querySelector('audio');
if (media) media.playbackRate = 1;
const ind = document.getElementById('speed-indicator');
if (ind) ind.classList.remove('show');
}
};
speedHoldBtn.addEventListener('pointerdown', () => {
document.addEventListener('pointerup', endSpeed, { once: true }); document.addEventListener('pointerup', endSpeed, { once: true });
document.addEventListener('pointercancel',endSpeed, { once: true }); document.addEventListener('pointercancel',endSpeed, { once: true });
speedTimer = setTimeout(() => { speedTimer = setTimeout(() => {
@@ -1465,7 +1470,11 @@
} }
}, 150); }, 150);
}, { passive: true }); }, { passive: true });
} };
wireSpeedHold(actions.querySelector('.js-speed-hold-btn'));
wireSpeedHold(slide.querySelector('.js-meta-speed-btn'));
const favBtn = actions.querySelector('.js-fav-btn'); const favBtn = actions.querySelector('.js-fav-btn');
if (favBtn) { if (favBtn) {
@@ -1504,13 +1513,14 @@
if (rehostBtn) rehostBtn.addEventListener('click', () => rehostItem(item, rehostBtn)); if (rehostBtn) rehostBtn.addEventListener('click', () => rehostItem(item, rehostBtn));
// Rating cycle — always wire the click; server enforces mod auth via 403 // Rating cycle — always wire the click; server enforces mod auth via 403
const ratingBadge = slide.querySelector('.scroll-rating[data-item-id]'); const rBadge = slide.querySelector('.scroll-rating[data-item-id]');
if (ratingBadge) { if (rBadge) {
if (window.scrollerIsMod || item.local_id) ratingBadge.classList.add('can-cycle'); if (window.scrollerIsMod || item.local_id) rBadge.classList.add('can-cycle');
ratingBadge.addEventListener('click', async e => { rBadge.addEventListener('click', async e => {
if (Date.now() - speedEndedAt < 200) return; // ignore click if we just ended a speed-hold
e.stopPropagation(); e.stopPropagation();
const slideEl = ratingBadge.closest('.scroll-slide'); const slideEl = rBadge.closest('.scroll-slide');
const id = slideEl?.dataset.localId || ratingBadge.dataset.itemId; const id = slideEl?.dataset.localId || rBadge.dataset.itemId;
if (!id || isNaN(id)) { showShareToast('Rehost first to change rating'); return; } if (!id || isNaN(id)) { showShareToast('Rehost first to change rating'); return; }
try { try {
const resp = await fetch(`/api/v2/tags/${id}/cycle-rating`, { const resp = await fetch(`/api/v2/tags/${id}/cycle-rating`, {
@@ -1519,9 +1529,9 @@
}); });
const data = await resp.json(); const data = await resp.json();
if (data.success) { if (data.success) {
ratingBadge.className = `scroll-rating ${data.rating_class}${window.scrollerIsMod ? ' can-cycle' : ''}`; rBadge.className = `scroll-rating ${data.rating_class}${window.scrollerIsMod ? ' can-cycle' : ''}`;
ratingBadge.textContent = data.rating_label; rBadge.textContent = data.rating_label;
ratingBadge.dataset.rating = data.rating_class; rBadge.dataset.rating = data.rating_class;
} }
} catch {} } catch {}
}); });