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