Files
apple-form/index.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>