generated from marquez.juan/Ejercicio-inicial--git-y-HTML
refactor: code structure for improved readability and maintainability
This commit is contained in:
411
src/script.js
411
src/script.js
@@ -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
|
|
||||||
});
|
});
|
||||||
1268
src/styles.css
1268
src/styles.css
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user