-
OTP Verification Animation
HTML
CSS
JavaScript
Let's verify your number
Enter the 4-digit code sent to your phone.
It'll auto-verify once entered.
✔
✔
✔
✔
Didn't receive the code?
Resend
OTP verification
↻
*{box-sizing:border-box} html,body{margin:0;width:100%;height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif} body{ display:grid; place-items:center; min-height:100vh; background:#000; color:#fff; scale:0.7; } .phone{ width:min(92vw,575px); aspect-ratio:0.94; min-height:620px; max-height:92vh; padding:100px 55px 55px; position:relative; overflow:hidden; border-radius:54px; background:#121015; border:1px solid #29262d; box-shadow:0 0 0 1px rgba(255,255,255,.015) inset; } .notch{ position:absolute; top:21px;left:50%; transform:translateX(-50%); width:66px;height:6px; border-radius:10px; background:#555158; z-index: 10; } .content{text-align:center; position:relative; z-index: 2;} h1{ margin:4px 0 22px; font-size:35px; line-height:1.15; letter-spacing:-1.1px; font-weight:700; transition: opacity 0.3s; } .sub{ margin:0 auto; max-width:430px; color:#77747b; font-size:20px; line-height:1.5; font-weight:500; transition: opacity 0.3s; } .sub span{display:block} /* OTP area */ .otp-stage{ width:100%; height:235px; margin-top:75px; position:relative; } .otp-row{ position:absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 22px; } .box{ position:absolute; top:50%; transform:translateY(-50%); width:88px;height:88px; border-radius:23px; background:#151318; box-shadow:inset 0 0 0 3px #37343a; display:grid; place-items:center; z-index:3; overflow:hidden; transition: left 0.8s cubic-bezier(0.68,-0.15,0.2,1.25), top 0.8s cubic-bezier(0.68,-0.15,0.2,1.25), transform 0.8s cubic-bezier(0.68,-0.15,0.2,1.25), border-radius 0.4s, width 0.4s, height 0.4s, background 0.4s, box-shadow 0.4s, opacity 0.4s; } /* Input Fields styling */ .box .val { width: 100%; height: 100%; background: transparent; border: none; outline: none; color: #fff; text-align: center; font-size: 42px; font-weight: 500; font-family: inherit; position: relative; z-index: 3; caret-color: #ff654d; transition: all 0.5s cubic-bezier(0.68,-0.15,0.2,1.25); display: flex; align-items: center; justify-content: center; } /* Fix text centering inside input elements across browsers */ .box .val { padding-left: 4px; /* visually compensates for native caret width */ } /* Electric Border System */ .box::before { content: ""; position: absolute; top: 50%; left: 50%; width: 200%; height: 200%; background: conic-gradient(transparent 70%, #0ff, #ff654d); animation: spin 1s linear infinite; z-index: 1; opacity: 0; transition: opacity 0.2s; } .box::after { content: ""; position: absolute; inset: 3px; border-radius: 20px; background: #151318; z-index: 2; opacity: 0; } .box.electric { box-shadow: none; } .box.electric::before, .box.electric::after { opacity: 1; } @keyframes spin { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } .box .check { position: absolute; color: #fff; font-size: 55px; z-index: 4; opacity: 0; transform: scale(0); transition: all 0.5s cubic-bezier(0.68,-0.15,0.2,1.25); pointer-events: none; } /* Stage 1: Typing - Linear Layout (Centered explicitly) */ .stage-typing .b1 { left: calc(50% - 200px); } .stage-typing .b2 { left: calc(50% - 100px); } .stage-typing .b3 { left: calc(50% + 0px); } .stage-typing .b4 { left: calc(50% + 100px); } /* Connections for Square */ .connections{ position:absolute; inset:0; pointer-events:none; opacity:0; transition: opacity 0.6s 0.3s; } .line{ position:absolute; background:#37343a; border-radius: 4px; } .line.t { height:4px; width:130px; left:calc(50% - 65px); top:calc(50% - 65px); transform:translateY(-50%); } .line.b { height:4px; width:130px; left:calc(50% - 65px); top:calc(50% + 65px); transform:translateY(-50%); } .line.l { width:4px; height:130px; left:calc(50% - 65px); top:calc(50% - 65px); transform:translateX(-50%); } .line.r { width:4px; height:130px; left:calc(50% + 65px); top:calc(50% - 65px); transform:translateX(-50%); } /* Stage 2: Square 4-Corners Layout */ .stage-square .b1 { left: calc(50% - 65px); top: calc(50% - 65px); transform: translate(-50%, -50%); } .stage-square .b2 { left: calc(50% + 65px); top: calc(50% - 65px); transform: translate(-50%, -50%); } .stage-square .b3 { left: calc(50% - 65px); top: calc(50% + 65px); transform: translate(-50%, -50%); } .stage-square .b4 { left: calc(50% + 65px); top: calc(50% + 65px); transform: translate(-50%, -50%); } .stage-square .connections { opacity: 1; } /* Stage 3: Merged to Center */ .stage-merged .box { left: 50%; top: 50%; transform: translate(-50%, -50%); opacity: 0; box-shadow: inset 0 0 0 3px #37343a; } .stage-merged .b1 { opacity: 1; z-index: 10; } /* Stage 4: Success Message */ .stage-merged.success .b1 { width: 110px; height: 110px; border-radius: 50%; background: #25d366; box-shadow: inset 0 0 0 4px #25d366, 0 10px 30px rgba(37,211,102,0.3); } .stage-merged.success .b1 .val { opacity: 0; transform: scale(0); } .stage-merged.success .b1 .check { opacity: 1; transform: scale(1); } .resend{ margin-top:13px; color:#77747b; font-size:19px; font-weight:500; transition: opacity 0.3s; } .resend strong{color:#f1eef2;margin-left:8px;font-weight:700;cursor:pointer;} .status{ position:absolute; bottom:23px; left:0;right:0; text-align:center; color:#555158; font-size:12px; letter-spacing:1px; text-transform:uppercase; } /* replay button */ .replay{ position:absolute; right:22px; bottom:20px; width:40px;height:40px; border:1px solid #353139; border-radius:50%; background:#18151a; color:#aaa6ac; cursor:pointer; font-size:18px; transition:.2s; z-index: 50; } .replay:hover{color:#fff;border-color:#66616a;transform:rotate(-25deg)} /* Confetti */ .confetti { position: absolute; top: -20px; width: 8px; height: 16px; border-radius: 4px; z-index: 5; animation: fall linear forwards; } @keyframes fall { 0% { transform: translateY(0) rotate(0deg); opacity: 1; } 100% { transform: translateY(800px) rotate(720deg); opacity: 0; } } @media(max-width:600px){ .phone{ width:94vw; min-height:570px; padding:78px 24px 42px; border-radius:40px; } .notch{top:18px;width:58px} h1{font-size:27px;margin-bottom:17px} .sub{font-size:16px} .otp-stage{margin-top:55px;height:205px} .box{width:72px;height:72px;border-radius:19px;} .box .val {font-size:34px} .stage-typing .b1 { left: calc(50% - 146px); } .stage-typing .b2 { left: calc(50% - 73px); } .stage-typing .b3 { left: calc(50% + 0px); } .stage-typing .b4 { left: calc(50% + 73px); } .stage-square .b1 { left: calc(50% - 50px); top: calc(50% - 50px); } .stage-square .b2 { left: calc(50% + 50px); top: calc(50% - 50px); } .stage-square .b3 { left: calc(50% - 50px); top: calc(50% + 50px); } .stage-square .b4 { left: calc(50% + 50px); top: calc(50% + 50px); } .line.t, .line.b { width:100px; left:calc(50% - 50px); } .line.l, .line.r { height:100px; top:calc(50% - 50px); } .line.t { top:calc(50% - 50px); } .line.b { top:calc(50% + 50px); } .line.l { left:calc(50% - 50px); } .line.r { left:calc(50% + 50px); } .resend{font-size:16px} .stage-merged.success .b1 { width: 90px; height: 90px; } .stage-merged.success .b1 .check { font-size: 45px; } }
const stage = document.getElementById('stage'); const inputs = document.querySelectorAll('.val'); const boxes = document.querySelectorAll('.box'); const title = document.getElementById('title'); const subtitle = document.getElementById('subtitle'); const resend = document.getElementById('resend'); const replayBtn = document.getElementById('replay'); const delay = ms => new Promise(res => setTimeout(res, ms)); let isAnimating = false; // Handle Confetti function fireConfetti() { const colors = ['#ff654d', '#0ff', '#25d366', '#ffeb3b', '#b388ff']; for(let i = 0; i < 60; i++) { let c = document.createElement('div'); c.className = 'confetti'; c.style.left = (Math.random() * 100) + '%'; c.style.background = colors[Math.floor(Math.random() * colors.length)]; c.style.animationDuration = (Math.random() * 2 + 1.5) + 's'; c.style.animationDelay = (Math.random() * 0.4) + 's'; document.querySelector('.phone').appendChild(c); } } // Input Event Listeners inputs.forEach((input, index) => { // Add electric border on focus input.addEventListener('focus', () => { if(!isAnimating) input.parentElement.classList.add('electric'); }); // Remove electric border on blur input.addEventListener('blur', () => { input.parentElement.classList.remove('electric'); }); // Handle typing and auto-focus next input.addEventListener('input', (e) => { // Only allow numbers e.target.value = e.target.value.replace(/[^0-9]/g, ''); if (e.target.value !== '') { if (index < inputs.length - 1) { inputs[index + 1].focus(); } checkCompletion(); } }); // Handle backspace to focus previous input.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && e.target.value === '') { if (index > 0) { inputs[index - 1].focus(); } } }); }); // Check if all 4 inputs are filled function checkCompletion() { const code = Array.from(inputs).map(inp => inp.value).join(''); if (code.length === 4) { triggerSuccessAnimation(); } } // Trigger the visual animations async function triggerSuccessAnimation() { if (isAnimating) return; isAnimating = true; // Remove focus and disable inputs so user can't type during animation inputs.forEach(inp => { inp.blur(); inp.disabled = true; }); await delay(200); // Phase 2: Move to Square stage.className = 'otp-stage stage-square'; await delay(1200); // Phase 3: Merge stage.className = 'otp-stage stage-merged'; await delay(800); // Phase 4: Success & Confetti stage.classList.add('success'); title.style.opacity = 0; subtitle.style.opacity = 0; resend.style.opacity = 0; await delay(300); title.innerText = "Successfully Verified!"; subtitle.innerHTML = "
Your identity has been confirmed.
"; title.style.opacity = 1; subtitle.style.opacity = 1; fireConfetti(); } // Reset function bound to Replay button function resetOTP() { isAnimating = false; stage.className = 'otp-stage stage-typing'; title.style.opacity = 0; subtitle.style.opacity = 0; resend.style.opacity = 1; document.querySelectorAll('.confetti').forEach(c => c.remove()); inputs.forEach(inp => { inp.value = ''; inp.disabled = false; inp.parentElement.classList.remove('electric'); }); setTimeout(() => { title.innerText = "Let's verify your number"; subtitle.innerHTML = "
Enter the 4-digit code sent to your phone.
It'll auto-verify once entered.
"; title.style.opacity = 1; subtitle.style.opacity = 1; inputs[0].focus(); // Automatically focus the first input }, 300); } replayBtn.addEventListener('click', resetOTP); // Focus on first box initially window.onload = () => { inputs[0].focus(); };
Live Preview