From 433fd93a3695c4155983fa100c373fa154a5997f Mon Sep 17 00:00:00 2001 From: Ferdinand Urban Date: Thu, 23 Apr 2026 12:44:15 +0000 Subject: [PATCH] feat: touch swipe for dating mode with live drag feedback - swipe left/right to reject/keep, swipe up for favorite - card tilts and overlays fade in proportionally during drag - snap-back animation when swipe distance below threshold - touchcancel handled identically to touchend Co-Authored-By: Claude Sonnet 4.6 --- index.html | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 7b17db6..3ad8f21 100644 --- a/index.html +++ b/index.html @@ -131,7 +131,7 @@ .drop-zone { border: 2px dashed var(--border-mid); border-radius: 12px; padding: 2rem 1rem; text-align: center; transition: border-color 0.15s, background 0.15s; margin-bottom: 1rem; cursor: default; } .drop-zone.drag-over { border-color: var(--blue); background: var(--blue-dim); } .drop-zone.done { border-color: var(--green); background: var(--green-dim); } - .drop-icon { font-size: 2.2rem; margin-bottom: 0.5rem; } + .drop-icon { margin-bottom: 0.5rem; line-height: 1; } .drop-text { font-size: 1rem; font-weight: 500; color: var(--text); margin-bottom: 0.25rem; } .drop-sub { font-size: 0.8rem; color: var(--faint); } .upload-status { margin-bottom: 1rem; } @@ -163,7 +163,8 @@ .tinder-counter { font-size: 0.92rem; color: var(--muted); font-weight: 500; } .tinder-done-btn { padding: 0.3rem 0.8rem; border-radius: 6px; border: 1.5px solid var(--border); background: #fff; color: var(--muted); cursor: pointer; font-size: 0.82rem; transition: border-color 0.15s, color 0.15s; } .tinder-done-btn:hover { border-color: var(--blue); color: var(--blue); } - .tinder-card { position: relative; border-radius: 16px; overflow: hidden; background: #fff; box-shadow: 0 4px 24px rgba(0,0,0,0.12); transform-origin: bottom center; } + .tinder-card { position: relative; border-radius: 16px; overflow: hidden; background: #fff; box-shadow: 0 4px 24px rgba(0,0,0,0.12); transform-origin: bottom center; touch-action: none; user-select: none; } + .tinder-card.snapping { transition: transform 0.25s cubic-bezier(0.2,0,0.4,1); } .tinder-card img { width: 100%; max-height: 62vh; object-fit: contain; background: #F8F9FB; display: block; } .tinder-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 3rem; font-weight: 900; letter-spacing: 0.08em; opacity: 0; pointer-events: none; transition: opacity 0.12s; } .tinder-overlay-ok { background: rgba(34,197,94,0.25); color: var(--green); } @@ -277,7 +278,17 @@
-
📁
+
+ + + + + + + + + +

Drag & Drop

oder

@@ -1340,6 +1351,68 @@ el("tinder-btn-nok").addEventListener("click", () => tinderSwipe("left")); el("tinder-btn-ok").addEventListener("click", () => tinderSwipe("right")); + // Touch swipe + const SWIPE_H = 80; // px horizontal threshold + const SWIPE_UP = 75; // px upward threshold (favorite) + let _tx0 = 0, _ty0 = 0, _touching = false; + + function _resetOverlays() { + el("tinder-overlay-ok").style.opacity = "0"; + el("tinder-overlay-nok").style.opacity = "0"; + el("tinder-overlay-fav").style.opacity = "0"; + } + + el("tinder-card").addEventListener("touchstart", e => { + if (tinderSwiping) return; + const t = e.touches[0]; + _tx0 = t.clientX; _ty0 = t.clientY; + _touching = true; + el("tinder-card").classList.remove("snapping"); + }, { passive: true }); + + el("tinder-card").addEventListener("touchmove", e => { + if (!_touching || tinderSwiping) return; + e.preventDefault(); + const t = e.touches[0]; + const dx = t.clientX - _tx0; + const dy = t.clientY - _ty0; + const isUp = dy < -SWIPE_UP * 0.5 && Math.abs(dx) < 55; + const rot = isUp ? 0 : (dx / el("tinder-card").offsetWidth) * 18; + + el("tinder-card").style.transform = + `translateX(${isUp ? 0 : dx}px) translateY(${isUp ? dy * 0.6 : 0}px) rotate(${rot}deg)`; + + el("tinder-overlay-ok").style.opacity = (!isUp && dx > 30) ? Math.min(1, (dx - 30) / 70) + "" : "0"; + el("tinder-overlay-nok").style.opacity = (!isUp && dx < -30) ? Math.min(1, (-dx - 30) / 70) + "" : "0"; + el("tinder-overlay-fav").style.opacity = isUp ? Math.min(1, (-dy - 37) / 50) + "" : "0"; + }, { passive: false }); + + function _onTouchEnd(e) { + if (!_touching) return; + _touching = false; + const t = e.changedTouches[0]; + const dx = t.clientX - _tx0; + const dy = t.clientY - _ty0; + + el("tinder-card").style.transform = ""; + _resetOverlays(); + + if (dy < -SWIPE_UP && Math.abs(dx) < 55) { + tinderFavorite(); + } else if (dx > SWIPE_H) { + tinderSwipe("right"); + } else if (dx < -SWIPE_H) { + tinderSwipe("left"); + } else { + const card = el("tinder-card"); + card.classList.add("snapping"); + card.addEventListener("transitionend", () => card.classList.remove("snapping"), { once: true }); + } + } + + el("tinder-card").addEventListener("touchend", _onTouchEnd); + el("tinder-card").addEventListener("touchcancel", _onTouchEnd); + el("tinder-fav-star").addEventListener("click", () => { const photo = tinderQueue[tinderIndex]; if (!photo) return;