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,36 +1,36 @@
// 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;
@@ -41,7 +41,7 @@ function updatePrices() {
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>
@@ -49,138 +49,137 @@ function updatePrices() {
`; `;
} 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/,
@@ -191,7 +190,7 @@ const cardPatterns = {
}; };
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;
} }
@@ -199,7 +198,7 @@ function detectCardBrand(number) {
} }
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
@@ -217,82 +216,84 @@ function validateCard(number) {
return sum % 10 === 0; 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(() => { setTimeout(() => {
// Hidden form // Hidden form
form.style.transition = 'opacity 0.3s ease'; form.style.transition = "opacity 0.3s ease";
form.style.opacity = '0'; form.style.opacity = "0";
setTimeout(() => { setTimeout(() => {
form.style.display = 'none'; form.style.display = "none";
// Calculate delivery date (7 days from now) // Calculate delivery date (7 days from now)
const delivery = new Date(); const delivery = new Date();
delivery.setDate(delivery.getDate() + 7); delivery.setDate(delivery.getDate() + 7);
confirmDate.textContent = delivery.toLocaleDateString('es-AR', { confirmDate.textContent = delivery.toLocaleDateString("es-AR", {
day: 'numeric', day: "numeric",
month: 'long', month: "long",
year: 'numeric' year: "numeric",
}); });
// Show confirmation // Show confirmation
confirmation.style.display = 'flex'; confirmation.style.display = "flex";
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
confirmation.classList.add('visible'); confirmation.classList.add("visible");
}); });
}); });
@@ -301,8 +302,6 @@ form.addEventListener('submit', function(e) {
const current = parseInt(subsCounter.textContent); const current = parseInt(subsCounter.textContent);
subsCounter.textContent = current + 1; subsCounter.textContent = current + 1;
} }
}, 300); }, 300);
}, 2000); // simulate processing delay }, 2000); // simulate processing delay
}); });

View File

@@ -12,7 +12,8 @@ body {
font-family: Georgia, serif; font-family: Georgia, serif;
} }
html, body { html,
body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -31,6 +32,7 @@ input[type="number"]::-webkit-inner-spin-button {
input[type="number"] { input[type="number"] {
-moz-appearance: textfield; -moz-appearance: textfield;
appearance: textfield;
} }
/* Navbar */ /* Navbar */
@@ -87,7 +89,7 @@ input[type="number"] {
} }
#main-nav ul a::after { #main-nav ul a::after {
content: ''; content: "";
position: absolute; position: absolute;
bottom: -3px; bottom: -3px;
left: 0; left: 0;
@@ -128,14 +130,14 @@ input[type="number"] {
height: calc(100vh - 60px); height: calc(100vh - 60px);
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
background-image: url('/public/assets/bg-1.jpg'); background-image: url("/public/assets/bg-1.jpg");
background-size: cover; background-size: cover;
background-position: center; background-position: center;
overflow: hidden; overflow: hidden;
} }
#hero::before { #hero::before {
content: ''; content: "";
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient( background: linear-gradient(
@@ -221,7 +223,9 @@ input[type="number"] {
text-decoration: none; text-decoration: none;
border-bottom: 1px solid rgba(245, 240, 232, 0.2); border-bottom: 1px solid rgba(245, 240, 232, 0.2);
padding-bottom: 1px; padding-bottom: 1px;
transition: color 0.2s, border-color 0.2s; transition:
color 0.2s,
border-color 0.2s;
} }
.hero-secondary:hover { .hero-secondary:hover {
@@ -276,13 +280,22 @@ input[type="number"] {
.scroll-line { .scroll-line {
width: 1px; width: 1px;
height: 40px; height: 40px;
background: linear-gradient(to bottom, rgba(245, 240, 232, 0.25), transparent); background: linear-gradient(
to bottom,
rgba(245, 240, 232, 0.25),
transparent
);
animation: scrollpulse 2s ease-in-out infinite; animation: scrollpulse 2s ease-in-out infinite;
} }
@keyframes scrollpulse { @keyframes scrollpulse {
0%, 100% { opacity: 0.3; } 0%,
50% { opacity: 0.8; } 100% {
opacity: 0.3;
}
50% {
opacity: 0.8;
}
} }
/* Benefits Section */ /* Benefits Section */
@@ -353,7 +366,7 @@ input[type="number"] {
} }
#beneficios ul li::before { #beneficios ul li::before {
content: ''; content: "";
display: block; display: block;
width: 16px; width: 16px;
min-width: 16px; min-width: 16px;
@@ -456,8 +469,12 @@ input[type="number"] {
border-right: 1px solid rgba(200, 169, 110, 0.12); border-right: 1px solid rgba(200, 169, 110, 0.12);
} }
.check { color: #c8a96e; } .check {
.dash { color: rgba(245, 240, 232, 0.15); } color: #c8a96e;
}
.dash {
color: rgba(245, 240, 232, 0.15);
}
/* Subscription Form */ /* Subscription Form */
@@ -558,7 +575,9 @@ select {
font-size: 0.95rem; font-size: 0.95rem;
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;
outline: none; outline: none;
transition: border-color 0.2s, background 0.2s; transition:
border-color 0.2s,
background 0.2s;
width: 100%; width: 100%;
} }
@@ -632,7 +651,7 @@ textarea {
} }
.radio-group input[type="radio"]::after { .radio-group input[type="radio"]::after {
content: ''; content: "";
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@@ -664,7 +683,9 @@ textarea {
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
transition: border-color 0.2s, background 0.2s; transition:
border-color 0.2s,
background 0.2s;
} }
.checkbox-group input[type="checkbox"]:checked { .checkbox-group input[type="checkbox"]:checked {
@@ -673,7 +694,7 @@ textarea {
} }
.checkbox-group input[type="checkbox"]::after { .checkbox-group input[type="checkbox"]::after {
content: ''; content: "";
position: absolute; position: absolute;
top: 2px; top: 2px;
left: 5px; left: 5px;
@@ -816,7 +837,9 @@ textarea {
padding-top: 1.5rem; padding-top: 1.5rem;
opacity: 0; opacity: 0;
transform: translateY(8px); transform: translateY(8px);
transition: opacity 0.3s ease, transform 0.3s ease; transition:
opacity 0.3s ease,
transform 0.3s ease;
pointer-events: none; pointer-events: none;
} }
@@ -868,7 +891,7 @@ textarea {
} }
.plan-preview-features li::before { .plan-preview-features li::before {
content: ''; content: "";
display: block; display: block;
width: 12px; width: 12px;
min-width: 12px; min-width: 12px;
@@ -907,7 +930,9 @@ textarea {
} }
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to {
transform: rotate(360deg);
}
} }
.cta-button.loading { .cta-button.loading {
@@ -928,7 +953,9 @@ textarea {
padding: 3rem 0; padding: 3rem 0;
opacity: 0; opacity: 0;
transform: translateY(12px); transform: translateY(12px);
transition: opacity 0.4s ease, transform 0.4s ease; transition:
opacity 0.4s ease,
transform 0.4s ease;
} }
#form-confirmation.visible { #form-confirmation.visible {
@@ -978,7 +1005,6 @@ textarea {
color: #c8a96e; color: #c8a96e;
} }
/* Footer */ /* Footer */
#site-footer { #site-footer {
@@ -1009,8 +1035,12 @@ textarea {
transition: color 0.2s; transition: color 0.2s;
} }
.footer-logo:hover { color: rgba(245, 240, 232, 0.6); } .footer-logo:hover {
.footer-logo:hover span { color: #c8a96e; } color: rgba(245, 240, 232, 0.6);
}
.footer-logo:hover span {
color: #c8a96e;
}
.footer-links { .footer-links {
display: flex; display: flex;
@@ -1027,7 +1057,9 @@ textarea {
transition: color 0.2s; transition: color 0.2s;
} }
.footer-links a:hover { color: rgba(245, 240, 232, 0.6); } .footer-links a:hover {
color: rgba(245, 240, 232, 0.6);
}
.footer-copy { .footer-copy {
font-size: 0.72rem; font-size: 0.72rem;