index.html
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sign in with a passkey</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="card">
<div class="scanner">
<img src="fingerprint.svg" />
</div>
<h1 class="title">Sign in with a passkey</h1>
<p class="subtitle">Use your fingerprint or face - no password to remember.</p>
<button class="primary">🔑 Continue with passkey</button>
<a class="secondary" href="#">Use a password instead</a>
<p class="trust">🔒 End-to-end encrypted</p>
</main>
<script src="script.js" type="text/javascript"></script>
</body>
</html>
styles.css
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: "Plus Jakarta Sans", sans-serif;
background: radial-gradient(70% 50% at 50% 14%, rgba(249, 115, 22, 0.22), transparent 60%), #06070b;
}
.card {
width: 350px;
padding: 42px 34px;
text-align: center;
background: linear-gradient(180deg, #141823, #0b0e16);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 26px;
box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55);
}
.scanner {
position: relative;
width: 112px;
height: 112px;
margin: 0 auto 26px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 1.5px solid rgba(251, 146, 60, 0.45);
background: radial-gradient(circle, rgba(249, 115, 22, 0.22), transparent 70%);
box-shadow: 0 0 44px -6px rgba(249, 115, 22, 0.55);
}
.scanner img {
filter: drop-shadow(0 0 8px rgba(249, 115, 22, 0.6));
}
.title {
font-size: 24px;
font-weight: 800;
color: #f2f5fa;
}
.subtitle {
margin: 10px auto 28px;
max-width: 290px;
font-size: 14px;
line-height: 1.5;
color: #939cad;
}
.primary {
display: flex;
align-items: center;
justify-content: center;
gap: 9px;
width: 100%;
padding: 15px;
font-size: 15px;
font-weight: 700;
font-family: inherit;
color: #1c0a02;
background: linear-gradient(135deg, #fdba74, #f97316 55%, #ea580c);
border: none;
border-radius: 14px;
cursor: pointer;
transition: filter 0.3s;
}
.primary:hover {
filter: brightness(1.08);
}
.secondary {
display: inline-block;
margin-top: 18px;
font-size: 13.5px;
font-weight: 600;
color: #8b94a3;
text-decoration: none;
}
.trust {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
margin-top: 26px;
padding-top: 20px;
font-size: 12px;
color: #6f7888;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.scanner::after {
content: "";
position: absolute;
inset: -9px;
border-radius: 50%;
border: 1px solid rgba(249, 115, 22, 0.16);
}
script.js
// Passkey continue flow - simulates a successful WebAuthn ceremony.
const button = document.querySelector(".primary");
button.addEventListener("click", () => {
button.disabled = true;
button.textContent = "Verifying...";
// In a real app this is navigator.credentials.get({ publicKey }).
setTimeout(() => {
button.textContent = "Verified ✓";
button.style.background = "linear-gradient(135deg, #4ade80, #16a34a)";
button.style.color = "#03130a";
}, 1100);
});