ziehen zum Drehen
.spin-viewer { position: relative; max-width: 600px; margin: 0 auto; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; } .spin-viewer.is-dragging { cursor: grabbing; touch-action: none; } .spin-viewer video { display: block; width: 100%; height: auto; pointer-events: none; } .spin-viewer::after { content: „“; position: absolute; inset: 0; z-index: 2; } .spin-hint { position: absolute; left: 50%; bottom: 0.8rem; z-index: 3; transform: translateX(-50%); font-size: 0.8rem; letter-spacing: 0.05em; color: #1a1a1a; opacity: 0.55; pointer-events: none; transition: opacity 0.4s ease; } .spin-hint.is-hidden { opacity: 0; } (function () { function init() { var wrap = document.querySelector(‚[data-spin]‘); if (!wrap || wrap.dataset.spinReady) return; wrap.dataset.spinReady = ‚1‘; var video = wrap.querySelector(‚video‘); var hint = wrap.querySelector(‚.spin-hint‘); var DIRECTION = 1; var SPEED = 1; var time = 0, dragging = false, lastX = 0; var rafId = null, pending = null; var unlocked = false, unlocking = false; video.removeAttribute(‚controls‘); video.removeAttribute(‚autoplay‘); video.removeAttribute(‚loop‘); // blockt jedes Abspielen, außer während der Entsperrung video.addEventListener(‚play‘, function () { if (!unlocking) video.pause(); }); function dur() { var d = video.duration; return (isFinite(d) && d > 0) ? d : 0; } // iOS lädt erst bei Nutzergeste – play() muss synchron im Handler stehen function unlock() { if (unlocked || unlocking) return; unlocking = true; var p = video.play(); var done = function () { video.pause(); unlocking = false; unlocked = true; if (!dur()) { video.addEventListener(‚loadedmetadata‘, function () { video.pause(); }, { once: true }); } }; if (p && p.then) p.then(done).catch(function () { unlocking = false; unlocked = true; }); else setTimeout(done, 60); } function wrapTime(t) { var d = dur(); if (!d) return 0; return ((t % d) + d) % d; } function flush() { rafId = null; if (pending === null) return; if (!video.seeking) { try { video.currentTime = pending; } catch (e) {} pending = null; } else { rafId = requestAnimationFrame(flush); } } function seek(t) { pending = t; if (!rafId) rafId = requestAnimationFrame(flush); } function getX(e) { return e.touches && e.touches.length ? e.touches[0].clientX : e.clientX; } function start(e) { unlock(); dragging = true; lastX = getX(e); time = video.currentTime || 0; wrap.classList.add(‚is-dragging‘); if (hint) hint.classList.add(‚is-hidden‘); if (e.type === ‚mousedown‘) e.preventDefault(); } function move(e) { if (!dragging) return; var d = dur(); if (!d) return; var x = getX(e), dx = x – lastX; lastX = x; var perPixel = (d / wrap.clientWidth) * SPEED; time = wrapTime(time + dx * perPixel * DIRECTION); seek(time); if (e.cancelable) e.preventDefault(); } function stop() { if (!dragging) return; dragging = false; video.pause(); wrap.classList.remove(‚is-dragging‘); } wrap.addEventListener(‚mousedown‘, start); window.addEventListener(‚mousemove‘, move); window.addEventListener(‚mouseup‘, stop); wrap.addEventListener(‚touchstart‘, start, { passive: true }); wrap.addEventListener(‚touchmove‘, move, { passive: false }); window.addEventListener(‚touchend‘, stop); window.addEventListener(‚touchcancel‘, stop); } if (document.readyState === ‚loading‘) { document.addEventListener(‚DOMContentLoaded‘, init); } else { init(); } })();

„Echsenphönix, 2026, Fundholz, Hanffaser, Acrylfarbe, 32 x 14 x 16 cm