feat: tier selector step with radio cards, white CTA buttons, 4-step form

This commit is contained in:
root
2026-06-20 17:44:32 +00:00
parent 38df4233e1
commit 17d660a49d
+101 -16
View File
@@ -30,9 +30,32 @@
.btn{display:inline-flex;align-items:center;gap:.5rem;padding:.7rem 1.5rem;border-radius:var(--radius-sm);font-weight:600;font-size:.9rem;text-decoration:none;cursor:pointer;transition:all .2s;border:none;font-family:inherit}
.btn-primary{background:var(--cta-bg);color:#fff;box-shadow:0 0 24px var(--accent-glow),var(--cta-shadow)}
.btn-primary:hover{background:linear-gradient(135deg,#4a8ff7,#7ab8fc);transform:translateY(-2px);box-shadow:0 0 32px rgba(59,130,246,0.35)}
/* White CTA button for Get a Quote / Get Pricing */
.btn-cta{background:#fff;color:#0a0a0b;border:1px solid rgba(255,255,255,.15);box-shadow:0 4px 20px rgba(0,0,0,.2),0 0 0 1px rgba(255,255,255,.1)}
.btn-cta:hover{background:#f0f0f5;transform:translateY(-2px);box-shadow:0 8px 30px rgba(0,0,0,.25),0 0 0 1px rgba(255,255,255,.2)}
.light .btn-cta{background:#1d1d1f;color:#fff;border-color:rgba(255,255,255,.1);box-shadow:0 4px 20px rgba(0,0,0,.15)}
.light .btn-cta:hover{background:#2d2d2f}
.btn-ghost{background:transparent;color:var(--text);border:1px solid var(--border)}
.btn-ghost:hover{border-color:var(--border-hover);background:var(--bg-card)}
.btn-sm{padding:.4rem 1rem;font-size:.8rem}
/* ── Tier Selector (Stripe-inspired radio cards) ── */
.tier-selector{display:grid;grid-template-columns:1fr 1fr;gap:.65rem;margin-bottom:1rem}
.tier-option{position:relative;background:var(--bg-card);border:1.5px solid var(--border);border-radius:10px;padding:1rem;cursor:pointer;transition:all .2s;user-select:none}
.tier-option:hover{border-color:var(--border-hover);background:var(--bg-card-hover)}
.tier-option.selected{border-color:var(--accent);background:rgba(59,130,246,.06);box-shadow:0 0 0 1px var(--accent)}
.tier-option input{position:absolute;opacity:0;pointer-events:none}
.tier-option .t-name{font-weight:700;font-size:1rem;margin-bottom:.15rem;display:flex;align-items:center;gap:.4rem}
.tier-option .t-name .check{width:18px;height:18px;border-radius:50%;border:2px solid var(--border);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .2s;font-size:10px;color:transparent}
.tier-option.selected .t-name .check{border-color:var(--accent);background:var(--accent);color:#fff}
.tier-option .t-tag{font-size:.72rem;color:var(--text-muted)}
.tier-option .t-specs{display:flex;gap:.4rem;flex-wrap:wrap;margin-top:.5rem}
.tier-option .t-specs span{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:rgba(255,255,255,.04);color:var(--text-dim)}
.tier-option.popular{border-color:rgba(59,130,246,.3)}
.tier-option.popular.selected{border-color:var(--accent)}
.tier-option.popular .t-name::after{content:'Best Value';font-size:.6rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:.1rem .4rem;border-radius:4px;background:rgba(59,130,246,.15);color:var(--accent);margin-left:auto}
.lang-fr .tier-option.popular .t-name::after{content:'Meilleur Q/P'}
.hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;padding:6rem 2rem 4rem;max-width:1200px;margin:0 auto}
.hero-grid{display:grid;grid-template-columns:1fr 1fr;gap:4rem;align-items:center}
.hero-badge{display:inline-flex;align-items:center;gap:.5rem;padding:.35rem .85rem;border-radius:100px;background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.2);font-size:.8rem;color:var(--accent);font-weight:500;margin-bottom:1.5rem;width:fit-content}
@@ -162,7 +185,7 @@
<a href="/check-order.html"><span class="en-only">Check Order</span><span class="fr-only">Suivi</span></a>
<a href="/backup-drives.html"><span class="en-only">Backup Drives</span><span class="fr-only">Disques Backup</span></a>
<button class="theme-btn" id="themeToggle" onclick="toggleTheme()" title="Toggle theme"></button>
<a href="#contact" class="btn btn-primary"><span class="en-only">Get a Quote</span><span class="fr-only">Demander un devis</span></a>
<a href="#contact" class="btn btn-cta"><span class="en-only">Get a Quote</span><span class="fr-only">Demander un devis</span></a>
</div>
</nav>
@@ -224,7 +247,7 @@
<li><span class="check"></span> <span class="en-only">On-site install</span><span class="fr-only">Installation sur place</span></li>
<li><span class="check"></span> <span class="en-only">Automated backups</span><span class="fr-only">Sauvegardes automatisées</span></li>
</ul>
<a href="#contact" class="btn btn-primary"><span class="en-only">Get Pricing</span><span class="fr-only">Voir le prix</span></a>
<a href="#contact" class="btn btn-cta"><span class="en-only">Get Pricing</span><span class="fr-only">Voir le prix</span></a>
</div>
<div class="tier-card">
<div class="tier-name">Atlas</div>
@@ -248,7 +271,7 @@
<li><span class="check"></span> <span class="en-only">On-site install</span><span class="fr-only">Installation sur place</span></li>
<li><span class="check"></span> <span class="en-only">Automated backups</span><span class="fr-only">Sauvegardes automatisées</span></li>
</ul>
<a href="#contact" class="btn btn-primary"><span class="en-only">Get Pricing</span><span class="fr-only">Voir le prix</span></a>
<a href="#contact" class="btn btn-cta"><span class="en-only">Get Pricing</span><span class="fr-only">Voir le prix</span></a>
</div>
</div>
</div>
@@ -343,8 +366,9 @@
<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 class="step-dot" data-step="4"></div>
</div>
<div class="step-label" id="stepLabel"><span class="en-only">Step 1 of 3</span><span class="fr-only">Étape 1 sur 3</span></div>
<div class="step-label" id="stepLabel"><span class="en-only">Step 1 of 4</span><span class="fr-only">Étape 1 sur 4</span></div>
<div class="form-step current" data-step="1" id="step1">
<div class="field-wrapper" id="emailWrapper">
@@ -358,11 +382,37 @@
</div>
<div class="form-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="Name" autocomplete="name">
<div class="step-label" style="font-size:.78rem;color:var(--text-dim);margin-bottom:1rem;text-align:center">
<span class="en-only">Choose your server tier</span>
<span class="fr-only">Choisissez votre offre</span>
</div>
<div class="field-error" id="nameError"><span class="en-only">Please enter your name.</span><span class="fr-only">Entrez votre nom.</span></div>
<div class="tier-selector">
<label class="tier-option" data-tier="rif" onclick="selectTier(this)">
<input type="radio" name="tier" value="rif">
<div class="t-name"><span class="check"></span>Rif</div>
<div class="t-tag"><span class="en-only">entry-level</span><span class="fr-only">entrée de gamme</span></div>
<div class="t-specs"><span>512 GB</span><span>Mini PC</span></div>
</label>
<label class="tier-option popular" data-tier="rifplus" onclick="selectTier(this)">
<input type="radio" name="tier" value="rif+">
<div class="t-name"><span class="check"></span>Rif<sup>+</sup></div>
<div class="t-tag"><span class="en-only">best value</span><span class="fr-only">meilleur rapport</span></div>
<div class="t-specs"><span>512+512 GB</span><span>Backup</span></div>
</label>
<label class="tier-option" data-tier="atlas" onclick="selectTier(this)">
<input type="radio" name="tier" value="atlas">
<div class="t-name"><span class="check"></span>Atlas</div>
<div class="t-tag"><span class="en-only">pro</span><span class="fr-only">pro</span></div>
<div class="t-specs"><span>1 TB</span><span>Mini PC</span></div>
</label>
<label class="tier-option popular" data-tier="atlasplus" onclick="selectTier(this)">
<input type="radio" name="tier" value="atlas+">
<div class="t-name"><span class="check"></span>Atlas<sup>+</sup></div>
<div class="t-tag"><span class="en-only">maximum</span><span class="fr-only">max</span></div>
<div class="t-specs"><span>1+1 TB</span><span>Backup</span></div>
</label>
</div>
<div class="field-error" id="tierError" style="text-align:center;margin-bottom:.5rem"><span class="en-only">Please select a tier.</span><span class="fr-only">Veuillez choisir une offre.</span></div>
<div class="btn-row">
<button type="button" class="btn btn-apple-secondary" id="back2"><span class="en-only">Back</span><span class="fr-only">Retour</span></button>
<button type="button" class="btn btn-primary" id="next2"><span class="en-only">Next</span><span class="fr-only">Suivant</span></button>
@@ -370,13 +420,25 @@
</div>
<div class="form-step" data-step="3" id="step3">
<div class="field-wrapper" id="nameWrapper">
<span class="field-icon">👤</span>
<input class="field-input" type="text" id="name" placeholder="Name" autocomplete="name">
</div>
<div class="field-error" id="nameError"><span class="en-only">Please enter your name.</span><span class="fr-only">Entrez votre nom.</span></div>
<div class="btn-row">
<button type="button" class="btn btn-apple-secondary" id="back3"><span class="en-only">Back</span><span class="fr-only">Retour</span></button>
<button type="button" class="btn btn-primary" id="next3"><span class="en-only">Next</span><span class="fr-only">Suivant</span></button>
</div>
</div>
<div class="form-step" data-step="4" id="step4">
<div class="field-wrapper" id="phoneWrapper">
<span class="field-icon">📞</span>
<input class="field-input" type="tel" id="phone" placeholder="Phone number">
</div>
<div class="field-error" id="phoneError"><span class="en-only">Please enter your phone number.</span><span class="fr-only">Entrez votre numéro.</span></div>
<div class="btn-row">
<button type="button" class="btn btn-apple-secondary" id="back3"><span class="en-only">Back</span><span class="fr-only">Retour</span></button>
<button type="button" class="btn btn-apple-secondary" id="back4"><span class="en-only">Back</span><span class="fr-only">Retour</span></button>
<button type="button" class="btn btn-primary" id="submitBtn"><span class="en-only">Submit</span><span class="fr-only">Envoyer</span></button>
</div>
</div>
@@ -440,9 +502,10 @@
<script>
(function(){
const API_URL = 'http://192.168.1.30:8581';
const totalSteps = 3;
const API_URL = 'http://192.168.1.126:8581';
const totalSteps = 4;
let currentStep = 1;
let selectedTier = '';
const steps = Array.from(document.querySelectorAll('.form-step'));
const dots = Array.from(document.querySelectorAll('.step-dot'));
@@ -454,14 +517,24 @@
name: { input: document.getElementById('name'), wrapper: document.getElementById('nameWrapper'), error: document.getElementById('nameError') },
phone: { input: document.getElementById('phone'), wrapper: document.getElementById('phoneWrapper'), error: document.getElementById('phoneError') }
};
const tierError = document.getElementById('tierError');
function validateEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim()); }
window.selectTier = function(el) {
document.querySelectorAll('.tier-option').forEach(o => o.classList.remove('selected'));
el.classList.add('selected');
el.querySelector('input').checked = true;
selectedTier = el.querySelector('input').value;
if (tierError) tierError.classList.remove('shown');
};
function clearErrors() {
Object.values(fields).forEach(f => {
if(f.wrapper) f.wrapper.classList.remove('error');
if(f.error) f.error.classList.remove('shown');
});
if (tierError) tierError.classList.remove('shown');
}
function showStep(n, dir) {
@@ -479,7 +552,6 @@
const isFr = document.documentElement.classList.contains('lang-fr');
label.textContent = isFr ? 'Étape ' + n + ' sur ' + totalSteps : 'Step ' + n + ' of ' + totalSteps;
const input = stepEl ? stepEl.querySelector('input, select') : null;
// Only auto-focus on user-initiated step changes, not on page load
if (input && dir) setTimeout(() => input.focus(), 100);
currentStep = n;
}
@@ -495,6 +567,12 @@
}
}
if (n === 2) {
if (!selectedTier) {
if (tierError) tierError.classList.add('shown');
return false;
}
}
if (n === 3) {
const v = fields.name.input.value.trim();
if (!v) {
fields.name.wrapper.classList.add('error');
@@ -502,7 +580,7 @@
return false;
}
}
if (n === 3) {
if (n === 4) {
const v = fields.phone.input.value.trim();
if (!v) {
fields.phone.wrapper.classList.add('error');
@@ -527,6 +605,7 @@
async function submitForm() {
const payload = {
email: fields.email.input.value.trim(),
tier: selectedTier,
name: fields.name.input.value.trim(),
phone: fields.phone.input.value.trim()
};
@@ -538,11 +617,10 @@
});
if (resp.ok) {
showSuccess();
// Save email/phone to cookie so order check is remembered
setVelaCookie(STATUS_COOKIE, JSON.stringify({email: payload.email, phone: payload.phone}), 30);
} else {
const isFr = document.documentElement.classList.contains('lang-fr');
alert(isFr ? 'Échec de l\'envoi. Réessayez.' : 'Submission failed. Please try again.');
alert(isFr ? "Échec de l'envoi. Réessayez." : 'Submission failed. Please try again.');
}
} catch (e) {
const isFr = document.documentElement.classList.contains('lang-fr');
@@ -553,6 +631,9 @@
function resetForm() {
success.classList.remove('visible');
Object.values(fields).forEach(f => { if (f.input) f.input.value = ''; });
document.querySelectorAll('.tier-option').forEach(o => o.classList.remove('selected'));
document.querySelectorAll('.tier-option input').forEach(i => i.checked = false);
selectedTier = '';
clearErrors();
currentStep = 1;
showStep(1);
@@ -585,8 +666,12 @@
if (validateStep(2)) showStep(3, 'animate-forward');
});
document.getElementById('back3').addEventListener('click', function() { showStep(2, 'animate-back'); });
document.getElementById('next3').addEventListener('click', function() {
if (validateStep(3)) showStep(4, 'animate-forward');
});
document.getElementById('back4').addEventListener('click', function() { showStep(3, 'animate-back'); });
document.getElementById('submitBtn').addEventListener('click', function() {
if (validateStep(3)) submitForm();
if (validateStep(4)) submitForm();
});
document.getElementById('restartBtn').addEventListener('click', resetForm);
showStep(1);