generated from marquez.juan/Ejercicio-inicial--git-y-HTML
Compare commits
5 Commits
c5cb44abab
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 76aa4ad897 | |||
| 3998d7e6b1 | |||
| fcbfc599a1 | |||
| 71e1ee1c11 | |||
|
|
7bf0bdc4e3 |
BIN
public/assets/bg-1.jpg
Normal file
BIN
public/assets/bg-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 MiB |
BIN
public/assets/bg-2.jpg
Normal file
BIN
public/assets/bg-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.8 MiB |
BIN
public/assets/bg-3.jpg
Normal file
BIN
public/assets/bg-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 MiB |
393
src/index.html
Normal file
393
src/index.html
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>BrewBox</title>
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="progress-bar"></div>
|
||||||
|
<header id="site-header">
|
||||||
|
<nav id="main-nav">
|
||||||
|
<a href="#" class="nav-logo">Brew<span>Box</span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#hero">Inicio</a></li>
|
||||||
|
<li><a href="#beneficios">Beneficios</a></li>
|
||||||
|
<li><a href="#planes">Planes</a></li>
|
||||||
|
<li><a href="#formulario">Suscribirse</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section id="hero">
|
||||||
|
<!-- Hero principal con imagen de fondo y call to action -->
|
||||||
|
<div class="hero-content">
|
||||||
|
<span class="hero-eyebrow">Suscripción mensual</span>
|
||||||
|
<h1>Café de especialidad, <em>en tu puerta.</em></h1>
|
||||||
|
<p>
|
||||||
|
Granos de origen único, tostados a pedido y enviados frescos cada
|
||||||
|
mes. Sin contratos, sin compromiso.
|
||||||
|
</p>
|
||||||
|
<div class="hero-actions">
|
||||||
|
<a href="#formulario" class="cta-button">Empezar ahora</a>
|
||||||
|
<a href="#planes" class="hero-secondary">Ver planes</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">12+</span>
|
||||||
|
<span class="stat-label">Orígenes</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-number">500</span>
|
||||||
|
<span class="stat-label">Suscriptores</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-scroll">
|
||||||
|
<div class="scroll-line"></div>
|
||||||
|
<span>Scroll</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="beneficios">
|
||||||
|
<!-- Cards de beneficios -->
|
||||||
|
<article class="card">
|
||||||
|
<span class="card-eyebrow">Nuestra filosofía</span>
|
||||||
|
<h2>¿Por qué BrewBox?</h2>
|
||||||
|
<p>
|
||||||
|
Trabajamos con tostadores independientes y fincas de origen único.
|
||||||
|
Cada envío viene con notas de cata, origen del grano y método de
|
||||||
|
preparación recomendado. Sin contratos. Cancelás cuando querés.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
<article class="card">
|
||||||
|
<span class="card-eyebrow">Lo que incluye</span>
|
||||||
|
<h2>Beneficios de la suscripción</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Acceso a lotes exclusivos y ediciones limitadas</li>
|
||||||
|
<li>Descuentos especiales para suscriptores</li>
|
||||||
|
<li>Envío gratuito en cada pedido</li>
|
||||||
|
<li>Notas de cata y guías de preparación en cada envío</li>
|
||||||
|
<li>Podés pausar o cancelar cuando quieras</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="planes">
|
||||||
|
<!-- Tabla comparativa de planes -->
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-eyebrow">Precios</span>
|
||||||
|
<h2>Elegí tu plan</h2>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Normal</th>
|
||||||
|
<th scope="col" class="col-premium">Premium</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Precio mensual</td>
|
||||||
|
<td>$10</td>
|
||||||
|
<td class="col-premium">$15</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Gramos por envío</td>
|
||||||
|
<td>250 g</td>
|
||||||
|
<td class="col-premium">500 g</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cafés de origen único</td>
|
||||||
|
<td>1 por mes</td>
|
||||||
|
<td class="col-premium">2 por mes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Envío gratuito</td>
|
||||||
|
<td><span class="check">✓</span></td>
|
||||||
|
<td class="col-premium"><span class="check">✓</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Guía de preparación</td>
|
||||||
|
<td><span class="check">✓</span></td>
|
||||||
|
<td class="col-premium"><span class="check">✓</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Acceso a lotes exclusivos</td>
|
||||||
|
<td><span class="dash">—</span></td>
|
||||||
|
<td class="col-premium"><span class="check">✓</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="formulario">
|
||||||
|
<!-- Formulario de suscripción -->
|
||||||
|
<div class="formulario-header">
|
||||||
|
<span class="section-eyebrow">Unite</span>
|
||||||
|
<h2>Suscribite a BrewBox</h2>
|
||||||
|
<p>
|
||||||
|
Completá el formulario y recibís tu primer envío en menos de 72hs.
|
||||||
|
Estudiantes obtienen un descuento del 20%.
|
||||||
|
</p>
|
||||||
|
<div class="plan-preview" id="plan-preview">
|
||||||
|
<div class="plan-preview-inner">
|
||||||
|
<span class="plan-preview-label">Plan seleccionado</span>
|
||||||
|
<span class="plan-preview-name" id="plan-preview-name">—</span>
|
||||||
|
<span class="plan-preview-price" id="plan-preview-price"></span>
|
||||||
|
<ul class="plan-preview-features" id="plan-preview-features"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="#" method="post">
|
||||||
|
<fieldset id="fs-personal">
|
||||||
|
<legend>Datos personales</legend>
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="name">Nombre</label>
|
||||||
|
<input type="text" id="name" name="name" required />
|
||||||
|
<span class="field-error-msg"
|
||||||
|
>Ingresá al menos 2 caracteres</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="lastname">Apellido</label>
|
||||||
|
<input type="text" id="lastname" name="lastname" required />
|
||||||
|
<span class="field-error-msg"
|
||||||
|
>Ingresá al menos 2 caracteres</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="email">Correo electrónico</label>
|
||||||
|
<input type="email" id="email" name="email" required />
|
||||||
|
<span class="field-error-msg"
|
||||||
|
>Ingresá un correo electrónico válido</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="field-group checkbox-group">
|
||||||
|
<input type="checkbox" id="student" name="student" />
|
||||||
|
<label for="student">Soy estudiante</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset id="fs-plan">
|
||||||
|
<legend>Plan</legend>
|
||||||
|
<div class="field-group">
|
||||||
|
<label>Plan elegido</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label
|
||||||
|
><input type="radio" name="plan" value="normal" required />
|
||||||
|
Normal — $10/mes</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><input type="radio" name="plan" value="premium" /> Premium —
|
||||||
|
$15/mes</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="reason">¿Por qué te suscribís?</label>
|
||||||
|
<textarea id="reason" name="reason" rows="4"></textarea>
|
||||||
|
<span class="char-counter" id="reason-counter">0 / 300</span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset id="fs-pago">
|
||||||
|
<legend>Método de pago</legend>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="payment">Tipo de tarjeta</label>
|
||||||
|
<select id="payment" name="payment" required>
|
||||||
|
<option value="">Seleccioná un método</option>
|
||||||
|
<option value="credit">Tarjeta de crédito</option>
|
||||||
|
<option value="debit">Tarjeta de débito</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field-group" id="cuotas-group" style="display: none">
|
||||||
|
<label for="cuotas">Cantidad de cuotas</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="cuotas"
|
||||||
|
name="cuotas"
|
||||||
|
min="1"
|
||||||
|
max="12"
|
||||||
|
placeholder="1"
|
||||||
|
/>
|
||||||
|
<div class="field-group field-group--icon">
|
||||||
|
<label for="card">Número de tarjeta</label>
|
||||||
|
<div class="input-icon-wrapper">
|
||||||
|
<svg
|
||||||
|
class="input-icon"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
|
||||||
|
<line x1="1" y1="10" x2="23" y2="10"></line>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="card"
|
||||||
|
name="card"
|
||||||
|
placeholder="XXXX-XXXX-XXXX-XXXX"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span class="card-brand" id="card-brand"></span>
|
||||||
|
</div>
|
||||||
|
<span class="field-error-msg">Número de tarjeta inválido</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="expiry">Vencimiento</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="expiry"
|
||||||
|
name="expiry"
|
||||||
|
placeholder="MM/AA"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span class="field-error-msg">Formato MM/AA</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="cvv">CVV</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="cvv"
|
||||||
|
name="cvv"
|
||||||
|
placeholder="123"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<span class="field-error-msg">Ingresá un CVV válido</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset id="fs-envio">
|
||||||
|
<legend>Dirección de envío</legend>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="address">Dirección</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="address"
|
||||||
|
name="address"
|
||||||
|
placeholder="Calle, número, piso/depto"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="postal">Código postal</label>
|
||||||
|
<input type="text" id="postal" name="postal" required />
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="city">Ciudad</label>
|
||||||
|
<input type="text" id="city" name="city" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="state">Provincia</label>
|
||||||
|
<select id="state" name="state" required>
|
||||||
|
<option value="">Seleccioná una provincia</option>
|
||||||
|
<option value="buenos-aires">Buenos Aires</option>
|
||||||
|
<option value="catamarca">Catamarca</option>
|
||||||
|
<option value="chaco">Chaco</option>
|
||||||
|
<option value="chubut">Chubut</option>
|
||||||
|
<option value="caba">Ciudad Autónoma de Buenos Aires</option>
|
||||||
|
<option value="cordoba">Córdoba</option>
|
||||||
|
<option value="corrientes">Corrientes</option>
|
||||||
|
<option value="entre-rios">Entre Ríos</option>
|
||||||
|
<option value="formosa">Formosa</option>
|
||||||
|
<option value="jujuy">Jujuy</option>
|
||||||
|
<option value="la-pampa">La Pampa</option>
|
||||||
|
<option value="la-rioja">La Rioja</option>
|
||||||
|
<option value="mendoza">Mendoza</option>
|
||||||
|
<option value="misiones">Misiones</option>
|
||||||
|
<option value="neuquen">Neuquén</option>
|
||||||
|
<option value="rio-negro">Río Negro</option>
|
||||||
|
<option value="salta">Salta</option>
|
||||||
|
<option value="san-juan">San Juan</option>
|
||||||
|
<option value="san-luis">San Luis</option>
|
||||||
|
<option value="santa-cruz">Santa Cruz</option>
|
||||||
|
<option value="santa-fe">Santa Fe</option>
|
||||||
|
<option value="santiago-del-estero">Santiago del Estero</option>
|
||||||
|
<option value="tierra-del-fuego">Tierra del Fuego</option>
|
||||||
|
<option value="tucuman">Tucumán</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset id="fs-legal">
|
||||||
|
<legend>Preferencias</legend>
|
||||||
|
<div class="field-group checkbox-group">
|
||||||
|
<input type="checkbox" id="terms" name="terms" required />
|
||||||
|
<label for="terms">Acepto los términos y condiciones</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-group checkbox-group">
|
||||||
|
<input type="checkbox" id="offers" name="offers" />
|
||||||
|
<label for="offers"
|
||||||
|
>Quiero recibir ofertas y novedades por email</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="form-submit">
|
||||||
|
<button type="submit" class="cta-button">Suscribirme</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="form-confirmation">
|
||||||
|
<div class="confirmation-icon">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="20 6 9 17 4 12"></polyline>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="confirmation-title">¡Bienvenido a BrewBox!</p>
|
||||||
|
<p class="confirmation-text">
|
||||||
|
Tu suscripción fue registrada. En las próximas horas vas a recibir
|
||||||
|
un email de confirmación con los detalles de tu primer envío.
|
||||||
|
</p>
|
||||||
|
<p class="confirmation-detail">
|
||||||
|
Primer envío estimado: <span id="confirm-date"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer id="site-footer">
|
||||||
|
<div class="footer-inner">
|
||||||
|
<a href="#" class="footer-logo">Brew<span>Box</span></a>
|
||||||
|
<ul class="footer-links">
|
||||||
|
<li><a href="#hero">Inicio</a></li>
|
||||||
|
<li><a href="#beneficios">Beneficios</a></li>
|
||||||
|
<li><a href="#planes">Planes</a></li>
|
||||||
|
<li><a href="#formulario">Suscribirse</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="footer-copy">© 2026 BrewBox</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
307
src/script.js
Normal file
307
src/script.js
Normal file
@@ -0,0 +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" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scroll 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 + "%";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Student discount logic
|
||||||
|
const studentCheckbox = document.getElementById("student");
|
||||||
|
|
||||||
|
const plans = {
|
||||||
|
normal: { full: 10, label: "Normal" },
|
||||||
|
premium: { full: 15, label: "Premium" },
|
||||||
|
};
|
||||||
|
|
||||||
|
function updatePrices() {
|
||||||
|
const isStudent = studentCheckbox.checked;
|
||||||
|
|
||||||
|
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 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" : ""}>
|
||||||
|
${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" : ""}>
|
||||||
|
${plan.label} — $${plan.full}/mes
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
studentCheckbox.addEventListener("change", updatePrices);
|
||||||
|
|
||||||
|
// Character counter for textarea
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name and Lastname
|
||||||
|
["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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// CVV
|
||||||
|
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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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 plan = planData[selected.value];
|
||||||
|
const isStudent = document.getElementById("student").checked;
|
||||||
|
|
||||||
|
planPreviewName.textContent = plan.name;
|
||||||
|
planPreviewPrice.textContent = isStudent ? plan.priceStudent : plan.price;
|
||||||
|
|
||||||
|
planPreviewFeatures.innerHTML = plan.features
|
||||||
|
.map((f) => `<li>${f}</li>`)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
planPreview.classList.add("visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="plan"]').forEach((radio) => {
|
||||||
|
radio.addEventListener("change", updatePlanPreview);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update prices and preview when student checkbox changes
|
||||||
|
studentCheckbox.addEventListener("change", () => {
|
||||||
|
updatePrices();
|
||||||
|
updatePlanPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Credit card validation and brand detection
|
||||||
|
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/,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCard(number) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
const brand = detectCardBrand(this.value);
|
||||||
|
const valid = validateCard(this.value);
|
||||||
|
|
||||||
|
if (brand) {
|
||||||
|
cardBrand.textContent = brand;
|
||||||
|
cardBrand.classList.add("visible");
|
||||||
|
} else {
|
||||||
|
cardBrand.classList.remove("visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
setFieldState(this, valid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payment cuotas logic
|
||||||
|
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 = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cuotas validation
|
||||||
|
cuotasInput.addEventListener("input", function () {
|
||||||
|
const val = parseInt(this.value);
|
||||||
|
setFieldState(this, val >= 1 && val <= 12);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form submission logic
|
||||||
|
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",
|
||||||
|
);
|
||||||
|
|
||||||
|
form.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 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(() => {
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
1091
src/styles.css
Normal file
1091
src/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user