/* Swipe/Keyboard navigation for Entry View - Respects localStorage 'swipeNavEnabled' (default ON) - Touch swipe on mobile, ArrowLeft/ArrowRight on keyboards - Edge protection and input-safe */ (function () { // Feature flag (client-side). Default ON. let swipeEnabled = true; try { swipeEnabled = (localStorage.getItem('swipeNavEnabled') !== 'false'); } catch (e) {} // Only attach if enabled if (!swipeEnabled) return; // Forms to submit for previous/next (your existing IDs) const prevForm = document.getElementById('navPrevForm'); const nextForm = document.getElementById('navNextForm'); // Create toast + hints lazily to keep HTML clean const toast = document.createElement('div'); toast.id = 'toast-swipe-error'; toast.style.cssText = [ 'position:fixed', 'left:50%', 'bottom:16px', 'transform:translateX(-50%)', 'padding:10px 14px', 'border-radius:10px', 'background:#111827', 'color:#fff', 'box-shadow:0 6px 20px rgba(0,0,0,.25)', 'opacity:0', 'pointer-events:none', 'transition:opacity .25s', 'z-index:1200', 'font-size:14px' ].join(';'); toast.textContent = "Can't navigate here."; document.body.appendChild(toast); function showToast() { toast.style.opacity = '1'; setTimeout(() => { toast.style.opacity = '0'; }, 1200); } // Only show swipe hints on touch devices, once per session const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); let hintL, hintR; if (isTouch) { try { if (!sessionStorage.getItem('swipeHintShown')) { sessionStorage.setItem('swipeHintShown', '1'); const base = 'position:fixed;width:34px;height:34px;display:flex;align-items:center;justify-content:center;' + 'background:rgba(0,0,0,.35);color:#fff;border-radius:999px;box-shadow:0 6px 20px rgba(0,0,0,.25);' + 'z-index:1100;opacity:0;transform:scale(.9);transition:opacity .25s,transform .25s;top:45%'; hintL = document.createElement('div'); hintL.style.cssText = base + ';left:10px;'; hintL.textContent = '‹'; document.body.appendChild(hintL); hintR = document.createElement('div'); hintR.style.cssText = base + ';right:10px;'; hintR.textContent = '›'; document.body.appendChild(hintR); setTimeout(() => { hintL.style.opacity = '1'; hintR.style.opacity = '1'; hintL.style.transform = 'scale(1)'; hintR.style.transform = 'scale(1)'; }, 300); setTimeout(() => { hintL.style.opacity = '0'; hintR.style.opacity = '0'; }, 2200); } } catch (e) {} } function navigate(dir) { try { if (dir < 0 && prevForm) { prevForm.submit(); return true; } if (dir > 0 && nextForm) { nextForm.submit(); return true; } } catch (e) {} showToast(); return false; } // Keyboard shortcuts document.addEventListener('keydown', function (e) { if (!swipeEnabled) return; const tag = (e.target && e.target.tagName) || ''; if (/INPUT|TEXTAREA|SELECT/.test(tag)) return; if (e.key === 'ArrowLeft') { e.preventDefault(); navigate(-1); } if (e.key === 'ArrowRight') { e.preventDefault(); navigate(1); } }); // Touch swipe (edge-protected, vertical tolerance) if (isTouch) { let startX = 0, startY = 0, tracking = false; const EDGE = 20, THRESH = 40; const area = document.querySelector('main.page') || document.body; function isInteractive(el) { return !!(el.closest && el.closest('input, textarea, select, button, a, [contenteditable], .no-swipe')); } area.addEventListener('touchstart', function (e) { const t = e.touches[0]; if (!t) return; if (isInteractive(e.target)) return; startX = t.clientX; startY = t.clientY; tracking = true; }, { passive: true }); area.addEventListener('touchmove', function (e) { if (!tracking) return; const t = e.touches[0]; if (!t) return; const dx = t.clientX - startX; const dy = t.clientY - startY; if (Math.abs(dx) > THRESH && Math.abs(dy) < 28) { // Edge-protection: avoid accidental from extreme edges if (startX < EDGE && dx > 0) { tracking = false; return; } if (startX > (window.innerWidth - EDGE) && dx < 0) { tracking = false; return; } e.preventDefault(); navigate(dx > 0 ? -1 : 1); tracking = false; } }, { passive: false }); area.addEventListener('touchend', function () { tracking = false; }, { passive: true }); } })();