refactor: code structure for improved readability and maintainability

This commit is contained in:
2026-04-13 17:08:34 -03:00
parent fcbfc599a1
commit 3998d7e6b1
2 changed files with 857 additions and 826 deletions

View File

@@ -1,308 +1,307 @@
// Smooth scrolling for anchor links // Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => { document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener('click', function(e) { anchor.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
const target = document.querySelector(this.getAttribute('href')); const target = document.querySelector(this.getAttribute("href"));
if (target) { if (target) {
target.scrollIntoView({ behavior: 'smooth' }); target.scrollIntoView({ behavior: "smooth" });
} }
}); });
}); });
// Scroll progress bar // Scroll progress bar
const progressBar = document.getElementById('progress-bar'); const progressBar = document.getElementById("progress-bar");
window.addEventListener('scroll', () => { window.addEventListener("scroll", () => {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight; const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = (scrollTop / docHeight) * 100; const progress = (scrollTop / docHeight) * 100;
progressBar.style.width = progress + '%'; progressBar.style.width = progress + "%";
}); });
// Student discount logic // Student discount logic
const studentCheckbox = document.getElementById('student'); const studentCheckbox = document.getElementById("student");
const plans = { const plans = {
normal: { full: 10, label: 'Normal' }, normal: { full: 10, label: "Normal" },
premium: { full: 15, label: 'Premium' } premium: { full: 15, label: "Premium" },
}; };
function updatePrices() { function updatePrices() {
const isStudent = studentCheckbox.checked; const isStudent = studentCheckbox.checked;
document.querySelectorAll('.radio-group label').forEach(lbl => { document.querySelectorAll(".radio-group label").forEach((lbl) => {
const input = lbl.querySelector('input[type="radio"]'); const input = lbl.querySelector('input[type="radio"]');
if (!input) return; if (!input) return;
const plan = plans[input.value]; const plan = plans[input.value];
if (!plan) return; if (!plan) return;
const discounted = (plan.full * 0.7).toFixed(2); const discounted = (plan.full * 0.7).toFixed(2);
if (isStudent) { if (isStudent) {
lbl.innerHTML = ` lbl.innerHTML = `
<input type="radio" name="plan" value="${input.value}" ${input.checked ? 'checked' : ''} ${input.required ? 'required' : ''}> <input type="radio" name="plan" value="${input.value}" ${input.checked ? "checked" : ""} ${input.required ? "required" : ""}>
${plan.label} ${plan.label}
<span class="price-original">$${plan.full}/mes</span> <span class="price-original">$${plan.full}/mes</span>
<span class="price-discount">20% OFF</span> <span class="price-discount">20% OFF</span>
<span class="price-new">$${discounted}/mes</span> <span class="price-new">$${discounted}/mes</span>
`; `;
} else { } else {
lbl.innerHTML = ` lbl.innerHTML = `
<input type="radio" name="plan" value="${input.value}" ${input.checked ? 'checked' : ''} ${input.required ? 'required' : ''}> <input type="radio" name="plan" value="${input.value}" ${input.checked ? "checked" : ""} ${input.required ? "required" : ""}>
${plan.label}$${plan.full}/mes ${plan.label}$${plan.full}/mes
`; `;
} }
}); });
} }
studentCheckbox.addEventListener('change', updatePrices); studentCheckbox.addEventListener("change", updatePrices);
// Character counter for textarea // Character counter for textarea
const textarea = document.getElementById('reason'); const textarea = document.getElementById("reason");
const counter = document.getElementById('reason-counter'); const counter = document.getElementById("reason-counter");
const MAX_CHARS = 300; const MAX_CHARS = 300;
textarea.addEventListener('input', () => { textarea.addEventListener("input", () => {
const len = textarea.value.length; const len = textarea.value.length;
counter.textContent = `${len} / ${MAX_CHARS}`; counter.textContent = `${len} / ${MAX_CHARS}`;
counter.classList.remove('near-limit', 'at-limit'); counter.classList.remove("near-limit", "at-limit");
if (len >= MAX_CHARS) { if (len >= MAX_CHARS) {
textarea.value = textarea.value.slice(0, MAX_CHARS); textarea.value = textarea.value.slice(0, MAX_CHARS);
counter.classList.add('at-limit'); counter.classList.add("at-limit");
} else if (len >= MAX_CHARS * 0.8) { } else if (len >= MAX_CHARS * 0.8) {
counter.classList.add('near-limit'); counter.classList.add("near-limit");
} }
}); });
// Form validation // Form validation
function setFieldState(input, isValid) { function setFieldState(input, isValid) {
const group = input.closest('.field-group'); const group = input.closest(".field-group");
if (!group) return; if (!group) return;
group.classList.remove('error', 'success'); group.classList.remove("error", "success");
if (input.value.trim() === '') return; if (input.value.trim() === "") return;
group.classList.add(isValid ? 'success' : 'error'); group.classList.add(isValid ? "success" : "error");
} }
// Name and Lastname // Name and Lastname
['name', 'lastname'].forEach(id => { ["name", "lastname"].forEach((id) => {
document.getElementById(id).addEventListener('input', function() { document.getElementById(id).addEventListener("input", function () {
setFieldState(this, this.value.trim().length >= 2); setFieldState(this, this.value.trim().length >= 2);
}); });
}); });
// Email // Email
document.getElementById('email').addEventListener('input', function() { document.getElementById("email").addEventListener("input", function () {
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.value); const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.value);
setFieldState(this, valid); setFieldState(this, valid);
}); });
// MMAA validation // MMAA validation
document.getElementById('expiry').addEventListener('input', function() { document.getElementById("expiry").addEventListener("input", function () {
this.value = this.value.replace(/[^\d]/g, '').slice(0, 4); this.value = this.value.replace(/[^\d]/g, "").slice(0, 4);
if (this.value.length > 2) { if (this.value.length > 2) {
this.value = this.value.slice(0, 2) + '/' + this.value.slice(2); this.value = this.value.slice(0, 2) + "/" + this.value.slice(2);
} }
const valid = /^(0[1-9]|1[0-2])\/\d{2}$/.test(this.value); const valid = /^(0[1-9]|1[0-2])\/\d{2}$/.test(this.value);
setFieldState(this, valid); setFieldState(this, valid);
}); });
// CVV // CVV
document.getElementById('cvv').addEventListener('input', function() { document.getElementById("cvv").addEventListener("input", function () {
this.value = this.value.replace(/\D/g, '').slice(0, 4); this.value = this.value.replace(/\D/g, "").slice(0, 4);
setFieldState(this, this.value.length >= 3); setFieldState(this, this.value.length >= 3);
}); });
// Plan preview logic // Plan preview logic
const planData = { const planData = {
normal: { normal: {
name: 'Normal', name: "Normal",
price: '$10/mes', price: "$10/mes",
priceStudent: '$7.00/mes', priceStudent: "$7.00/mes",
features: [ features: [
'250 g por envío', "250 g por envío",
'1 café de origen único por mes', "1 café de origen único por mes",
'Envío gratuito', "Envío gratuito",
'Guía de preparación' "Guía de preparación",
] ],
}, },
premium: { premium: {
name: 'Premium', name: "Premium",
price: '$15/mes', price: "$15/mes",
priceStudent: '$10.50/mes', priceStudent: "$10.50/mes",
features: [ features: [
'500 g por envío', "500 g por envío",
'2 cafés de origen único por mes', "2 cafés de origen único por mes",
'Envío gratuito', "Envío gratuito",
'Guía de preparación', "Guía de preparación",
'Acceso a lotes exclusivos' "Acceso a lotes exclusivos",
] ],
} },
}; };
const planPreview = document.getElementById('plan-preview'); const planPreview = document.getElementById("plan-preview");
const planPreviewName = document.getElementById('plan-preview-name'); const planPreviewName = document.getElementById("plan-preview-name");
const planPreviewPrice = document.getElementById('plan-preview-price'); const planPreviewPrice = document.getElementById("plan-preview-price");
const planPreviewFeatures = document.getElementById('plan-preview-features'); const planPreviewFeatures = document.getElementById("plan-preview-features");
function updatePlanPreview() { function updatePlanPreview() {
const selected = document.querySelector('input[name="plan"]:checked'); const selected = document.querySelector('input[name="plan"]:checked');
if (!selected) { if (!selected) {
planPreview.classList.remove('visible'); planPreview.classList.remove("visible");
return; return;
} }
const plan = planData[selected.value]; const plan = planData[selected.value];
const isStudent = document.getElementById('student').checked; const isStudent = document.getElementById("student").checked;
planPreviewName.textContent = plan.name; planPreviewName.textContent = plan.name;
planPreviewPrice.textContent = isStudent ? plan.priceStudent : plan.price; planPreviewPrice.textContent = isStudent ? plan.priceStudent : plan.price;
planPreviewFeatures.innerHTML = plan.features planPreviewFeatures.innerHTML = plan.features
.map(f => `<li>${f}</li>`) .map((f) => `<li>${f}</li>`)
.join(''); .join("");
planPreview.classList.add('visible'); planPreview.classList.add("visible");
} }
document.querySelectorAll('input[name="plan"]').forEach(radio => { document.querySelectorAll('input[name="plan"]').forEach((radio) => {
radio.addEventListener('change', updatePlanPreview); radio.addEventListener("change", updatePlanPreview);
}); });
// Update prices and preview when student checkbox changes // Update prices and preview when student checkbox changes
studentCheckbox.addEventListener('change', () => { studentCheckbox.addEventListener("change", () => {
updatePrices(); updatePrices();
updatePlanPreview(); updatePlanPreview();
}); });
// Credit card validation and brand detection // Credit card validation and brand detection
const cardInput = document.getElementById('card'); const cardInput = document.getElementById("card");
const cardBrand = document.getElementById('card-brand'); const cardBrand = document.getElementById("card-brand");
const cardPatterns = { const cardPatterns = {
Visa: /^4/, Visa: /^4/,
Mastercard: /^5[1-5]|^2[2-7]/, Mastercard: /^5[1-5]|^2[2-7]/,
Amex: /^3[47]/, Amex: /^3[47]/,
Naranja: /^589562/, Naranja: /^589562/,
Cabal: /^604201|^589657/, Cabal: /^604201|^589657/,
}; };
function detectCardBrand(number) { function detectCardBrand(number) {
const clean = number.replace(/\s/g, ''); const clean = number.replace(/\s/g, "");
for (const [brand, pattern] of Object.entries(cardPatterns)) { for (const [brand, pattern] of Object.entries(cardPatterns)) {
if (pattern.test(clean)) return brand; if (pattern.test(clean)) return brand;
} }
return null; return null;
} }
function validateCard(number) { function validateCard(number) {
const clean = number.replace(/\s/g, ''); const clean = number.replace(/\s/g, "");
if (clean.length < 13) return false; if (clean.length < 13) return false;
// Luhn algorithm // Luhn algorithm
let sum = 0; let sum = 0;
let alternate = false; let alternate = false;
for (let i = clean.length - 1; i >= 0; i--) { for (let i = clean.length - 1; i >= 0; i--) {
let n = parseInt(clean[i]); let n = parseInt(clean[i]);
if (alternate) { if (alternate) {
n *= 2; n *= 2;
if (n > 9) n -= 9; if (n > 9) n -= 9;
}
sum += n;
alternate = !alternate;
} }
return sum % 10 === 0; sum += n;
alternate = !alternate;
}
return sum % 10 === 0;
} }
cardInput.addEventListener('input', function() { cardInput.addEventListener("input", function () {
// Remove non-digits and limit to 16 characters // Remove non-digits and limit to 16 characters
let val = this.value.replace(/\D/g, '').slice(0, 16); let val = this.value.replace(/\D/g, "").slice(0, 16);
this.value = val.replace(/(.{4})/g, '$1 ').trim(); this.value = val.replace(/(.{4})/g, "$1 ").trim();
const brand = detectCardBrand(this.value); const brand = detectCardBrand(this.value);
const valid = validateCard(this.value); const valid = validateCard(this.value);
if (brand) { if (brand) {
cardBrand.textContent = brand; cardBrand.textContent = brand;
cardBrand.classList.add('visible'); cardBrand.classList.add("visible");
} else { } else {
cardBrand.classList.remove('visible'); cardBrand.classList.remove("visible");
} }
setFieldState(this, valid); setFieldState(this, valid);
}); });
// Payment cuotas logic // Payment cuotas logic
const paymentSelect = document.getElementById('payment'); const paymentSelect = document.getElementById("payment");
const cuotasGroup = document.getElementById('cuotas-group'); const cuotasGroup = document.getElementById("cuotas-group");
const cuotasInput = document.getElementById('cuotas'); const cuotasInput = document.getElementById("cuotas");
paymentSelect.addEventListener('change', function() { paymentSelect.addEventListener("change", function () {
if (this.value === 'credit') { if (this.value === "credit") {
cuotasGroup.style.display = 'flex'; cuotasGroup.style.display = "flex";
cuotasInput.setAttribute('required', ''); cuotasInput.setAttribute("required", "");
} else { } else {
cuotasGroup.style.display = 'none'; cuotasGroup.style.display = "none";
cuotasInput.removeAttribute('required'); cuotasInput.removeAttribute("required");
cuotasInput.value = ''; cuotasInput.value = "";
} }
}); });
// Cuotas validation // Cuotas validation
cuotasInput.addEventListener('input', function() { cuotasInput.addEventListener("input", function () {
const val = parseInt(this.value); const val = parseInt(this.value);
setFieldState(this, val >= 1 && val <= 12); setFieldState(this, val >= 1 && val <= 12);
}); });
// Form submission logic // Form submission logic
const form = document.querySelector('form'); const form = document.querySelector("form");
const submitBtn = document.querySelector('.cta-button[type="submit"]'); const submitBtn = document.querySelector('.cta-button[type="submit"]');
const confirmation = document.getElementById('form-confirmation'); const confirmation = document.getElementById("form-confirmation");
const confirmDate = document.getElementById('confirm-date'); const confirmDate = document.getElementById("confirm-date");
const subsCounter = document.querySelector('.hero-stats .stat-item:last-child .stat-number'); const subsCounter = document.querySelector(
".hero-stats .stat-item:last-child .stat-number",
);
form.addEventListener('submit', function(e) { form.addEventListener("submit", function (e) {
e.preventDefault(); e.preventDefault();
// loading state // loading state
submitBtn.classList.add('loading'); submitBtn.classList.add("loading");
submitBtn.innerHTML = '<span class="spinner"></span> Procesando...'; submitBtn.innerHTML = '<span class="spinner"></span> Procesando...';
setTimeout(() => {
// Hidden form
form.style.transition = "opacity 0.3s ease";
form.style.opacity = "0";
setTimeout(() => { setTimeout(() => {
// Hidden form form.style.display = "none";
form.style.transition = 'opacity 0.3s ease';
form.style.opacity = '0';
setTimeout(() => { // Calculate delivery date (7 days from now)
form.style.display = 'none'; const delivery = new Date();
delivery.setDate(delivery.getDate() + 7);
confirmDate.textContent = delivery.toLocaleDateString("es-AR", {
day: "numeric",
month: "long",
year: "numeric",
});
// Calculate delivery date (7 days from now) // Show confirmation
const delivery = new Date(); confirmation.style.display = "flex";
delivery.setDate(delivery.getDate() + 7); requestAnimationFrame(() => {
confirmDate.textContent = delivery.toLocaleDateString('es-AR', { requestAnimationFrame(() => {
day: 'numeric', confirmation.classList.add("visible");
month: 'long', });
year: 'numeric' });
});
// Show confirmation // +1 to subscriber counter
confirmation.style.display = 'flex'; if (subsCounter) {
requestAnimationFrame(() => { const current = parseInt(subsCounter.textContent);
requestAnimationFrame(() => { subsCounter.textContent = current + 1;
confirmation.classList.add('visible'); }
}); }, 300);
}); }, 2000); // simulate processing delay
// +1 to subscriber counter
if (subsCounter) {
const current = parseInt(subsCounter.textContent);
subsCounter.textContent = current + 1;
}
}, 300);
}, 2000); // simulate processing delay
}); });

File diff suppressed because it is too large Load Diff