Copy Create a dual email-change verification flow with Tailwind CSS. Show old and new email addresses as two numbered steps, each with a six-digit code input, verification badge, and resend control. A progress line should connect the steps and the final Update email button should remain disabled until both codes are verified. Add JavaScript that formats numeric codes, verifies the demo codes 142857 and 620319 independently, updates the step states, runs a 20-second resend countdown per address, and announces when the email change is ready to submit. <div class="mx-auto max-w-lg p-6">
<section class="rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm">
<h1 class="text-lg font-semibold text-neutral-900">Confirm your new email</h1>
<p class="mt-1 text-sm text-neutral-500">For security, verify both addresses before we make the change.</p>
<div class="relative mt-7 space-y-6 before:absolute before:bottom-8 before:left-4 before:top-8 before:w-px before:bg-neutral-200">
<div data-email-step data-code="142857" class="relative flex gap-4">
<span data-number class="z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-neutral-900 text-xs font-semibold text-white">1</span>
<div class="flex-1"><div class="flex items-center justify-between gap-3"><div><p class="text-sm font-medium text-neutral-800">Current address</p><p class="text-xs text-neutral-400">[email protected] </p></div><span data-badge class="hidden rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700">Verified</span></div><div data-fields class="mt-3 flex gap-2"><input data-code-input inputmode="numeric" maxlength="6" placeholder="6-digit code" class="min-w-0 flex-1 rounded-xl border border-neutral-200 px-3 py-2.5 font-mono text-sm tracking-widest outline-none focus:border-neutral-900" /><button data-verify class="rounded-xl border border-neutral-200 px-3 text-sm font-medium text-neutral-700">Verify</button></div><button data-resend class="mt-2 text-xs text-neutral-400 hover:text-neutral-700">Resend code</button><p data-message class="mt-2 hidden text-xs text-red-600" role="alert"></p></div>
</div>
<div data-email-step data-code="620319" class="relative flex gap-4">
<span data-number class="z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-neutral-200 text-xs font-semibold text-neutral-600">2</span>
<div class="flex-1"><div class="flex items-center justify-between gap-3"><div><p class="text-sm font-medium text-neutral-800">New address</p><p class="text-xs text-neutral-400">[email protected] </p></div><span data-badge class="hidden rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700">Verified</span></div><div data-fields class="mt-3 flex gap-2"><input data-code-input inputmode="numeric" maxlength="6" placeholder="6-digit code" class="min-w-0 flex-1 rounded-xl border border-neutral-200 px-3 py-2.5 font-mono text-sm tracking-widest outline-none focus:border-neutral-900" /><button data-verify class="rounded-xl border border-neutral-200 px-3 text-sm font-medium text-neutral-700">Verify</button></div><button data-resend class="mt-2 text-xs text-neutral-400 hover:text-neutral-700">Resend code</button><p data-message class="mt-2 hidden text-xs text-red-600" role="alert"></p></div>
</div>
</div>
<button id="emailCommit" disabled class="mt-7 w-full rounded-xl bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white disabled:cursor-not-allowed disabled:opacity-40">Update email address</button>
<p id="emailReady" aria-live="polite" class="mt-3 text-center text-xs text-neutral-400">Waiting for both verifications</p>
</section>
</div> var steps = [].slice.call(document.querySelectorAll("[data-email-step]"));
var commit = document.getElementById("emailCommit");
var ready = document.getElementById("emailReady");
function sync() {
var complete = steps.every(function (step) { return step.getAttribute("data-verified") === "true"; });
commit.disabled = !complete;
ready.textContent = complete ? "Both addresses verified. Your change is ready." : "Waiting for both verifications";
ready.className = complete ? "mt-3 text-center text-xs text-green-700" : "mt-3 text-center text-xs text-neutral-400";
}
steps.forEach(function (step) {
var input = step.querySelector("[data-code-input]");
var message = step.querySelector("[data-message]");
var resend = step.querySelector("[data-resend]");
input.addEventListener("input", function () { input.value = input.value.replace(/D/g, "").slice(0, 6); });
step.querySelector("[data-verify]").addEventListener("click", function () {
if (input.value !== step.getAttribute("data-code")) {
message.textContent = "Code does not match. Check the latest email.";
message.classList.remove("hidden");
return;
}
step.setAttribute("data-verified", "true");
step.querySelector("[data-fields]").classList.add("hidden");
step.querySelector("[data-badge]").classList.remove("hidden");
step.querySelector("[data-number]").className = "z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-green-600 text-xs font-semibold text-white";
resend.classList.add("hidden");
message.classList.add("hidden");
sync();
});
resend.addEventListener("click", function () {
var seconds = 20;
resend.disabled = true;
resend.textContent = "Resend in " + seconds + "s";
var timer = setInterval(function () {
seconds -= 1;
resend.textContent = seconds ? "Resend in " + seconds + "s" : "Resend code";
if (!seconds) { clearInterval(timer); resend.disabled = false; }
}, 1000);
});
});
commit.addEventListener("click", function () { commit.textContent = "Email updated ✓"; commit.disabled = true; });
sync();