- Spam & Bot Protection: Stops bots with math (cognitive) and movement (physical) check.
- User Experience: Easy for humans and hard for bots.
- Two-Step Check Logic: Math question and a draggable element to confirm real activity.
- Hidden Bot-Detection Logic: Analyzes mouse movement behaviours.
- Accessible & Mobile-Ready: Works on desktop and mobile devices (touch & mouse support).
- Visual Feedback: Instantly shows errors, success, or next step.
- Easy Integration: Just drop the script into any HTML form.
- Fully Client-Side – True Real Privacy: No external library, service, server or tracking.
Is this the perfect solution for blocking bots? Of course not. When it comes to cyber security nothing is perfect because of human factor. For now this will block current generic programmed bots and AI bots (Unless targeted specifically)
I am watching the AI Agents very closely so in time I have more ideas to develop this further.

<script> const formSelectorString = '#your-form'; // Replace with your form selector function initializeAdvancedCaptcha(targetForm) { const snn_captcha_captchaHTML = ` <div class="snn_captcha_container" id="snn_captcha_advancedCaptchaContainer"> <div id="snn_captcha_math-captcha-step" class="snn_captcha_math-step-container"> <div class="snn_captcha_math-input-group"> <label for="snn_captcha_math-answer" id="snn_captcha_math-question-label" class="snn_captcha_math-question-label"></label> <input type="number" id="snn_captcha_math-answer" class="snn_captcha_math-answer-input" placeholder="Answer"> </div> </div> <div id="snn_captcha_mouse-captcha-step" class="snn_captcha_mouse-step-container snn_captcha_is-hidden"> <label class="snn_captcha_mouse-captcha-instruction">Drag:</label> <div id="snn_captcha_mouse-captcha-area"> <div id="snn_captcha_draggable-item"></div> <div id="snn_captcha_drop-target">Target</div> </div> </div> </div> `; const snn_captcha_submitButton = targetForm.querySelector('button[type="submit"], input[type="submit"]'); if (!snn_captcha_submitButton) { console.error("Advanced Captcha: No submit button found in the form:", targetForm); return; } const snn_captcha_captchaWrapper = document.createElement('div'); snn_captcha_captchaWrapper.innerHTML = snn_captcha_captchaHTML; const snn_captcha_captchaContainerElementToInsert = snn_captcha_captchaWrapper.firstElementChild; if (!snn_captcha_captchaContainerElementToInsert) { console.error("Advanced Captcha: Captcha HTML content is empty or invalid."); return; } const snn_captcha_insertedCaptchaNode = snn_captcha_submitButton.parentNode.insertBefore(snn_captcha_captchaContainerElementToInsert, snn_captcha_submitButton); if (!snn_captcha_insertedCaptchaNode || !document.body.contains(snn_captcha_insertedCaptchaNode)) { console.error("Advanced Captcha: CRITICAL - The main captcha container was not successfully inserted or found in the DOM."); return; } const snn_captcha_mathCaptchaStep = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_math-captcha-step'); const snn_captcha_mathQuestionLabel = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_math-question-label'); const snn_captcha_mathAnswerInput = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_math-answer'); const snn_captcha_mouseCaptchaStep = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_mouse-captcha-step'); const snn_captcha_mouseCaptchaArea = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_mouse-captcha-area'); const snn_captcha_draggableItem = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_draggable-item'); const snn_captcha_dropTarget = snn_captcha_insertedCaptchaNode.querySelector('#snn_captcha_drop-target'); const snn_captcha_loginBtn = snn_captcha_submitButton; const formMessageDiv = document.getElementById('form-message'); // Assuming you have a div with id="form-message" for feedback let snn_captcha_num1, snn_captcha_num2, snn_captcha_expectedMathAnswer; let snn_captcha_mathCaptchaVerified = false; let snn_captcha_mouseCaptchaVerified = false; let snn_captcha_isDragging = false; let snn_captcha_offsetX; const snn_captcha_initialDraggableLeft = 20; // px let snn_captcha_mouseMoveEvents = []; // Stores {timestamp, pos} for speed analysis let snn_captcha_mouseEventCount = 0; // Raw count of mousemove/touchmove events function snn_captcha_resetInputStyles(snn_captcha_inputElement) { snn_captcha_inputElement.classList.remove('snn_captcha_input-error-state', 'snn_captcha_input-success-state'); } function snn_captcha_generateMathProblem() { if (!snn_captcha_mathQuestionLabel || !snn_captcha_mathAnswerInput) { console.error("Math captcha elements not found for snn_captcha_generateMathProblem."); if(snn_captcha_mathAnswerInput) snn_captcha_mathAnswerInput.disabled = true; return; } snn_captcha_num1 = Math.floor(Math.random() * 7) + 1; snn_captcha_num2 = Math.floor(Math.random() * 7) + 1; snn_captcha_expectedMathAnswer = snn_captcha_num1 + snn_captcha_num2; snn_captcha_mathQuestionLabel.textContent = `${snn_captcha_num1} + ${snn_captcha_num2} = `; snn_captcha_mathAnswerInput.value = ''; snn_captcha_mathAnswerInput.disabled = false; snn_captcha_resetInputStyles(snn_captcha_mathAnswerInput); snn_captcha_mathCaptchaVerified = false; } if (snn_captcha_mathAnswerInput) { snn_captcha_mathAnswerInput.addEventListener('input', () => { if (snn_captcha_mathCaptchaVerified) return; const snn_captcha_userAnswerString = snn_captcha_mathAnswerInput.value; const snn_captcha_userAnswer = snn_captcha_userAnswerString ? parseInt(snn_captcha_userAnswerString) : NaN; snn_captcha_resetInputStyles(snn_captcha_mathAnswerInput); if (snn_captcha_userAnswer === snn_captcha_expectedMathAnswer && snn_captcha_userAnswerString.length === snn_captcha_expectedMathAnswer.toString().length) { snn_captcha_mathCaptchaVerified = true; snn_captcha_mathAnswerInput.disabled = true; snn_captcha_mathAnswerInput.classList.add('snn_captcha_input-success-state'); setTimeout(() => { if (snn_captcha_mathCaptchaStep) snn_captcha_mathCaptchaStep.classList.add('snn_captcha_is-hidden'); if (snn_captcha_mouseCaptchaStep) snn_captcha_mouseCaptchaStep.classList.remove('snn_captcha_is-hidden'); snn_captcha_checkAllCaptchasVerified(); }, 300); } else { if (snn_captcha_userAnswerString.length >= snn_captcha_expectedMathAnswer.toString().length && snn_captcha_userAnswer !== snn_captcha_expectedMathAnswer) { snn_captcha_mathAnswerInput.classList.add('snn_captcha_input-error-state'); } } }); snn_captcha_mathAnswerInput.addEventListener('blur', () => { if (!snn_captcha_mathCaptchaVerified && snn_captcha_mathAnswerInput.value !== "") { const snn_captcha_userAnswer = parseInt(snn_captcha_mathAnswerInput.value); snn_captcha_resetInputStyles(snn_captcha_mathAnswerInput); if (snn_captcha_userAnswer !== snn_captcha_expectedMathAnswer || snn_captcha_mathAnswerInput.value.length !== snn_captcha_expectedMathAnswer.toString().length) { snn_captcha_mathAnswerInput.classList.add('snn_captcha_input-error-state'); } } }); } function snn_captcha_resetDraggableItemPosition() { if (!snn_captcha_mouseCaptchaVerified && snn_captcha_draggableItem) { snn_captcha_draggableItem.style.left = `${snn_captcha_initialDraggableLeft}px`; snn_captcha_draggableItem.style.top = `50%`; snn_captcha_draggableItem.style.transform = 'translateY(-50%)'; snn_captcha_draggableItem.textContent = 'Drag'; snn_captcha_draggableItem.style.cursor = 'grab'; if(snn_captcha_mouseCaptchaArea) snn_captcha_mouseCaptchaArea.classList.remove('snn_captcha_verified-area'); } if (snn_captcha_dropTarget) { snn_captcha_dropTarget.style.borderColor = '#007bff'; snn_captcha_dropTarget.style.borderStyle = 'dashed'; snn_captcha_dropTarget.style.backgroundColor = 'transparent'; } } function snn_captcha_handleDragStart(event) { if (snn_captcha_mouseCaptchaVerified || !snn_captcha_draggableItem || !snn_captcha_mouseCaptchaArea) return; if (event.type === 'touchstart') event.preventDefault(); snn_captcha_isDragging = true; snn_captcha_draggableItem.classList.add('snn_captcha_is-grabbing'); snn_captcha_mouseEventCount = 0; snn_captcha_mouseMoveEvents = []; const snn_captcha_draggableRect = snn_captcha_draggableItem.getBoundingClientRect(); const snn_captcha_clientX = event.touches ? event.touches[0].clientX : event.clientX; snn_captcha_offsetX = snn_captcha_clientX - snn_captcha_draggableRect.left; let initialPos = parseFloat(snn_captcha_draggableItem.style.left); if (isNaN(initialPos)) { initialPos = snn_captcha_initialDraggableLeft; } snn_captcha_mouseMoveEvents.push({ timestamp: Date.now(), pos: initialPos }); document.addEventListener('mousemove', snn_captcha_handleDragMove); document.addEventListener('mouseup', snn_captcha_handleDragEnd); document.addEventListener('touchmove', snn_captcha_handleDragMove, { passive: false }); document.addEventListener('touchend', snn_captcha_handleDragEnd); } function snn_captcha_handleDragMove(event) { if (!snn_captcha_isDragging || snn_captcha_mouseCaptchaVerified || !snn_captcha_draggableItem || !snn_captcha_mouseCaptchaArea) return; if (event.type === 'touchmove') event.preventDefault(); snn_captcha_mouseEventCount++; // Increment raw event counter const snn_captcha_clientX = event.touches ? event.touches[0].clientX : event.clientX; const snn_captcha_areaRect = snn_captcha_mouseCaptchaArea.getBoundingClientRect(); let snn_captcha_newX = snn_captcha_clientX - snn_captcha_areaRect.left - snn_captcha_offsetX; if (snn_captcha_newX < 0) snn_captcha_newX = 0; if (snn_captcha_newX + snn_captcha_draggableItem.offsetWidth > snn_captcha_areaRect.width) { snn_captcha_newX = snn_captcha_areaRect.width - snn_captcha_draggableItem.offsetWidth; } snn_captcha_draggableItem.style.left = `${snn_captcha_newX}px`; if (snn_captcha_mouseMoveEvents.length === 0 || Math.abs(snn_captcha_mouseMoveEvents[snn_captcha_mouseMoveEvents.length - 1].pos - snn_captcha_newX) > 0.1) { // Threshold for "meaningful change" snn_captcha_mouseMoveEvents.push({ timestamp: Date.now(), pos: snn_captcha_newX }); } } function snn_captcha_handleDragEnd() { if (!snn_captcha_isDragging || !snn_captcha_draggableItem || !snn_captcha_dropTarget || !snn_captcha_mouseCaptchaArea) return; snn_captcha_isDragging = false; snn_captcha_draggableItem.classList.remove('snn_captcha_is-grabbing'); document.removeEventListener('mousemove', snn_captcha_handleDragMove); document.removeEventListener('mouseup', snn_captcha_handleDragEnd); document.removeEventListener('touchmove', snn_captcha_handleDragMove); document.removeEventListener('touchend', snn_captcha_handleDragEnd); if (snn_captcha_mouseCaptchaVerified) return; // Already verified let snn_captcha_isBotMovement = false; const MIN_RAW_MOVE_EVENTS = 8; // Threshold for raw mousemove/touchmove events if (snn_captcha_mouseEventCount > 0 && snn_captcha_mouseEventCount < MIN_RAW_MOVE_EVENTS) { snn_captcha_isBotMovement = true; console.log(`Advanced Captcha: Low raw mouse/touch event count detected: ${snn_captcha_mouseEventCount}`); } if (!snn_captcha_isBotMovement && snn_captcha_mouseMoveEvents.length >= 3) { const speeds = []; for (let i = 0; i < snn_captcha_mouseMoveEvents.length - 1; i++) { const p1 = snn_captcha_mouseMoveEvents[i]; const p2 = snn_captcha_mouseMoveEvents[i+1]; const timeDeltaMs = p2.timestamp - p1.timestamp; if (timeDeltaMs < 1) continue; // Avoid division by zero or extreme speeds if events are too close const distanceDeltaPx = Math.abs(p2.pos - p1.pos); speeds.push(distanceDeltaPx / (timeDeltaMs / 1000)); // pixels per second } if (speeds.length >= 2) { // Need at least 2 speed values to calculate standard deviation const meanSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length; const variance = speeds.reduce((a, b) => a + Math.pow(b - meanSpeed, 2), 0) / speeds.length; const stdDevSpeed = Math.sqrt(variance); const VERY_LOW_STD_DEV_THRESHOLD = 15; // px/s. Tune this value carefully. // If std dev is below this, speed is too constant. if (stdDevSpeed < VERY_LOW_STD_DEV_THRESHOLD && speeds.length > 2) { // Ensure enough speed segments for meaningful std dev snn_captcha_isBotMovement = true; console.log(`Advanced Captcha: Low speed variance detected (StdDev: ${stdDevSpeed.toFixed(2)} px/s)`); } } } if (snn_captcha_isBotMovement) { console.warn("Advanced Captcha: Potential bot-like movement pattern detected. Failing mouse captcha silently."); if (snn_captcha_dropTarget) { // Visually indicate failure as if target was missed snn_captcha_dropTarget.style.borderColor = '#dc3545'; snn_captcha_dropTarget.style.borderStyle = 'dashed'; } snn_captcha_resetDraggableItemPosition(); snn_captcha_mouseCaptchaVerified = false; // Explicitly ensure verification fails snn_captcha_checkAllCaptchasVerified(); return; // Exit early, CAPTCHA failed due to bot detection } const snn_captcha_itemRect = snn_captcha_draggableItem.getBoundingClientRect(); const snn_captcha_targetRect = snn_captcha_dropTarget.getBoundingClientRect(); const snn_captcha_itemCenterX = snn_captcha_itemRect.left + snn_captcha_itemRect.width / 2; const snn_captcha_itemCenterY = snn_captcha_itemRect.top + snn_captcha_itemRect.height / 2; if (snn_captcha_itemCenterX >= snn_captcha_targetRect.left && snn_captcha_itemCenterX <= snn_captcha_targetRect.right && snn_captcha_itemCenterY >= snn_captcha_targetRect.top && snn_captcha_itemCenterY <= snn_captcha_targetRect.bottom) { snn_captcha_mouseCaptchaVerified = true; if(snn_captcha_mouseCaptchaArea) snn_captcha_mouseCaptchaArea.classList.add('snn_captcha_verified-area'); if(snn_captcha_draggableItem) { snn_captcha_draggableItem.textContent = 'OK'; snn_captcha_draggableItem.style.cursor = 'default'; } if (snn_captcha_dropTarget) { snn_captcha_dropTarget.style.borderColor = '#28a745'; snn_captcha_dropTarget.style.borderStyle = 'solid'; } } else { if (snn_captcha_dropTarget) { snn_captcha_dropTarget.style.borderColor = '#dc3545'; snn_captcha_dropTarget.style.borderStyle = 'dashed'; } snn_captcha_resetDraggableItemPosition(); snn_captcha_mouseCaptchaVerified = false; // Ensure it's false if missed } snn_captcha_checkAllCaptchasVerified(); } if (snn_captcha_draggableItem && snn_captcha_mouseCaptchaArea && snn_captcha_dropTarget) { snn_captcha_draggableItem.addEventListener('mousedown', snn_captcha_handleDragStart); snn_captcha_draggableItem.addEventListener('touchstart', snn_captcha_handleDragStart, { passive: false }); } function snn_captcha_checkAllCaptchasVerified() { if (!snn_captcha_loginBtn) return; if (snn_captcha_mathCaptchaVerified && snn_captcha_mouseCaptchaVerified) { snn_captcha_loginBtn.disabled = false; } else { snn_captcha_loginBtn.disabled = true; } } targetForm.addEventListener('submit', (event) => { if (!snn_captcha_mathCaptchaVerified || !snn_captcha_mouseCaptchaVerified) { event.preventDefault(); if (formMessageDiv) { formMessageDiv.textContent = 'Please complete all security checks correctly.'; formMessageDiv.className = 'form-overall-feedback error-text'; // Example class } if (!snn_captcha_mathCaptchaVerified && snn_captcha_mathAnswerInput && !snn_captcha_mathAnswerInput.disabled) { snn_captcha_mathAnswerInput.focus(); } else if (snn_captcha_mathCaptchaVerified && !snn_captcha_mouseCaptchaVerified) { if (snn_captcha_mouseCaptchaStep && snn_captcha_mouseCaptchaStep.classList.contains('snn_captcha_is-hidden')) { if(snn_captcha_mathCaptchaStep) snn_captcha_mathCaptchaStep.classList.add('snn_captcha_is-hidden'); if(snn_captcha_mouseCaptchaStep) snn_captcha_mouseCaptchaStep.classList.remove('snn_captcha_is-hidden'); } } return; } event.preventDefault(); if (formMessageDiv) { const usernameInput = targetForm.querySelector('input[name="username"], input[type="email"]'); const username = usernameInput ? usernameInput.value : 'user'; formMessageDiv.textContent = `Form submission attempt for ${username} (Captchas passed!). This is a demo.`; formMessageDiv.className = 'form-overall-feedback success-text'; // Example class } console.log('Form submitted (demo) for:', targetForm.id || targetForm.name || 'Unnamed Form'); }); function snn_captcha_resetFormAndCaptchas() { snn_captcha_mathCaptchaVerified = false; snn_captcha_mouseCaptchaVerified = false; if (snn_captcha_mathAnswerInput) snn_captcha_resetInputStyles(snn_captcha_mathAnswerInput); snn_captcha_generateMathProblem(); if (snn_captcha_mathCaptchaStep) snn_captcha_mathCaptchaStep.classList.remove('snn_captcha_is-hidden'); if (snn_captcha_mouseCaptchaStep) snn_captcha_mouseCaptchaStep.classList.add('snn_captcha_is-hidden'); snn_captcha_resetDraggableItemPosition(); snn_captcha_isDragging = false; snn_captcha_mouseMoveEvents = []; snn_captcha_mouseEventCount = 0; document.removeEventListener('mousemove', snn_captcha_handleDragMove); document.removeEventListener('mouseup', snn_captcha_handleDragEnd); document.removeEventListener('touchmove', snn_captcha_handleDragMove); document.removeEventListener('touchend', snn_captcha_handleDragEnd); snn_captcha_checkAllCaptchasVerified(); if (formMessageDiv) formMessageDiv.textContent = ''; } if (snn_captcha_mathCaptchaStep && snn_captcha_mouseCaptchaStep && snn_captcha_draggableItem && snn_captcha_mathQuestionLabel && snn_captcha_mathAnswerInput && snn_captcha_dropTarget) { snn_captcha_resetFormAndCaptchas(); } else { let snn_captcha_missingElements = []; if (!snn_captcha_mathCaptchaStep) snn_captcha_missingElements.push("snn_captcha_mathCaptchaStep"); if (!snn_captcha_mouseCaptchaStep) snn_captcha_missingElements.push("snn_captcha_mouseCaptchaStep"); if (!snn_captcha_draggableItem) snn_captcha_missingElements.push("snn_captcha_draggableItem"); if (!snn_captcha_mathQuestionLabel) snn_captcha_missingElements.push("snn_captcha_mathQuestionLabel"); if (!snn_captcha_mathAnswerInput) snn_captcha_missingElements.push("snn_captcha_mathAnswerInput"); if (!snn_captcha_dropTarget) snn_captcha_missingElements.push("snn_captcha_dropTarget"); if (snn_captcha_missingElements.length > 0) { console.error(`Advanced Captcha: Could not initialize. Missing critical elements: ${snn_captcha_missingElements.join(', ')}.`); if(snn_captcha_loginBtn) snn_captcha_loginBtn.disabled = true; // Disable submission if CAPTCHA broken if(snn_captcha_insertedCaptchaNode) snn_captcha_insertedCaptchaNode.innerHTML = "<p class='snn_captcha_feedback-message snn_captcha_error-text'>Error initializing security check. Please refresh.</p>"; } } } window.addEventListener('DOMContentLoaded', () => { const targetFormElement = document.querySelector(formSelectorString); if (targetFormElement) { initializeAdvancedCaptcha(targetFormElement); } else { console.warn(`Advanced Captcha: Form with selector "${formSelectorString}" not found.`); } }); </script> <style> .snn_captcha_container { } .snn_captcha_math-step-container { } .snn_captcha_math-input-group { display: flex; align-items: center; margin-bottom: 10px; justify-content: space-between; } .snn_captcha_math-question-label { font-size: 18px; font-weight: 600; color: #333333; margin-right: 8px; } .snn_captcha_math-answer-input { width: calc(100% - 90px); padding: 8px 12px; border: 1px solid #cccccc; border-radius: 6px; box-shadow: inset 0 1px 2px rgba(0,0,0,0.075); font-size: 14px; color: #333333; background-color: #ffffff; box-sizing: border-box; } .snn_captcha_math-answer-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); } .snn_captcha_math-answer-input.snn_captcha_input-error-state { border-color: #dc3545; color: #dc3545; } .snn_captcha_math-answer-input.snn_captcha_input-error-state:focus { border-color: #dc3545; box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.25); } .snn_captcha_math-answer-input.snn_captcha_input-success-state { border-color: #28a745; color: #155724; } .snn_captcha_math-answer-input.snn_captcha_input-success-state:focus { border-color: #28a745; box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.25); } .snn_captcha_math-answer-input:disabled { background-color: #e9ecef; color: #6c757d; cursor: not-allowed; border-color: #ced4da; } .snn_captcha_is-hidden { display: none !important; } .snn_captcha_mouse-step-container { margin-bottom: 10px; display: flex; justify-content: space-between; gap:10px; } .snn_captcha_mouse-captcha-instruction { display: block; font-size: 18px; font-weight: 600; color: #333333; margin-right: 8px; margin-top: 8px; } #snn_captcha_mouse-captcha-area { width: 100%; height: 60px; position: relative; overflow: hidden; cursor: default; box-sizing: border-box; } #snn_captcha_draggable-item { width: 50px; height: 50px; background-color: #007bff; color: white; display: flex; justify-content: center; align-items: center; border-radius: 4px; position: absolute; top: 50%; left: 20px; transform: translateY(-50%); cursor: grab; user-select: none; font-size: 14px; font-weight: bold; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); z-index: 99; box-sizing: border-box; transition: background-color 0.2s ease-in-out; } #snn_captcha_draggable-item.snn_captcha_is-grabbing { cursor: grabbing; background-color: #0056b3; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } #snn_captcha_drop-target { width: 60px; height: 100%; background-color: transparent; border: 4px dashed #007bff; display: flex; justify-content: center; align-items: center; position: absolute; top: 0; right: 20px; font-size: 12px; font-weight: bold; border-radius: 0 4px 4px 0; box-sizing: border-box; } #snn_captcha_mouse-captcha-area.snn_captcha_verified-area #snn_captcha_draggable-item { background-color: #28a745; cursor: default; } button[type="submit"]:disabled, input[type="submit"]:disabled { background-color: #cccccc !important; color: #666666 !important; border-color: #bbbbbb !important; cursor: not-allowed !important; opacity: 0.65 !important; } .form-overall-feedback { margin-top: 16px; text-align: center; font-size: 14px; min-height: 20px; } </style>