689 lines
19 KiB
HTML
689 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Tell us about you</title>
|
|
<style>
|
|
/* ===== Apple Reset & Base ===== */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--blue: #0071e3;
|
|
--blue-hover: #0077ed;
|
|
--link: #0066cc;
|
|
--fg: #1d1d1f;
|
|
--fg-secondary: rgba(0,0,0,0.8);
|
|
--fg-tertiary: rgba(0,0,0,0.48);
|
|
--bg-light: #f5f5f7;
|
|
--bg-dark: #000000;
|
|
--white: #ffffff;
|
|
--shadow: rgba(0,0,0,0.22) 3px 5px 30px 0px;
|
|
--nav-bg: rgba(0,0,0,0.8);
|
|
}
|
|
|
|
html { height: 100%; }
|
|
|
|
body {
|
|
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
line-height: 1.47;
|
|
letter-spacing: -0.022px;
|
|
color: var(--fg);
|
|
background: var(--white);
|
|
min-height: 100%;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* ===== Apple Glass Nav ===== */
|
|
.nav {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
height: 48px;
|
|
background: var(--nav-bg);
|
|
backdrop-filter: saturate(180%) blur(20px);
|
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.nav-brand {
|
|
color: var(--white);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
letter-spacing: -0.016px;
|
|
opacity: 0.92;
|
|
}
|
|
|
|
/* ===== Main Section ===== */
|
|
.section {
|
|
background: var(--bg-light);
|
|
min-height: calc(100vh - 48px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px 24px 80px;
|
|
}
|
|
|
|
.form-container {
|
|
width: 100%;
|
|
max-width: 500px;
|
|
}
|
|
|
|
/* ===== Typography ===== */
|
|
.heading {
|
|
font-size: 40px;
|
|
font-weight: 600;
|
|
line-height: 1.10;
|
|
letter-spacing: normal;
|
|
color: var(--fg);
|
|
text-align: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.caption {
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
line-height: 1.47;
|
|
letter-spacing: -0.022px;
|
|
color: var(--fg-secondary);
|
|
text-align: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
/* ===== Step Indicator ===== */
|
|
.step-track {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.step-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--fg-tertiary);
|
|
transition: background 0.35s cubic-bezier(0.4, 0, 0.2, 1), transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.step-dot.active {
|
|
background: var(--blue);
|
|
transform: scale(1.5);
|
|
}
|
|
|
|
.step-dot.done {
|
|
background: #34c759;
|
|
}
|
|
|
|
/* ===== Step Counter ===== */
|
|
.step-label {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
letter-spacing: -0.01px;
|
|
color: var(--fg-tertiary);
|
|
text-align: center;
|
|
margin-bottom: 28px;
|
|
transition: opacity 0.25s ease;
|
|
}
|
|
|
|
.step-label.pulse {
|
|
animation: labelPulse 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
@keyframes labelPulse {
|
|
0% { opacity: 0.4; transform: translateY(-4px); }
|
|
100% { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* ===== Form Steps ===== */
|
|
.step {
|
|
display: none;
|
|
}
|
|
|
|
.step.current {
|
|
display: block;
|
|
}
|
|
|
|
.step.animate-forward {
|
|
animation: slideInLeft 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.step.animate-back {
|
|
animation: slideInRight 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
@keyframes slideInLeft {
|
|
from { opacity: 0; transform: translateX(28px); }
|
|
to { opacity: 1; transform: translateX(0); }
|
|
}
|
|
|
|
@keyframes slideInRight {
|
|
from { opacity: 0; transform: translateX(-28px); }
|
|
to { opacity: 1; transform: translateX(0); }
|
|
}
|
|
|
|
.field-wrapper {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
background: var(--white);
|
|
border-radius: 10px;
|
|
border: 1.5px solid transparent;
|
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
|
|
.field-wrapper:focus-within {
|
|
border-color: var(--blue);
|
|
box-shadow: 0 0 0 3px rgba(0,113,227,0.18);
|
|
}
|
|
|
|
.field-icon {
|
|
position: absolute;
|
|
left: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 18px;
|
|
color: var(--fg-tertiary);
|
|
pointer-events: none;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.field-wrapper:focus-within .field-icon {
|
|
color: var(--blue);
|
|
}
|
|
|
|
.field-input {
|
|
width: 100%;
|
|
padding: 14px 14px 14px 42px;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
line-height: 1.47;
|
|
letter-spacing: -0.022px;
|
|
color: var(--fg);
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 10px;
|
|
outline: none;
|
|
}
|
|
|
|
.field-input::placeholder {
|
|
color: var(--fg-tertiary);
|
|
}
|
|
|
|
.field-error {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.01px;
|
|
color: #e03131;
|
|
margin-top: 6px;
|
|
margin-left: 14px;
|
|
opacity: 0;
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
transition: opacity 0.2s, max-height 0.2s;
|
|
}
|
|
|
|
.field-error.shown {
|
|
opacity: 1;
|
|
max-height: 24px;
|
|
}
|
|
|
|
.field-wrapper.error {
|
|
border-color: #e03131;
|
|
}
|
|
|
|
/* ===== Buttons Row ===== */
|
|
.btn-row {
|
|
margin-top: 28px;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn {
|
|
flex: 1;
|
|
padding: 14px 24px;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
line-height: 1.47;
|
|
letter-spacing: -0.022px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
|
|
.btn-primary {
|
|
color: var(--white);
|
|
background: var(--blue);
|
|
border: none;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--blue-hover);
|
|
box-shadow: 0 2px 14px rgba(0,113,227,0.35);
|
|
}
|
|
|
|
.btn-secondary {
|
|
color: var(--blue);
|
|
background: transparent;
|
|
border: 1.5px solid var(--blue);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--blue);
|
|
color: var(--white);
|
|
}
|
|
|
|
.btn:focus-visible {
|
|
outline: 2px solid var(--blue);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* ===== Success State (inline) ===== */
|
|
.success-card {
|
|
display: none;
|
|
text-align: center;
|
|
padding: 32px 0;
|
|
}
|
|
|
|
.success-card.visible {
|
|
display: block;
|
|
animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(12px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.checkmark-wrap {
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 0 auto 16px;
|
|
}
|
|
|
|
.checkmark-circle {
|
|
fill: none;
|
|
stroke: #34c759;
|
|
stroke-width: 2.5;
|
|
stroke-dasharray: 166;
|
|
stroke-dashoffset: 166;
|
|
}
|
|
|
|
.success-card.visible .checkmark-circle {
|
|
animation: circleDraw 0.45s 0.05s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
}
|
|
|
|
.checkmark-tick {
|
|
fill: none;
|
|
stroke: #34c759;
|
|
stroke-width: 2.5;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
stroke-dasharray: 48;
|
|
stroke-dashoffset: 48;
|
|
}
|
|
|
|
.success-card.visible .checkmark-tick {
|
|
animation: tickDraw 0.3s 0.35s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
}
|
|
|
|
@keyframes circleDraw {
|
|
to { stroke-dashoffset: 0; }
|
|
}
|
|
|
|
@keyframes tickDraw {
|
|
to { stroke-dashoffset: 0; }
|
|
}
|
|
|
|
.success-title {
|
|
font-size: 28px;
|
|
font-weight: 600;
|
|
line-height: 1.14;
|
|
letter-spacing: 0.007px;
|
|
color: var(--fg);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.success-sub {
|
|
font-size: 15px;
|
|
font-weight: 400;
|
|
line-height: 1.47;
|
|
letter-spacing: -0.02px;
|
|
color: var(--fg-secondary);
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.btn-restart {
|
|
display: inline-block;
|
|
padding: 10px 36px;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.022px;
|
|
color: var(--blue);
|
|
background: transparent;
|
|
border: 1.5px solid var(--blue);
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, color 0.15s ease;
|
|
}
|
|
|
|
.btn-restart:hover {
|
|
background: var(--blue);
|
|
color: var(--white);
|
|
}
|
|
|
|
/* ===== Footer ===== */
|
|
.footer {
|
|
text-align: center;
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.01px;
|
|
color: var(--fg-tertiary);
|
|
margin-top: 32px;
|
|
}
|
|
|
|
/* ===== Responsive ===== */
|
|
@media (max-width: 480px) {
|
|
.heading { font-size: 32px; }
|
|
.caption { font-size: 15px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Apple Glass Nav -->
|
|
<nav class="nav">
|
|
<span class="nav-brand">Form</span>
|
|
</nav>
|
|
|
|
<!-- Form Section -->
|
|
<section class="section">
|
|
<div class="form-container">
|
|
<h1 class="heading">Tell us about you</h1>
|
|
<p class="caption">Three quick things and we're ready.</p>
|
|
|
|
<!-- Step Dots -->
|
|
<div class="step-track" id="stepTrack">
|
|
<div class="step-dot" data-step="1"></div>
|
|
<div class="step-dot" data-step="2"></div>
|
|
<div class="step-dot" data-step="3"></div>
|
|
</div>
|
|
|
|
<!-- Step Label -->
|
|
<div class="step-label" id="stepLabel">Step 1 of 3</div>
|
|
|
|
<!-- Step 1: Email -->
|
|
<div class="step current" data-step="1" id="step1">
|
|
<div class="field-wrapper" id="emailWrapper">
|
|
<span class="field-icon">✉</span>
|
|
<input class="field-input" type="email" id="email" placeholder="Email address" autocomplete="email">
|
|
</div>
|
|
<div class="field-error" id="emailError">Please enter a valid email.</div>
|
|
<div class="btn-row">
|
|
<button type="button" class="btn btn-primary" id="next1">Next</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Name -->
|
|
<div class="step" data-step="2" id="step2">
|
|
<div class="field-wrapper" id="nameWrapper">
|
|
<span class="field-icon">👤</span>
|
|
<input class="field-input" type="text" id="name" placeholder="Your name" autocomplete="name">
|
|
</div>
|
|
<div class="field-error" id="nameError">Don't be a stranger — what's your name?</div>
|
|
<div class="btn-row">
|
|
<button type="button" class="btn btn-secondary" id="back2">Back</button>
|
|
<button type="button" class="btn btn-primary" id="next2">Next</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Domain -->
|
|
<div class="step" data-step="3" id="step3">
|
|
<div class="field-wrapper" id="domainWrapper">
|
|
<span class="field-icon">🌐</span>
|
|
<input class="field-input" type="text" id="domain" placeholder="Domain name" autocomplete="url">
|
|
</div>
|
|
<div class="field-error" id="domainError">Please enter your domain name.</div>
|
|
<div class="btn-row">
|
|
<button type="button" class="btn btn-secondary" id="back3">Back</button>
|
|
<button type="button" class="btn btn-primary" id="submitBtn">Submit</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success State -->
|
|
<div class="success-card" id="successCard">
|
|
<div class="checkmark-wrap">
|
|
<svg viewBox="0 0 52 52" width="64" height="64">
|
|
<circle class="checkmark-circle" cx="26" cy="26" r="24"/>
|
|
<path class="checkmark-tick" d="M16 27l7 7 13-13"/>
|
|
</svg>
|
|
</div>
|
|
<h2 class="success-title">Success!</h2>
|
|
<p class="success-sub">We'll be in touch.</p>
|
|
<button class="btn-restart" id="restartBtn">Start over</button>
|
|
</div>
|
|
|
|
<p class="footer">Your info stays with us. Always.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
(function() {
|
|
const totalSteps = 3;
|
|
let currentStep = 1;
|
|
|
|
const steps = Array.from(document.querySelectorAll('.step'));
|
|
const dots = Array.from(document.querySelectorAll('.step-dot'));
|
|
const label = document.getElementById('stepLabel');
|
|
const success = document.getElementById('successCard');
|
|
const heading = document.querySelector('.heading');
|
|
const caption = document.querySelector('.caption');
|
|
const track = document.getElementById('stepTrack');
|
|
|
|
const fields = {
|
|
email: { input: document.getElementById('email'), wrapper: document.getElementById('emailWrapper'), error: document.getElementById('emailError') },
|
|
name: { input: document.getElementById('name'), wrapper: document.getElementById('nameWrapper'), error: document.getElementById('nameError') },
|
|
domain: { input: document.getElementById('domain'), wrapper: document.getElementById('domainWrapper'), error: document.getElementById('domainError') }
|
|
};
|
|
|
|
const API_URL = ''; // relative — same origin
|
|
|
|
function validateEmail(v) {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim());
|
|
}
|
|
|
|
function clearErrors() {
|
|
Object.values(fields).forEach(f => {
|
|
f.wrapper.classList.remove('error');
|
|
f.error.classList.remove('shown');
|
|
});
|
|
}
|
|
|
|
function showStep(n, dir) {
|
|
const prevEl = document.getElementById('step' + currentStep);
|
|
|
|
steps.forEach(s => {
|
|
s.classList.remove('current', 'animate-forward', 'animate-back');
|
|
});
|
|
|
|
const stepEl = document.getElementById('step' + n);
|
|
if (stepEl) {
|
|
stepEl.classList.add('current');
|
|
if (dir) stepEl.classList.add(dir);
|
|
}
|
|
|
|
dots.forEach((d, i) => {
|
|
d.classList.remove('active', 'done');
|
|
if (i + 1 < n) d.classList.add('done');
|
|
if (i + 1 === n) d.classList.add('active');
|
|
});
|
|
|
|
// Pulse the step label
|
|
label.classList.remove('pulse');
|
|
void label.offsetWidth; // force reflow
|
|
label.textContent = 'Step ' + n + ' of ' + totalSteps;
|
|
label.classList.add('pulse');
|
|
|
|
// Focus the input in the new step
|
|
const input = stepEl ? stepEl.querySelector('input') : null;
|
|
if (input) setTimeout(() => input.focus(), 100);
|
|
|
|
currentStep = n;
|
|
}
|
|
|
|
function validateStep(n) {
|
|
clearErrors();
|
|
if (n === 1) {
|
|
const v = fields.email.input.value.trim();
|
|
if (!v || !validateEmail(v)) {
|
|
fields.email.wrapper.classList.add('error');
|
|
fields.email.error.classList.add('shown');
|
|
return false;
|
|
}
|
|
}
|
|
if (n === 2) {
|
|
const v = fields.name.input.value.trim();
|
|
if (!v) {
|
|
fields.name.wrapper.classList.add('error');
|
|
fields.name.error.classList.add('shown');
|
|
return false;
|
|
}
|
|
}
|
|
if (n === 3) {
|
|
const v = fields.domain.input.value.trim();
|
|
if (!v) {
|
|
fields.domain.wrapper.classList.add('error');
|
|
fields.domain.error.classList.add('shown');
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function showSuccess() {
|
|
// Reset SVG strokes for replay
|
|
const circle = success.querySelector('.checkmark-circle');
|
|
const tick = success.querySelector('.checkmark-tick');
|
|
if (circle) {
|
|
circle.style.animation = 'none';
|
|
circle.offsetHeight;
|
|
circle.style.animation = '';
|
|
}
|
|
if (tick) {
|
|
tick.style.animation = 'none';
|
|
tick.offsetHeight;
|
|
tick.style.animation = '';
|
|
}
|
|
|
|
steps.forEach(s => s.classList.remove('current'));
|
|
heading.style.display = 'none';
|
|
caption.style.display = 'none';
|
|
track.style.display = 'none';
|
|
label.style.display = 'none';
|
|
success.classList.remove('visible');
|
|
void success.offsetWidth; // force reflow to retrigger animation
|
|
success.classList.add('visible');
|
|
}
|
|
|
|
async function submitForm() {
|
|
const payload = {
|
|
email: fields.email.input.value.trim(),
|
|
name: fields.name.input.value.trim(),
|
|
domain: fields.domain.input.value.trim()
|
|
};
|
|
|
|
try {
|
|
const resp = await fetch(API_URL + '/api/submit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
if (resp.ok) {
|
|
const data = await resp.json();
|
|
console.log('Submitted:', data);
|
|
showSuccess();
|
|
} else {
|
|
const err = await resp.json().catch(() => ({}));
|
|
alert('Error: ' + (err.error || err.detail || 'Submission failed. Please try again.'));
|
|
}
|
|
} catch (e) {
|
|
console.error('Fetch error:', e);
|
|
alert('Could not reach the server. Please check your connection.');
|
|
}
|
|
}
|
|
|
|
function resetForm() {
|
|
success.classList.remove('visible');
|
|
heading.style.display = '';
|
|
caption.style.display = '';
|
|
track.style.display = '';
|
|
label.style.display = '';
|
|
Object.values(fields).forEach(f => f.input.value = '');
|
|
clearErrors();
|
|
currentStep = 1;
|
|
showStep(1);
|
|
}
|
|
|
|
// Clear error on input for all fields
|
|
Object.values(fields).forEach(f => {
|
|
f.input.addEventListener('input', function() {
|
|
f.wrapper.classList.remove('error');
|
|
f.error.classList.remove('shown');
|
|
});
|
|
});
|
|
|
|
// Allow Enter key to advance
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter' && currentStep <= totalSteps) {
|
|
e.preventDefault();
|
|
if (validateStep(currentStep)) {
|
|
if (currentStep < totalSteps) {
|
|
showStep(currentStep + 1, 'animate-forward');
|
|
} else {
|
|
submitForm();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Step 1: Next
|
|
document.getElementById('next1').addEventListener('click', function() {
|
|
if (validateStep(1)) showStep(2, 'animate-forward');
|
|
});
|
|
|
|
// Step 2: Back / Next
|
|
document.getElementById('back2').addEventListener('click', function() {
|
|
showStep(1, 'animate-back');
|
|
});
|
|
document.getElementById('next2').addEventListener('click', function() {
|
|
if (validateStep(2)) showStep(3, 'animate-forward');
|
|
});
|
|
|
|
// Step 3: Back / Submit
|
|
document.getElementById('back3').addEventListener('click', function() {
|
|
showStep(2, 'animate-back');
|
|
});
|
|
document.getElementById('submitBtn').addEventListener('click', function() {
|
|
if (validateStep(3)) submitForm();
|
|
});
|
|
|
|
// Restart
|
|
document.getElementById('restartBtn').addEventListener('click', resetForm);
|
|
|
|
// Start
|
|
showStep(1);
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|