Spaces:
Running
Running
Download script.js from Faheemkabeer/Gesture-Drop: direct link, hf CLI and curl.
- Browser
- Download file 5.23 kB
-
https://huggingface.co/spaces/Faheemkabeer/Gesture-Drop/resolve/main/script.js
- Command line
-
hf download hf://spaces/Faheemkabeer/Gesture-Drop/script.js
-
curl -L -o script.js https://huggingface.co/spaces/Faheemkabeer/Gesture-Drop/resolve/main/script.js
5.23 kB
| const $ = id => document.getElementById(id); | |
| const limit = 1200; | |
| let note = "", stream = null, running = false, palmFrames = 0; | |
| function encode(text) { | |
| let binary = ""; | |
| for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte); | |
| return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); | |
| } | |
| function decode(encoded) { | |
| if (!/^[A-Za-z0-9_-]+$/.test(encoded)) throw Error("Invalid link"); | |
| const binary = atob(encoded.replace(/-/g, "+").replace(/_/g, "/")); | |
| const text = new TextDecoder("utf-8", {fatal: true}).decode(Uint8Array.from(binary, c => c.charCodeAt(0))); | |
| if (!text || text.length > limit) throw Error("Invalid note"); | |
| return text; | |
| } | |
| function showQr(url) { | |
| const box = $("qr-code"); | |
| box.replaceChildren(); | |
| $("qr-wrap").classList.add("hidden"); | |
| $("qr-status").textContent = ""; | |
| if (url.length > 450) { | |
| $("qr-status").textContent = "This note is too long for an easy-to-scan QR code. Use Copy instead."; | |
| return; | |
| } | |
| if (!window.qrcode) { | |
| $("qr-status").textContent = "QR code could not load. Use Copy instead."; | |
| return; | |
| } | |
| try { | |
| const qr = qrcode(0, "M"); | |
| qr.addData(url); | |
| qr.make(); | |
| box.innerHTML = qr.createSvgTag({cellSize: 4, margin: 16, scalable: true}); | |
| $("qr-wrap").classList.remove("hidden"); | |
| } catch { | |
| $("qr-status").textContent = "This note could not fit in a QR code. Use Copy instead."; | |
| } | |
| } | |
| function load() { | |
| const match = /^#note=([A-Za-z0-9_-]+)$/.exec(location.hash); | |
| try { note = match ? decode(match[1]) : ""; } | |
| catch { note = ""; } | |
| $("note-panel").classList.add("hidden"); | |
| $("status").textContent = note ? "Note loaded. Show an open palm or press Reveal note." : location.hash ? "This share link is invalid." : "Open a share link to load a note."; | |
| } | |
| function reveal() { | |
| if (!note) { $("status").textContent = "Open a share link first."; return; } | |
| $("received").textContent = note; | |
| $("note-panel").classList.remove("hidden"); | |
| $("status").textContent = "Note revealed."; | |
| } | |
| function distance(a,b) { return Math.hypot(a[0]-b[0],a[1]-b[1]); } | |
| function openPalm(p) { | |
| const wrist = p[0]; | |
| const extended = [[8,6],[12,10],[16,14],[20,18]].filter(([tip,mid]) => distance(p[tip],wrist)>distance(p[mid],wrist)*1.22); | |
| return extended.length===4 && distance(p[4],p[17])>distance(p[3],p[17])*1.15; | |
| } | |
| async function detect(model) { | |
| running = true; | |
| while (running && stream) { | |
| try { | |
| const hands = await model.estimateHands($("camera")); | |
| const palm = hands.length>0 && openPalm(hands[0].landmarks); | |
| palmFrames = palm ? palmFrames+1 : 0; | |
| $("gesture").textContent = palm ? "Open palm detected" : "Show an open palm"; | |
| if (palmFrames===5) reveal(); | |
| } catch(error) { $("status").textContent = `Gesture detection stopped: ${error.message}`; break; } | |
| await new Promise(resolve => setTimeout(resolve,120)); | |
| } | |
| running = false; | |
| } | |
| function stop() { | |
| running = false; palmFrames = 0; | |
| stream?.getTracks().forEach(track => track.stop()); stream = null; | |
| $("camera").srcObject = null; $("camera").style.display = "none"; | |
| $("placeholder").style.display = "block"; $("gesture").textContent = "Camera off"; | |
| $("start").disabled = false; $("stop").disabled = true; | |
| } | |
| async function start() { | |
| if (stream) return; | |
| try { | |
| if (!navigator.mediaDevices?.getUserMedia) throw Error("Camera access is unavailable. Use Reveal note."); | |
| stream = await navigator.mediaDevices.getUserMedia({video:{facingMode:"user"},audio:false}); | |
| $("camera").srcObject = stream; $("camera").style.display = "block"; $("placeholder").style.display = "none"; | |
| $("start").disabled = true; $("stop").disabled = false; | |
| await $("camera").play(); | |
| $("status").textContent = "Loading hand model…"; | |
| if (!window.handpose) throw Error("Hand model could not load. Use Reveal note."); | |
| const model = await handpose.load(); | |
| if (!stream) return; | |
| $("status").textContent = "Camera ready. Hold an open palm in view."; | |
| detect(model); | |
| } catch(error) { stop(); $("status").textContent = `Camera unavailable: ${error.message}`; } | |
| } | |
| $("message").addEventListener("input", () => { $("count").textContent = $("message").value.length; }); | |
| $("create").addEventListener("click", () => { | |
| const text = $("message").value.trim(); | |
| if (!text) { $("message").focus(); return; } | |
| $("share-link").value = `${location.origin}${location.pathname}#note=${encode(text)}`; | |
| $("link-panel").classList.remove("hidden"); | |
| showQr($("share-link").value); | |
| }); | |
| $("copy-link").addEventListener("click", async () => { | |
| try { await navigator.clipboard.writeText($("share-link").value); $("copy-link").textContent="Copied"; } | |
| catch { $("share-link").select(); $("copy-link").textContent="Select and copy"; } | |
| }); | |
| $("copy-note").addEventListener("click", async () => { | |
| try { await navigator.clipboard.writeText(note); $("copy-note").textContent="Copied"; } | |
| catch { $("status").textContent="Select the note text to copy it."; } | |
| }); | |
| $("reveal").addEventListener("click",reveal); | |
| $("start").addEventListener("click",start); | |
| $("stop").addEventListener("click",stop); | |
| window.addEventListener("hashchange",load); | |
| window.addEventListener("pagehide",stop); | |
| load(); | |