UNPKG

drag-puzzle-captcha

Version:

A beautiful, modern drag-and-drop puzzle CAPTCHA component for React applications

973 lines (845 loc) 21.8 kB
/* Modal overlay */ .drag-puzzle-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.8)); backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; z-index: 9999; animation: fadeIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); padding: 1rem; box-sizing: border-box; } @keyframes fadeIn { from { opacity: 0; backdrop-filter: blur(0px); } to { opacity: 1; backdrop-filter: blur(8px); } } /* Modal container */ .drag-puzzle-modal { background: linear-gradient(145deg, #ffffff, #f8f9fa); border-radius: clamp(16px, 4vw, 20px); box-shadow: 0 25px 80px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.8); width: 100%; max-width: min(90vw, 450px); max-height: 90vh; overflow: hidden; animation: slideIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); position: relative; border: 1px solid rgba(255, 255, 255, 0.2); margin: auto; } @keyframes slideIn { from { transform: scale(0.8) translateY(-40px) rotateX(15deg); opacity: 0; } to { transform: scale(1) translateY(0) rotateX(0deg); opacity: 1; } } /* Modal header */ .drag-puzzle-modal-header { display: flex; justify-content: space-between; align-items: center; padding: clamp(1.5em, 3vw, 2.5em) clamp(1.5em, 4vw, 2.5em) clamp(0.75em, 2vw, 1em); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; position: relative; overflow: hidden; } .drag-puzzle-modal-header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>'); opacity: 0.3; } .drag-puzzle-modal-header h3 { margin: 0; font-size: clamp(1.1em, 3vw, 1.4em); color: white; font-weight: 700; font-family: var(--font-family-secondary, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif); text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); z-index: 1; position: relative; display: flex; align-items: center; gap: 0.5em; line-height: 1.2; } .drag-puzzle-modal-header h3::before { content: '🛡️'; font-size: 1.2em; } .drag-puzzle-close-btn { background: rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.3); font-size: clamp(1.2em, 3vw, 1.5em); color: white; cursor: pointer; padding: 0; width: clamp(40px, 8vw, 45px); height: clamp(40px, 8vw, 45px); min-width: 44px; /* Touch target minimum */ min-height: 44px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); backdrop-filter: blur(10px); z-index: 1; position: relative; touch-action: manipulation; } .drag-puzzle-close-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.5); transform: scale(1.1); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); } .drag-puzzle-close-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .drag-puzzle-container { width: auto; margin: 0; padding: clamp(1.5em, 4vw, 2.5em); background: linear-gradient(145deg, #f8f9fa, #ffffff); font-family: var(--font-family-secondary, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif); position: relative; box-sizing: border-box; } .drag-puzzle-title { text-align: center; margin-bottom: 1.5em; font-size: 16px; color: #495057; font-weight: 600; line-height: 1.5; letter-spacing: 0.5px; } .drag-puzzle-loading { text-align: center; padding: 3em 2em; color: #6c757d; font-size: 16px; display: flex; flex-direction: column; align-items: center; gap: 1em; } .drag-puzzle-loading::before { content: ''; width: 40px; height: 40px; border: 4px solid #e9ecef; border-top: 4px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .drag-puzzle-game { position: relative; width: 100%; user-select: none; } .drag-puzzle-background { width: min(340px, calc(100vw - 4rem)); height: min(180px, calc((100vw - 4rem) * 0.529)); max-width: 100%; border-radius: clamp(8px, 2vw, 12px); margin: 0 auto clamp(1em, 3vw, 1.5em); background-size: cover; background-position: center; border: clamp(2px, 0.5vw, 3px) solid #e9ecef; position: relative; overflow: hidden; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.5); transition: all 0.3s ease; } .drag-puzzle-background:hover { box-shadow: 0 12px 35px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.5); } .drag-puzzle-hole { position: absolute; width: min(70px, calc((100vw - 4rem) * 0.206)); height: min(70px, calc((100vw - 4rem) * 0.206)); background: radial-gradient(circle at center, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.2) 70%, transparent 100%), linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%); border: clamp(2px, 0.5vw, 3px) dashed #adb5bd; border-radius: clamp(6px, 1.5vw, 8px); box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.4), inset 0 4px 8px rgba(0, 0, 0, 0.2); animation: pulse 2s ease-in-out infinite; } @keyframes pulse { 0%, 100% { border-color: #adb5bd; box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.4), inset 0 4px 8px rgba(0, 0, 0, 0.2); } 50% { border-color: #667eea; box-shadow: inset 0 0 25px rgba(0, 0, 0, 0.5), inset 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 15px rgba(102, 126, 234, 0.3); } } .drag-puzzle-piece { position: absolute; width: min(70px, calc((100vw - 4rem) * 0.206)); height: min(70px, calc((100vw - 4rem) * 0.206)); background-size: cover; background-position: center; border-radius: clamp(6px, 1.5vw, 8px); cursor: grab; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 10; border: clamp(1px, 0.3vw, 2px) solid rgba(255, 255, 255, 0.8); touch-action: none; } .drag-puzzle-piece:hover { transform: scale(1.05); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.15); cursor: grab; } .drag-puzzle-piece:active { cursor: grabbing; transform: scale(1.1); } .drag-puzzle-piece.verified { cursor: default; transform: scale(1); box-shadow: 0 0 25px rgba(76, 175, 80, 0.6), 0 0 50px rgba(76, 175, 80, 0.3), 0 4px 15px rgba(0, 0, 0, 0.2); border: 3px solid #4caf50; animation: success 0.6s ease-out; } @keyframes success { 0% { transform: scale(1.2); filter: brightness(1.3); } 50% { transform: scale(1.1); filter: brightness(1.2); } 100% { transform: scale(1); filter: brightness(1); } } .drag-puzzle-slider { width: min(340px, calc(100vw - 4rem)); max-width: 100%; margin: 0 auto; } .drag-puzzle-track { position: relative; width: 100%; height: clamp(50px, 12vw, 54px); background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 50%, #f8f9fa 100%); border-radius: clamp(22px, 6vw, 25px); border: clamp(1px, 0.3vw, 2px) solid #dee2e6; margin-bottom: clamp(0.75em, 2vw, 1em); overflow: hidden; box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05); } .drag-puzzle-track::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(90deg, rgba(102, 126, 234, 0.1) 0%, rgba(102, 126, 234, 0.05) 50%, rgba(102, 126, 234, 0.1) 100%); opacity: 0; transition: opacity 0.3s ease; } .drag-puzzle-track:hover::before { opacity: 1; } .drag-puzzle-slider-button { position: absolute; top: clamp(2px, 0.5vw, 3px); width: clamp(42px, 10vw, 44px); height: clamp(42px, 10vw, 44px); min-width: 44px; /* Touch target minimum */ min-height: 44px; background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); border: clamp(2px, 0.5vw, 3px) solid #dee2e6; border-radius: 50%; cursor: grab; display: flex; align-items: center; justify-content: center; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8); position: relative; overflow: hidden; touch-action: manipulation; } .drag-puzzle-slider-button::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.4) 50%, transparent 100%); transition: left 0.5s ease; } .drag-puzzle-slider-button:hover::before { left: 100%; } .drag-puzzle-slider-button:hover { background: linear-gradient(135deg, #ffffff 0%, #f1f3f4 100%); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.9); transform: scale(1.05); } .drag-puzzle-slider-button.dragging { cursor: grabbing; transform: scale(1.1); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.9); border-color: #667eea; } .drag-puzzle-slider-button.verified { background: linear-gradient(135deg, #4caf50 0%, #45a049 100%); border-color: #4caf50; color: white; cursor: default; transform: scale(1); box-shadow: 0 0 20px rgba(76, 175, 80, 0.4), 0 4px 15px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.3); animation: verifiedPulse 2s ease-in-out infinite; } @keyframes verifiedPulse { 0%, 100% { box-shadow: 0 0 20px rgba(76, 175, 80, 0.4), 0 4px 15px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.3); } 50% { box-shadow: 0 0 30px rgba(76, 175, 80, 0.6), 0 4px 15px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.3); } } .drag-puzzle-slider-icon { font-size: 18px; font-weight: bold; color: #6c757d; transition: all 0.3s ease; z-index: 1; position: relative; } .drag-puzzle-slider-button:hover .drag-puzzle-slider-icon { color: #495057; } .drag-puzzle-slider-button.verified .drag-puzzle-slider-icon { color: white; animation: checkmark 0.6s ease-out; } @keyframes checkmark { 0% { transform: scale(0); opacity: 0; } 50% { transform: scale(1.2); } 100% { transform: scale(1); opacity: 1; } } .drag-puzzle-text { text-align: center; font-size: 14px; color: #6c757d; margin-bottom: 1.5em; font-weight: 500; letter-spacing: 0.3px; transition: all 0.3s ease; } .drag-puzzle-attempts { text-align: center; font-size: 13px; color: #dc3545; margin-bottom: 0; font-weight: 600; padding: 0.5em 1em; background: linear-gradient(135deg, rgba(220, 53, 69, 0.1), rgba(220, 53, 69, 0.05)); border-radius: 25px; border: 1px solid rgba(220, 53, 69, 0.2); display: inline-block; animation: shake 0.5s ease-in-out; } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } .drag-puzzle-actions { text-align: center; margin-top: 0; } .drag-puzzle-attempts-container { display: flex; flex-direction: column; align-items: center; gap: 0.75em; margin-top: 0.75em; } .drag-puzzle-refresh { background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%); border: 2px solid transparent; border-radius: 25px; padding: 0.75em 1.5em; font-size: 13px; color: white; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; position: relative; overflow: hidden; box-shadow: 0 4px 15px rgba(108, 117, 125, 0.3); } .drag-puzzle-refresh::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.2) 50%, transparent 100%); transition: left 0.5s ease; } .drag-puzzle-refresh:hover:not(:disabled) { background: linear-gradient(135deg, #5a6268 0%, #495057 100%); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(108, 117, 125, 0.4); } .drag-puzzle-refresh:hover:not(:disabled)::before { left: 100%; } .drag-puzzle-refresh:active:not(:disabled) { transform: translateY(0); box-shadow: 0 2px 10px rgba(108, 117, 125, 0.4); } .drag-puzzle-refresh:disabled { opacity: 0.6; cursor: not-allowed; transform: none; background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); color: #6c757d; box-shadow: none; } /* Enhanced Mobile and Tablet Responsiveness */ /* Tablets and small laptops */ @media (max-width: 1024px) { .drag-puzzle-modal { max-width: 85vw; } } /* Large tablets and small screens */ @media (max-width: 768px) { .drag-puzzle-modal-overlay { padding: 0.75rem; } .drag-puzzle-modal { max-width: calc(100vw - 1.5rem); border-radius: 16px; } .drag-puzzle-modal-header { padding: 1.25em 1.5em 0.75em; } .drag-puzzle-modal-header h3 { font-size: 1.2em; } .drag-puzzle-modal-header h3::before { font-size: 1em; } .drag-puzzle-close-btn { width: 42px; height: 42px; min-width: 44px; min-height: 44px; font-size: 1.3em; } .drag-puzzle-container { padding: 2em 1.5em 1.5em; } .drag-puzzle-background { width: min(300px, calc(100vw - 3rem)); height: min(158px, calc((100vw - 3rem) * 0.527)); } .drag-puzzle-hole, .drag-puzzle-piece { width: min(60px, calc((100vw - 3rem) * 0.2)); height: min(60px, calc((100vw - 3rem) * 0.2)); } .drag-puzzle-slider { width: min(300px, calc(100vw - 3rem)); } .drag-puzzle-track { height: 48px; } .drag-puzzle-slider-button { width: 40px; height: 40px; top: 2px; border-radius: 20px; } .drag-puzzle-title { font-size: 15px; margin-bottom: 1.25em; } .drag-puzzle-text { font-size: 13px; margin-bottom: 1.25em; } .drag-puzzle-refresh { padding: 0.7em 1.3em; font-size: 12px; } .drag-puzzle-attempts { font-size: 12px; padding: 0.3em 0.7em; margin-bottom: 0; } .drag-puzzle-actions { margin-top: 0; } .drag-puzzle-attempts-container { gap: 0.5em; margin-top: 0.5em; } .drag-puzzle-refresh { padding: 0.6em 1.2em; font-size: 12px; } } /* Mobile phones and small tablets */ @media (max-width: 480px) { .drag-puzzle-modal-overlay { padding: 0.5rem; align-items: flex-start; padding-top: 2rem; } .drag-puzzle-modal { max-width: calc(100vw - 1rem); border-radius: 14px; max-height: calc(100vh - 4rem); overflow-y: auto; } .drag-puzzle-modal-header { padding: 1em 1.25em 0.5em; } .drag-puzzle-modal-header h3 { font-size: 1.1em; } .drag-puzzle-close-btn { width: 38px; height: 38px; min-width: 44px; min-height: 44px; font-size: 1.2em; } .drag-puzzle-container { padding: 1.5em 1.25em 1.25em; } .drag-puzzle-background { width: min(280px, calc(100vw - 2.5rem)); height: min(148px, calc((100vw - 2.5rem) * 0.529)); } .drag-puzzle-hole, .drag-puzzle-piece { width: min(55px, calc((100vw - 2.5rem) * 0.196)); height: min(55px, calc((100vw - 2.5rem) * 0.196)); } .drag-puzzle-slider { width: min(280px, calc(100vw - 2.5rem)); } .drag-puzzle-track { height: 44px; } .drag-puzzle-slider-button { width: 36px; height: 36px; min-width: 44px; min-height: 44px; top: 2px; border-radius: 18px; } .drag-puzzle-slider-icon { font-size: 16px; } .drag-puzzle-title { font-size: 14px; margin-bottom: 1em; line-height: 1.4; } .drag-puzzle-text { font-size: 12px; margin-bottom: 1em; line-height: 1.4; } .drag-puzzle-refresh { padding: 0.6em 1.1em; font-size: 11px; } .drag-puzzle-attempts { font-size: 11px; padding: 0.3em 0.6em; margin-bottom: 0; } .drag-puzzle-actions { margin-top: 0; } .drag-puzzle-attempts-container { gap: 0.4em; margin-top: 0.4em; } .drag-puzzle-refresh { padding: 0.6em 1.1em; font-size: 11px; } /* Make attempts and refresh button more compact on very small screens */ .drag-puzzle-attempts + .drag-puzzle-actions { margin-top: 0; } } /* Very small mobile screens */ @media (max-width: 360px) { .drag-puzzle-modal-overlay { padding: 0.25rem; padding-top: 1rem; } .drag-puzzle-modal { max-width: calc(100vw - 0.5rem); } .drag-puzzle-container { padding: 1.25em 1em 1em; } .drag-puzzle-background { width: min(260px, calc(100vw - 2rem)); height: min(137px, calc((100vw - 2rem) * 0.527)); } .drag-puzzle-hole, .drag-puzzle-piece { width: min(50px, calc((100vw - 2rem) * 0.192)); height: min(50px, calc((100vw - 2rem) * 0.192)); } .drag-puzzle-slider { width: min(260px, calc(100vw - 2rem)); } .drag-puzzle-track { height: 42px; } .drag-puzzle-slider-button { width: 34px; height: 34px; min-width: 44px; min-height: 44px; top: 2px; } /* Compact layout for attempts and refresh button */ .drag-puzzle-attempts { font-size: 10px; padding: 0.25em 0.5em; margin-bottom: 0; border-radius: 15px; } .drag-puzzle-actions { margin-top: 0; } .drag-puzzle-attempts-container { gap: 0.3em; margin-top: 0.3em; } .drag-puzzle-refresh { padding: 0.5em 0.9em; font-size: 10px; border-radius: 15px; } /* Alternative: Side-by-side layout for very tight spaces */ .drag-puzzle-attempts-container { display: flex; flex-direction: column; align-items: center; gap: 0.3em; } /* Compact layout when both attempts and refresh are visible */ @media (max-height: 600px), (max-width: 360px) { .drag-puzzle-attempts-container { flex-direction: row; justify-content: center; gap: 0.5em; flex-wrap: wrap; } .drag-puzzle-attempts { margin-bottom: 0; flex-shrink: 0; } .drag-puzzle-actions { margin-top: 0; flex-shrink: 0; } .drag-puzzle-refresh { padding: 0.4em 0.7em; font-size: 9px; } } } /* Landscape orientation for mobile devices */ @media (max-height: 600px) and (orientation: landscape) { .drag-puzzle-modal-overlay { align-items: flex-start; padding-top: 1rem; overflow-y: auto; } .drag-puzzle-modal { max-height: calc(100vh - 2rem); margin: 0 auto; } .drag-puzzle-modal-header { padding: 0.75em 1.5em 0.5em; } .drag-puzzle-container { padding: 1.25em 1.5em 1em; } .drag-puzzle-title { margin-bottom: 0.75em; } .drag-puzzle-background { margin-bottom: 1em; } .drag-puzzle-text { margin-bottom: 0.75em; } } /* Touch device optimizations */ @media (pointer: coarse) { .drag-puzzle-piece { cursor: pointer; } .drag-puzzle-piece:hover { transform: none; } .drag-puzzle-slider-button { cursor: pointer; } .drag-puzzle-slider-button:hover { transform: none; } /* Ensure touch targets are at least 44px */ .drag-puzzle-close-btn, .drag-puzzle-refresh, .drag-puzzle-slider-button { min-width: 44px; min-height: 44px; } } /* High DPI screens */ @media (min-resolution: 2dppx) { .drag-puzzle-background, .drag-puzzle-piece { image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; } } /* Success animation for the entire modal */ .drag-puzzle-modal.success-animation { animation: successBounce 0.8s ease-out; } @keyframes successBounce { 0% { transform: scale(1); } 25% { transform: scale(1.02); } 50% { transform: scale(0.98); } 75% { transform: scale(1.01); } 100% { transform: scale(1); } } /* Enhanced focus states for accessibility */ .drag-puzzle-close-btn:focus, .drag-puzzle-refresh:focus, .drag-puzzle-slider-button:focus { outline: 3px solid rgba(102, 126, 234, 0.5); outline-offset: 2px; } /* Improved contrast for text */ @media (prefers-contrast: high) { .drag-puzzle-title, .drag-puzzle-text { color: #212529; font-weight: 600; } .drag-puzzle-hole { border-color: #000; border-width: 2px; } }