paybito-slider-captcha
Version: 
A beautiful, interactive slider-based CAPTCHA verification system with puzzle piece matching. Provides secure human verification with an elegant user interface.
1,100 lines (962 loc) âĸ 32.8 kB
JavaScript
/**
 * Paybito Slider Captcha - Interactive Puzzle Verification System
 * A beautiful, secure slider-based CAPTCHA with puzzle piece matching
 * 
 * @author Md Athar
 * @version 1.0.1
 * @license MIT
 */
class VerificationSlider {
  constructor(options = {}) {
    if(!options.apiEndpoint) return alert("API endpoint is required for VerificationSlider");
    // API Configuration
    this.API_ENDPOINT = options.apiEndpoint;
    
    // Captcha Properties
    this.puzzleX = 0;
    this.sliderValue = 0;
    this.captchaId = '';
    this.positionX = '';
    this.positionY = '';
    this.encryptedData = '';
    this.tolerance = options.tolerance || 5;
    
    // Puzzle Configuration
    this.shapes = ["star", "circle", "triangle", "diamond", "puzzle", "heart"];
    this.defaultImage = options.defaultImage || "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRsULqCy41TcR6rECUinoAuRQhaJsgcIjvtmw&s";
    this.imageWidth = options.imageWidth || 300;
    this.imageHeight = options.imageHeight || 200;
    this.pieceSize = options.pieceSize || 50;
    
    // State Management
    this.inactivityTimer = null;
    this.callback = null;
    this.modal = null;
    this.overlay = null;
    this.isInitialized = false;
    this.isVisible = false;
  }
  /**
   * Initialize the verification slider
   * Creates the modal and sets up the interface
   */
  init() {
    if (this.isInitialized) return;
    this.createModal();
    this.isInitialized = true;
  }
  /**
   * Show verification modal and start the verification process
   * @param {Function} callback - Callback function to handle verification result
   */
  verify(callback) {
    if (!this.isInitialized) this.init();
    this.callback = callback;
    this.showModal();
    this.loadDefaultImage();
  }
  /**
   * Create the modal HTML structure
   * @private
   */
  createModal() {
    this.overlay = document.createElement('div');
    this.overlay.className = 'verification-overlay';
    
    this.modal = document.createElement('div');
    this.modal.className = 'verification-modal';
    const modalHTML = `
      <div class="verification-modal-content">
        <div class="verification-modal-header">
          <h3 class="verification-modal-title">
            <span class="verification-shield-icon">đĄī¸</span>
            Security Verification
          </h3>
          <button type="button" class="verification-close-btn" id="verification-close-btn">
            <span class="verification-close-icon">Ã</span>
          </button>
        </div>
        <div class="verification-modal-body">
          <div class="verification-captcha-container">
            <div class="verification-captcha-area">
              <img id="verification-base-image" alt="Verification puzzle" class="verification-base-image">
              <img id="verification-drag-piece" class="verification-drag-piece">
              <div id="verification-loader" class="verification-loader">
                <div class="verification-spinner"></div>
                <p>Generating puzzle...</p>
              </div>
            </div>
            <div class="verification-slider-container">
              <label for="verification-slider" class="verification-slider-label">
                <span class="verification-arrow-icon">â</span>
                Slide to verify
              </label>
              <div class="verification-slider-wrapper">
                <input type="range" id="verification-slider" class="verification-slider" min="0" max="100" value="0">
                <div class="verification-slider-track">
                  <div class="verification-slider-fill" id="verification-slider-fill"></div>
                </div>
              </div>
            </div>
            <div class="verification-status" id="verification-status">
              <div class="verification-alert verification-alert-info">
                <span class="verification-info-icon">âšī¸</span>
                Drag the slider to match the puzzle piece position
              </div>
            </div>
          </div>
        </div>
        <div class="verification-modal-footer">
          <div class="verification-powered-by">
            Powered by <a href="https://paybito.com" target="_blank">PayBitoPro</a>
          </div>
          <div class="verification-footer-buttons">
            <button type="button" class="verification-btn verification-btn-secondary" id="verification-refresh-btn">
              <span class="verification-refresh-icon">đ</span>
              Refresh
            </button>
            <button type="button" class="verification-btn verification-btn-cancel" id="verification-cancel-btn">
              Cancel
            </button>
          </div>
        </div>
      </div>
    `;
    this.modal.innerHTML = modalHTML;
    this.overlay.appendChild(this.modal);
    document.body.appendChild(this.overlay);
    
    this.addStyles();
    this.setupEventListeners();
  }
  /**
   * Add CSS styles to the document
   * @private
   */
  addStyles() {
    const styles = `
      <style id="verification-slider-styles">
        .verification-overlay {
          position: fixed;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: rgba(0, 0, 0, 0.5);
          display: none;
          justify-content: center;
          align-items: center;
          z-index: 10000;
          backdrop-filter: blur(5px);
          animation: fadeIn 0.3s ease-in-out;
        }
        
        .verification-overlay.show {
          display: flex;
        }
        
        .verification-modal {
          background: white;
          border-radius: 12px;
          margin: 0 auto;
          box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
          max-width: 500px;
          width: 90%;
          max-height: 90vh;
          overflow-y: auto;
          transform: scale(0.8);
          opacity: 0;
          transition: all 0.3s ease-in-out;
        }
        
        .verification-modal.show {
          transform: scale(1);
          opacity: 1;
        }
        
        .verification-modal-content {
          position: relative;
        }
        
        .verification-modal-header {
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          color: white;
          padding: 20px;
          border-radius: 12px 12px 0 0;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        
        .verification-modal-title {
          margin: 0;
          font-size: 18px;
          font-weight: 600;
          display: flex;
          align-items: center;
          gap: 10px;
        }
        
        .verification-shield-icon {
          font-size: 20px;
        }
        
        .verification-close-btn {
          background: none;
          border: none;
          color: white;
          cursor: pointer;
          padding: 5px;
          border-radius: 50%;
          width: 30px;
          height: 30px;
          display: flex;
          align-items: center;
          justify-content: center;
          transition: background 0.2s;
        }
        
        .verification-close-btn:hover {
          background: rgba(255, 255, 255, 0.2);
        }
        
        .verification-close-icon {
          font-size: 20px;
          line-height: 1;
        }
        
        .verification-modal-body {
          padding: 30px;
        }
        
        .verification-captcha-container {
          max-width: 400px;
          margin: 0 auto;
          padding: 25px;
          border: 2px solid #e1e8ed;
          border-radius: 12px;
          background: linear-gradient(145deg, #ffffff, #f8f9fa);
          box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
        }
        
        .verification-captcha-area {
          position: relative;
          width: 100%;
          aspect-ratio: 3/2;
          margin: 0 auto 25px;
          border: 2px solid #ddd;
          border-radius: 8px;
          background: #f8f9fa;
          box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.1);
          overflow: hidden;
        }
        
        .verification-base-image {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 6px;
          opacity: 0;
          transition: opacity 0.3s ease;
        }
        
        .verification-base-image.loaded {
          opacity: 1;
        }
        
        .verification-drag-piece {
          position: absolute;
          width: 16.67%;
          height: 25%;
          cursor: grab;
          z-index: 10;
          transition: left 0.1s ease-in-out;
          border-radius: 6px;
          left: 0;
          top: 0;
          opacity: 0;
          transition: opacity 0.3s ease, left 0.1s ease-in-out;
        }
        
        .verification-drag-piece.loaded {
          opacity: 1;
        }
        
        .verification-drag-piece:active {
          cursor: grabbing;
        }
        
        .verification-loader {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          text-align: center;
          background: rgba(255, 255, 255, 0.95);
          padding: 25px;
          border-radius: 10px;
          box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
          z-index: 20;
        }
        
        .verification-spinner {
          width: 40px;
          height: 40px;
          border: 4px solid #f3f3f3;
          border-top: 4px solid #667eea;
          border-radius: 50%;
          animation: spin 1s linear infinite;
          margin: 0 auto 15px;
        }
        
        .verification-loader p {
          margin: 0;
          color: #666;
          font-size: 14px;
        }
        
        .verification-slider-container {
          margin-bottom: 25px;
        }
        
        .verification-slider-label {
          display: flex;
          align-items: center;
          gap: 8px;
          margin-bottom: 12px;
          font-size: 14px;
          font-weight: 600;
          color: #333;
        }
        
        .verification-arrow-icon {
          font-size: 16px;
          color: #667eea;
          font-weight: bold;
        }
        
        .verification-slider-wrapper {
          position: relative;
        }
        
        .verification-slider {
          width: 100%;
          height: 8px;
          background: linear-gradient(to right, #e9ecef, #dee2e6);
          border-radius: 20px;
          outline: none;
          appearance: none;
          cursor: pointer;
          position: relative;
          z-index: 2;
        }
        
        .verification-slider::-webkit-slider-thumb {
          appearance: none;
          width: 32px;
          height: 32px;
          background: linear-gradient(45deg, #667eea, #764ba2);
          border-radius: 50%;
          cursor: pointer;
          box-shadow: 0 3px 8px rgba(102, 126, 234, 0.5);
          transition: all 0.2s ease;
          position: relative;
        }
        
        .verification-slider::-webkit-slider-thumb:hover {
          transform: scale(1.1);
          box-shadow: 0 5px 15px rgba(102, 126, 234, 0.7);
        }
        
        .verification-slider::-moz-range-thumb {
          width: 32px;
          height: 32px;
          background: linear-gradient(45deg, #667eea, #764ba2);
          border-radius: 50%;
          cursor: pointer;
          border: none;
          box-shadow: 0 3px 8px rgba(102, 126, 234, 0.5);
          position: relative;
        }
        
        .verification-slider-track {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          background: #e9ecef;
          z-index: 1;
        }
        
        .verification-slider-fill {
          height: 100%;
          background: linear-gradient(90deg, #667eea, #764ba2);
          border-radius: 20px;
          width: 0%;
          transition: width 0.1s ease;
        }
        
        .verification-status {
          min-height: 60px;
        }
        
        .verification-alert {
          padding: 12px 16px;
          border-radius: 8px;
          margin: 0;
          font-size: 14px;
          display: flex;
          align-items: center;
          gap: 10px;
          line-height: 1.4;
        }
        
        .verification-alert-success {
          background: #d4edda;
          border: 1px solid #c3e6cb;
          color: #155724;
        }
        
        .verification-alert-danger {
          background: #f8d7da;
          border: 1px solid #f5c6cb;
          color: #721c24;
        }
        
        .verification-alert-info {
          background: #cce7ff;
          border: 1px solid #b8daff;
          color: #004085;
        }
        
        .verification-info-icon {
          font-size: 16px;
        }
        
        .verification-modal-footer {
          padding: 20px 30px;
          border-top: 1px solid #e1e8ed;
          display: flex;
          justify-content: space-between;
          align-items: center;
          gap: 12px;
          border-radius: 0 0 12px 12px;
          background: #f8f9fa;
        }
        
        .verification-powered-by {
          font-size: 12px;
          color: #666;
          display: flex;
          align-items: center;
          gap: 5px;
        }
        
        .verification-powered-by a {
          color: #667eea;
          text-decoration: none;
          font-weight: 600;
        }
        
        .verification-powered-by a:hover {
          text-decoration: underline;
        }
        
        .verification-footer-buttons {
          display: flex;
          gap: 12px;
        }
        
        .verification-btn {
          padding: 10px 20px;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 14px;
          font-weight: 500;
          transition: all 0.2s ease;
          display: flex;
          align-items: center;
          gap: 8px;
          min-width: 100px;
          justify-content: center;
        }
        
        .verification-btn-secondary {
          background: #6c757d;
          color: white;
        }
        
        .verification-btn-secondary:hover {
          background: #5a6268;
          transform: translateY(-1px);
        }
        
        .verification-btn-cancel {
          background: #dc3545;
          color: white;
        }
        
        .verification-btn-cancel:hover {
          background: #c82333;
          transform: translateY(-1px);
        }
        
        .verification-refresh-icon {
          font-size: 14px;
        }
        
        @keyframes fadeIn {
          from { opacity: 0; }
          to { opacity: 1; }
        }
        
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
        
        @media (max-width: 767px) {
          .verification-modal {
            width: 95%;
            margin: 10px;
          }
          
          .verification-modal-body {
            padding: 20px;
          }
          
          .verification-captcha-container {
            padding: 20px;
          }
          
          .verification-drag-piece {
            width: 20%;
            height: 30%;
          }
          
          .verification-modal-footer {
            padding: 15px 20px;
            flex-direction: column;
          }
          
          .verification-footer-buttons {
            width: 100%;
            flex-direction: column;
          }
          
          .verification-btn {
            width: 100%;
          }
          
          .verification-powered-by {
            align-self: center;
            margin-top: 10px;
          }
        }
      </style>
    `;
    document.head.insertAdjacentHTML('beforeend', styles);
  }
  /**
   * Set up event listeners for the slider and modal interactions
   * @private
   */
  setupEventListeners() {
    const slider = document.getElementById('verification-slider');
    const sliderFill = document.getElementById('verification-slider-fill');
    const dragPiece = document.getElementById('verification-drag-piece');
    const closeBtn = document.getElementById('verification-close-btn');
    const cancelBtn = document.getElementById('verification-cancel-btn');
    const refreshBtn = document.getElementById('verification-refresh-btn');
    // Slider events
    slider.addEventListener('input', (e) => {
      this.sliderValue = e.target.value;
      sliderFill.style.width = `${this.sliderValue}%`;
      const captchaArea = document.querySelector('.verification-captcha-area');
      const areaWidth = captchaArea.offsetWidth;
      const pieceWidth = dragPiece.offsetWidth;
      const maxOffset = areaWidth - pieceWidth;
      const offsetX = (this.sliderValue / 100) * maxOffset;
      dragPiece.style.left = `${offsetX}px`;
      this.resetInactivityTimer();
    });
    slider.addEventListener('change', () => {
      this.validatePosition();
    });
    // Button events - bind to this instance
    closeBtn.addEventListener('click', () => {
      this.hideModal();
    });
    cancelBtn.addEventListener('click', () => {
      this.hideModal();
    });
    refreshBtn.addEventListener('click', () => {
      this.refreshCaptcha();
    });
    // Close modal on overlay click
    this.overlay.addEventListener('click', (e) => {
      if (e.target === this.overlay) this.hideModal();
    });
    // Close modal on Escape key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && this.isVisible) this.hideModal();
    });
  }
  /**
   * Show the verification modal
   * @private
   */
  showModal() {
    this.overlay.classList.add('show');
    setTimeout(() => {
      this.modal.classList.add('show');
    }, 100);
    this.isVisible = true;
    document.body.style.overflow = 'hidden';
  }
  /**
   * Hide the verification modal
   */
  hideModal() {
    this.modal.classList.remove('show');
    setTimeout(() => {
      this.overlay.classList.remove('show');
      this.isVisible = false;
      document.body.style.overflow = '';
      this.cleanup();
      if (this.callback) this.callback({ success: false });
    }, 300);
  }
  /**
   * Load the default image and start puzzle generation
   * @private
   */
  loadDefaultImage() {
    // Show loader immediately
    this.showLoader();
    
    // Hide images initially
    const baseImage = document.getElementById('verification-base-image');
    const dragPiece = document.getElementById('verification-drag-piece');
    
    baseImage.classList.remove('loaded');
    dragPiece.classList.remove('loaded');
    baseImage.style.opacity = '0';
    dragPiece.style.opacity = '0';
    
    // Start generating captcha after a short delay
    setTimeout(() => {
      this.generateCaptcha();
    }, 500);
  }
  /**
   * Generate CAPTCHA from API
   * @private
   */
  async generateCaptcha() {
    try {
      const response = await fetch(this.API_ENDPOINT);
      const data = await response.json();
      
      this.captchaId = data.sessionId;
      this.positionX = data.positionX;
      this.positionY = data.positionY;
      
      const msg = `${this.positionX}:Rtz2IQzefccFwmiurXq)8IjzUm?v3o:${this.positionY}`;
      this.encryptedData = await this.sha256(msg);
      
      // Create puzzle and wait for it to complete
      await this.createPuzzle(data);
      
    } catch (error) {
      console.error('Error generating CAPTCHA:', error);
      this.showStatus('Error generating verification. Please try again.', 'danger');
      this.hideLoader();
    }
  }
  /**
   * Create the puzzle piece and base image
   * @private
   * @param {Object} data - CAPTCHA data from API
   */
  async createPuzzle(data) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = this.imageWidth;
    canvas.height = this.imageHeight;
    const image = new Image();
    image.crossOrigin = "anonymous";
    image.src = data.baseImage || this.defaultImage;
    return new Promise((resolve, reject) => {
      image.onload = () => {
        try {
          ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
          const captchaArea = document.querySelector('.verification-captcha-area');
          const areaWidth = captchaArea.offsetWidth;
          const areaHeight = captchaArea.offsetHeight;
          const pieceWidthPx = (this.pieceSize / this.imageWidth) * areaWidth;
          const pieceHeightPx = (this.pieceSize / this.imageHeight) * areaHeight;
          this.puzzleX = Math.floor(Math.random() * (canvas.width - this.pieceSize));
          const fixedY = parseInt(data.positionY || Math.floor(Math.random() * (canvas.height - this.pieceSize)));
          const shape = this.shapes[Math.floor(Math.random() * this.shapes.length)];
          // Create puzzle piece
          const pieceCanvas = document.createElement('canvas');
          const pieceCtx = pieceCanvas.getContext('2d');
          pieceCanvas.width = this.pieceSize;
          pieceCanvas.height = this.pieceSize;
          this.drawShape(pieceCtx, shape, this.pieceSize / 2, this.pieceSize / 2);
          pieceCtx.clip();
          pieceCtx.drawImage(canvas, this.puzzleX, fixedY, this.pieceSize, this.pieceSize, 0, 0, this.pieceSize, this.pieceSize);
          // Remove piece from base image
          ctx.save();
          ctx.beginPath();
          this.drawShape(ctx, shape, this.puzzleX + this.pieceSize / 2, fixedY + this.pieceSize / 2);
          ctx.clip();
          ctx.clearRect(this.puzzleX, fixedY, this.pieceSize, this.pieceSize);
          ctx.restore();
          // Update UI
          const baseImage = document.getElementById('verification-base-image');
          const dragPiece = document.getElementById('verification-drag-piece');
          const slider = document.getElementById('verification-slider');
          const sliderFill = document.getElementById('verification-slider-fill');
          // Set the images
          baseImage.src = canvas.toDataURL();
          dragPiece.src = pieceCanvas.toDataURL();
          
          // Reset slider
          slider.value = 0;
          sliderFill.style.width = '0%';
          dragPiece.style.left = '0px';
          const topPositionPercent = (fixedY / this.imageHeight) * 100;
          dragPiece.style.top = `${topPositionPercent}%`;
          // Wait for both images to load before hiding loader
          let baseImageLoaded = false;
          let pieceImageLoaded = false;
          const checkBothImagesLoaded = () => {
            if (baseImageLoaded && pieceImageLoaded) {
              // Small delay to ensure smooth transition
              setTimeout(() => {
                this.hideLoader();
                baseImage.classList.add('loaded');
                dragPiece.classList.add('loaded');
                baseImage.style.opacity = '1';
                dragPiece.style.opacity = '1';
              }, 200);
            }
          };
          // Add load listeners to both images
          const tempBaseImg = new Image();
          tempBaseImg.onload = () => {
            baseImageLoaded = true;
            checkBothImagesLoaded();
          };
          tempBaseImg.src = baseImage.src;
          const tempPieceImg = new Image();
          tempPieceImg.onload = () => {
            pieceImageLoaded = true;
            checkBothImagesLoaded();
          };
          tempPieceImg.src = dragPiece.src;
          this.resetInactivityTimer();
          resolve();
          
        } catch (error) {
          console.error('Error creating puzzle:', error);
          this.hideLoader();
          reject(error);
        }
      };
      image.onerror = () => {
        console.error('Error loading image');
        this.hideLoader();
        this.showStatus('Error loading image. Please try again.', 'danger');
        reject(new Error('Image loading failed'));
      };
    });
  }
  /**
   * Draw different shapes for puzzle pieces
   * @private
   * @param {CanvasRenderingContext2D} ctx - Canvas context
   * @param {string} shape - Shape type
   * @param {number} x - X coordinate
   * @param {number} y - Y coordinate
   */
  drawShape(ctx, shape, x, y) {
    switch (shape) {
      case 'star':
        this.drawStar(ctx, x, y, 5, 20, 10);
        break;
      case 'circle':
        this.drawCircle(ctx, x, y, 20);
        break;
      case 'triangle':
        this.drawTriangle(ctx, x, y, 40, 40);
        break;
      case 'diamond':
        this.drawDiamond(ctx, x, y, 40, 40);
        break;
      case 'puzzle':
        this.drawPuzzle(ctx, x, y, 60);
        break;
      case 'heart':
        this.drawHeart(ctx, x, y, 30);
        break;
      default:
        this.drawCircle(ctx, x, y, 20);
    }
  }
  /**
   * Draw star shape
   * @private
   */
  drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
    let rot = (Math.PI / 2) * 3;
    const step = Math.PI / spikes;
    ctx.beginPath();
    ctx.moveTo(cx, cy - outerRadius);
    for (let i = 0; i < spikes; i++) {
      const x = cx + Math.cos(rot) * outerRadius;
      const y = cy + Math.sin(rot) * outerRadius;
      ctx.lineTo(x, y);
      rot += step;
      const x2 = cx + Math.cos(rot) * innerRadius;
      const y2 = cy + Math.sin(rot) * innerRadius;
      ctx.lineTo(x2, y2);
      rot += step;
    }
    ctx.closePath();
  }
  /**
   * Draw circle shape
   * @private
   */
  drawCircle(ctx, cx, cy, radius) {
    ctx.beginPath();
    ctx.arc(cx, cy, radius, 0, Math.PI * 2);
    ctx.closePath();
  }
  /**
   * Draw triangle shape
   * @private
   */
  drawTriangle(ctx, cx, cy, width, height) {
    ctx.beginPath();
    ctx.moveTo(cx, cy - height / 2);
    ctx.lineTo(cx - width / 2, cy + height / 2);
    ctx.lineTo(cx + width / 2, cy + height / 2);
    ctx.closePath();
  }
  /**
   * Draw diamond shape
   * @private
   */
  drawDiamond(ctx, cx, cy, width, height) {
    ctx.beginPath();
    ctx.moveTo(cx, cy - height / 2);
    ctx.lineTo(cx - width / 2, cy);
    ctx.lineTo(cx, cy + height / 2);
    ctx.lineTo(cx + width / 2, cy);
    ctx.closePath();
  }
  /**
   * Draw puzzle piece shape
   * @private
   */
  drawPuzzle(ctx, centerX, centerY, size) {
    const puzzleSize = size * 0.6;
    const tabSize = puzzleSize * 0.25;
    const left = centerX - puzzleSize / 2;
    const right = centerX + puzzleSize / 2;
    const top = centerY - puzzleSize / 2;
    const bottom = centerY + puzzleSize / 2;
    const midX = centerX;
    const midY = centerY;
    ctx.beginPath();
    ctx.moveTo(left, top);
    ctx.lineTo(midX - tabSize, top);
    ctx.quadraticCurveTo(midX - tabSize + 9, top - tabSize - 4, midX + tabSize, top);
    ctx.lineTo(right, top);
    ctx.lineTo(right, midY - tabSize);
    ctx.quadraticCurveTo(right + tabSize + 4, midY - tabSize + 9, right, midY + tabSize);
    ctx.lineTo(right, bottom);
    ctx.lineTo(left, bottom);
    ctx.lineTo(left, midY + tabSize);
    ctx.quadraticCurveTo(left + tabSize, midY + tabSize, left + tabSize, midY);
    ctx.quadraticCurveTo(left + tabSize, midY - tabSize, left, midY - tabSize);
    ctx.lineTo(left, top);
    ctx.closePath();
  }
  /**
   * Draw heart shape
   * @private
   */
  drawHeart(ctx, centerX, centerY, size) {
    const topCurveHeight = size * 0.3;
    ctx.beginPath();
    ctx.moveTo(centerX, centerY + size / 2);
    ctx.bezierCurveTo(
      centerX - size, centerY - topCurveHeight,
      centerX - size * 0.1, centerY - topCurveHeight,
      centerX, centerY
    );
    ctx.bezierCurveTo(
      centerX + size * 0.1, centerY - topCurveHeight,
      centerX + size, centerY - topCurveHeight,
      centerX, centerY + size / 2
    );
    ctx.closePath();
  }
  /**
   * Validate if the slider position matches the puzzle position
   * @private
   */
  validatePosition() {
    const captchaArea = document.querySelector('.verification-captcha-area');
    const areaWidth = captchaArea.offsetWidth;
    const dragPiece = document.getElementById('verification-drag-piece');
    const pieceWidth = dragPiece.offsetWidth;
    const maxOffset = areaWidth - pieceWidth;
    const offsetX = (this.sliderValue / 100) * maxOffset;
    const actualPositionPercent = (this.puzzleX / this.imageWidth) * 100;
    const sliderPositionPercent = (offsetX / areaWidth) * 100;
    if (Math.abs(actualPositionPercent - sliderPositionPercent) <= this.tolerance) {
      this.showStatus('Verification successful! â
', 'success');
      setTimeout(() => {
        this.hideModal();
        if (this.callback) {
          this.callback({
            success: true,
            sessionId: this.captchaId,
            gRecaptchaResponse: this.encryptedData
          });
        }
      }, 1000);
    } else {
      this.showStatus('Position incorrect. Please try again. â', 'danger');
      setTimeout(() => {
        this.refreshCaptcha();
      }, 1500);
    }
  }
  /**
   * Show status message
   * @private
   * @param {string} message - Status message
   * @param {string} type - Message type (success, danger, info)
   */
  showStatus(message, type) {
    const statusDiv = document.getElementById('verification-status');
    const iconClass = type === 'success' ? 'â
' : type === 'danger' ? 'â' : 'âšī¸';
    statusDiv.innerHTML = `
      <div class="verification-alert verification-alert-${type}">
        <span class="verification-info-icon">${iconClass}</span>
        ${message}
      </div>
    `;
  }
  /**
   * Show loading indicator
   * @private
   */
  showLoader() {
    document.getElementById('verification-loader').style.display = 'block';
  }
  /**
   * Hide loading indicator
   * @private
   */
  hideLoader() {
    document.getElementById('verification-loader').style.display = 'none';
  }
  /**
   * Refresh the CAPTCHA puzzle
   */
  refreshCaptcha() {
    const slider = document.getElementById('verification-slider');
    const sliderFill = document.getElementById('verification-slider-fill');
    
    slider.value = 0;
    sliderFill.style.width = '0%';
    this.sliderValue = 0;
    
    this.showStatus('Drag the slider to match the puzzle piece position', 'info');
    this.loadDefaultImage();
  }
  /**
   * Reset the inactivity timer
   * @private
   */
  resetInactivityTimer() {
    clearTimeout(this.inactivityTimer);
    this.inactivityTimer = setTimeout(() => {
      if (this.sliderValue === 0) {
        this.refreshCaptcha();
      }
    }, 60000); // 60 seconds
  }
  /**
   * Generate SHA-256 hash
   * @private
   * @param {string} message - Message to hash
   * @returns {Promise<string>} Hexadecimal hash
   */
  async sha256(message) {
    const msgBuffer = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
  }
  /**
   * Clean up timers and reset state
   * @private
   */
  cleanup() {
    clearTimeout(this.inactivityTimer);
    this.sliderValue = 0;
    this.showStatus('Drag the slider to match the puzzle piece position', 'info');
  }
  /**
   * Destroy the verification slider and clean up
   */
  destroy() {
    if (this.overlay) this.overlay.remove();
    const styles = document.getElementById('verification-slider-styles');
    if (styles) styles.remove();
    document.body.style.overflow = '';
    this.isInitialized = false;
    this.isVisible = false;
    this.cleanup();
  }
}
export default VerificationSlider;