737 lines
25 KiB
JavaScript
737 lines
25 KiB
JavaScript
/* ============================================
|
|
CARGA DE INCLUDES (menu y footer)
|
|
============================================ */
|
|
async function loadInclude(selector, url) {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
const html = await response.text();
|
|
const container = document.querySelector(selector);
|
|
if (container) container.innerHTML = html;
|
|
} catch (error) {
|
|
console.error(`Error cargando ${url}:`, error);
|
|
}
|
|
}
|
|
|
|
/* ============================================
|
|
SWITCH DE TEMA (claro por defecto)
|
|
============================================ */
|
|
function initTheme() {
|
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
}
|
|
|
|
function toggleTheme() {
|
|
const current = document.documentElement.getAttribute('data-theme') || 'light';
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
localStorage.setItem('theme', next);
|
|
}
|
|
|
|
// Ejecutar al cargar la página
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initTheme();
|
|
|
|
const themeBtn = document.querySelector('.theme-toggle');
|
|
if (themeBtn) {
|
|
themeBtn.addEventListener('click', toggleTheme);
|
|
}
|
|
});
|
|
/* ============================================
|
|
CALCULAR ANCHO DEL SCROLLBAR
|
|
============================================ */
|
|
function getScrollbarWidth() {
|
|
return window.innerWidth - document.documentElement.clientWidth;
|
|
}
|
|
|
|
function applyScrollbarCompensation() {
|
|
const scrollbarWidth = getScrollbarWidth();
|
|
document.documentElement.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`);
|
|
}
|
|
|
|
/* ============================================
|
|
MENÚ FULLSCREEN
|
|
============================================ */
|
|
function openMenu() {
|
|
const menu = document.getElementById('fullscreen-menu');
|
|
const overlay = document.getElementById('menu-overlay');
|
|
const menuToggle = document.querySelector('.menu-toggle');
|
|
|
|
if (menu && overlay) {
|
|
applyScrollbarCompensation();
|
|
menu.classList.add('is-open');
|
|
overlay.classList.add('is-open');
|
|
document.body.classList.add('menu-open');
|
|
menuToggle.setAttribute('aria-expanded', 'true');
|
|
}
|
|
}
|
|
|
|
function closeMenu() {
|
|
const menu = document.getElementById('fullscreen-menu');
|
|
const overlay = document.getElementById('menu-overlay');
|
|
const menuToggle = document.querySelector('.menu-toggle');
|
|
|
|
if (menu && overlay) {
|
|
menu.classList.remove('is-open');
|
|
overlay.classList.remove('is-open');
|
|
document.body.classList.remove('menu-open');
|
|
menuToggle.setAttribute('aria-expanded', 'false');
|
|
}
|
|
}
|
|
|
|
|
|
/* ============================================
|
|
ACORDEÓN DE SERVICIOS
|
|
============================================ */
|
|
function initServiciosAccordion() {
|
|
const accordion = document.getElementById('servicios-accordion');
|
|
if (!accordion) {
|
|
console.log('Acordeón no encontrado');
|
|
return;
|
|
}
|
|
|
|
const items = accordion.querySelectorAll('.servicio-item');
|
|
console.log('Items encontrados:', items.length);
|
|
|
|
// Función para activar un item
|
|
function activateItem(item) {
|
|
if (item.classList.contains('is-active')) return;
|
|
|
|
accordion.classList.add('has-active');
|
|
items.forEach(i => i.classList.remove('is-active'));
|
|
item.classList.add('is-active');
|
|
}
|
|
|
|
// Función para cerrar (volver al estado inicial)
|
|
function closeAll() {
|
|
accordion.classList.remove('has-active');
|
|
items.forEach(i => i.classList.remove('is-active'));
|
|
}
|
|
|
|
// Click en cada item para expandirlo
|
|
items.forEach((item, index) => {
|
|
console.log(`Agregando listener al item ${index}`);
|
|
|
|
item.addEventListener('click', (e) => {
|
|
console.log('Click en item:', index);
|
|
|
|
// Si el click fue en el botón cerrar o en un botón CTA, no activar
|
|
if (e.target.closest('.servicio-close')) {
|
|
console.log('Click en cerrar');
|
|
return;
|
|
}
|
|
if (e.target.closest('.btn')) {
|
|
console.log('Click en botón CTA');
|
|
return;
|
|
}
|
|
|
|
activateItem(item);
|
|
});
|
|
});
|
|
|
|
// Click en los botones de cerrar
|
|
const closeButtons = accordion.querySelectorAll('.servicio-close');
|
|
closeButtons.forEach((btn, index) => {
|
|
console.log(`Agregando listener al botón cerrar ${index}`);
|
|
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
console.log('Botón cerrar clicked');
|
|
closeAll();
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ============================================
|
|
INICIALIZACIÓN
|
|
============================================ */
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Cargar menú y footer
|
|
await Promise.all([
|
|
loadInclude('#menu-container', 'includes/menu.html'),
|
|
loadInclude('#footer-container', 'includes/footer.html')
|
|
]);
|
|
|
|
// Inicializar tema
|
|
initTheme();
|
|
|
|
// Eventos del tema
|
|
const themeBtn = document.querySelector('.theme-toggle');
|
|
if (themeBtn) themeBtn.addEventListener('click', toggleTheme);
|
|
|
|
// Eventos del menú
|
|
const menuToggle = document.querySelector('.menu-toggle');
|
|
const menuClose = document.querySelector('.menu-close');
|
|
const menuOverlay = document.getElementById('menu-overlay');
|
|
const menuLinks = document.querySelectorAll('.fullscreen-menu a');
|
|
|
|
if (menuToggle) menuToggle.addEventListener('click', openMenu);
|
|
if (menuClose) menuClose.addEventListener('click', closeMenu);
|
|
if (menuOverlay) menuOverlay.addEventListener('click', closeMenu);
|
|
|
|
// Cerrar menú al hacer clic en un enlace
|
|
menuLinks.forEach(link => {
|
|
link.addEventListener('click', closeMenu);
|
|
});
|
|
|
|
// Cerrar menú con tecla Escape
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') closeMenu();
|
|
});
|
|
|
|
// Año dinámico en el footer
|
|
const yearEl = document.getElementById('current-year');
|
|
if (yearEl) yearEl.textContent = new Date().getFullYear();
|
|
|
|
initServiciosAccordion();
|
|
initNavbarAutoHide();
|
|
|
|
});
|
|
|
|
/* ============================================
|
|
DIAGNÓSTICO INTERACTIVO
|
|
============================================ */
|
|
function initDiagnostico() {
|
|
// Elementos del DOM
|
|
const bienvenida = document.getElementById('diagnostico-bienvenida');
|
|
const quiz = document.getElementById('diagnostico-quiz');
|
|
const resultado = document.getElementById('resultado-contenedor');
|
|
const btnComenzar = document.getElementById('btn-comenzar');
|
|
const contenedorPreguntas = document.getElementById('cuestionario-contenedor');
|
|
const barraCompletado = document.getElementById('progreso-completado');
|
|
const textoProgreso = document.getElementById('progreso-texto');
|
|
|
|
if (!bienvenida || !quiz || !resultado || !btnComenzar) return;
|
|
|
|
// Banco de preguntas
|
|
const preguntas = [
|
|
{
|
|
id: 1,
|
|
titulo: '¿En qué etapa se encuentra hoy tu negocio o proyecto?',
|
|
opciones: [
|
|
{ texto: 'Creando el proyecto desde cero o formalizando una idea reciente.', valor: 'inicio' },
|
|
{ texto: 'Negocio activo, pero con una imagen visual que no refleja la calidad de lo que vendo.', valor: 'identidad' },
|
|
{ texto: 'En pleno movimiento comercial, pero desbordados para contestar consultas a tiempo.', valor: 'automatizacion' },
|
|
{ texto: 'Establecido y con ventas, pero con un techo de crecimiento que necesitamos romper con publicidad.', valor: 'paid' },
|
|
{ texto: 'Empresa consolidada que busca liderazgo de mercado y delegar todo su marketing.', valor: 'corporativo' },
|
|
{ texto: 'Emprendimiento personal que gestiono por mi cuenta y necesito resolver piezas puntuales.', valor: 'consultoria' }
|
|
]
|
|
},
|
|
{
|
|
id: 2,
|
|
titulo: '¿Cuál es tu mayor desafío actual en marketing?',
|
|
opciones: [
|
|
{ texto: 'No tengo una identidad clara ni coherente en mis canales.', valor: 'identidad' },
|
|
{ texto: 'Tengo redes pero no generan consultas ni ventas reales.', valor: 'redes' },
|
|
{ texto: 'Mi sitio web está desactualizado o no convierte visitantes en clientes.', valor: 'web' },
|
|
{ texto: 'No tengo tiempo ni equipo para producir contenido constante.', valor: 'contenido' },
|
|
{ texto: 'Invierto en pauta pero no sé si estoy llegando a la audiencia correcta.', valor: 'paid' },
|
|
{ texto: 'No tengo una estrategia clara y voy tomando decisiones sobre la marcha.', valor: 'consultoria' }
|
|
]
|
|
},
|
|
{
|
|
id: 3,
|
|
titulo: '¿Qué objetivo prioritario tenés para los próximos 6 meses?',
|
|
opciones: [
|
|
{ texto: 'Lanzar mi marca con una identidad sólida y profesional.', valor: 'identidad' },
|
|
{ texto: 'Construir una comunidad activa y fiel en redes sociales.', valor: 'redes' },
|
|
{ texto: 'Tener un sitio web que venda por mí las 24 horas.', valor: 'web' },
|
|
{ texto: 'Automatizar procesos para ganar tiempo y escalar.', valor: 'automatizacion' },
|
|
{ texto: 'Escalar ventas con campañas de publicidad pagada.', valor: 'paid' },
|
|
{ texto: 'Tener un partner estratégico que me acompañe en las decisiones.', valor: 'consultoria' }
|
|
]
|
|
},
|
|
{
|
|
id: 4,
|
|
titulo: '¿Cómo describirías tu inversión actual en marketing?',
|
|
opciones: [
|
|
{ texto: 'Casi nula o muy básica, recién estoy empezando.', valor: 'inicio' },
|
|
{ texto: 'Baja, hago algunas cosas por mi cuenta pero me falta estructura.', valor: 'identidad' },
|
|
{ texto: 'Media, invierto en contenido pero no veo retorno claro.', valor: 'contenido' },
|
|
{ texto: 'Media-alta, ya tengo equipo o freelancers pero falta dirección.', valor: 'consultoria' },
|
|
{ texto: 'Alta, invierto en pauta y necesito maximizar el ROI.', valor: 'paid' },
|
|
{ texto: 'Integral, busco una agencia que gestione todo 360°.', valor: 'corporativo' }
|
|
]
|
|
}
|
|
];
|
|
|
|
// Mapeo de resultados según valor más frecuente
|
|
const resultados = {
|
|
inicio: {
|
|
titulo: 'Plan Esencial',
|
|
diagnostico: 'Estás en una etapa fundacional. Tu marca necesita bases sólidas antes de escalar.',
|
|
solucion: 'Te recomendamos comenzar con un <strong>Plan Esencial</strong>: identidad visual básica, sitio web institucional y gestión inicial de redes. Es el punto de partida ideal para construir con estrategia desde el día uno.'
|
|
},
|
|
identidad: {
|
|
titulo: 'Construcción de Identidad Estratégica',
|
|
diagnostico: 'Tu negocio tiene potencial, pero la percepción visual no está a la altura de lo que ofrecés.',
|
|
solucion: 'Necesitás un trabajo de <strong>Branding integral</strong>: definir el ADN de tu marca, su posicionamiento y el camino para conectar con tu audiencia ideal de manera auténtica y memorable.'
|
|
},
|
|
redes: {
|
|
titulo: 'Comunidades que Convierten',
|
|
diagnostico: 'Tenés presencia pero no estás generando el impacto ni la conexión que buscás.',
|
|
solucion: 'Te recomendamos nuestro servicio de <strong>Gestión de Redes Sociales</strong>: creamos contenido de valor y gestionamos tus canales para construir una comunidad activa, fiel y comprometida con tu marca.'
|
|
},
|
|
web: {
|
|
titulo: 'Experiencias Digitales Impactantes',
|
|
diagnostico: 'Tu sitio web no está cumpliendo su rol como herramienta de venta y conversión.',
|
|
solucion: 'Necesitás un <strong>rediseño o desarrollo web estratégico</strong>: un sitio que no solo se vea bien, sino que genere resultados tangibles y convierta visitantes en clientes.'
|
|
},
|
|
contenido: {
|
|
titulo: 'Historias que Enamoran',
|
|
diagnostico: 'Tenés el negocio andando pero te falta el contenido que conecte emocionalmente con tu audiencia.',
|
|
solucion: 'Te recomendamos nuestro servicio de <strong>Producción de Contenido Audiovisual y Gráfico</strong>: contenido de alta calidad que cuente la historia de tu marca y cautive a tu audiencia.'
|
|
},
|
|
automatizacion: {
|
|
titulo: 'Automatización de Procesos',
|
|
diagnostico: 'El crecimiento te está desbordando. Necesitás sistemas que trabajen por vos.',
|
|
solucion: 'Necesitás <strong>automatizaciones inteligentes</strong>: flujos de WhatsApp, respuestas automáticas y sistemas que liberen tu tiempo para que te enfoques en lo estratégico.'
|
|
},
|
|
paid: {
|
|
titulo: 'Campañas que Escalan Resultados',
|
|
diagnostico: 'Tenés producto y demanda, pero necesitás acelerar el crecimiento con inversión publicitaria inteligente.',
|
|
solucion: 'Te recomendamos <strong>Paid Media en Meta, Google y LinkedIn</strong>: campañas gestionadas estratégicamente para maximizar tu ROI y alcanzar a los clientes correctos.'
|
|
},
|
|
consultoria: {
|
|
titulo: 'Asesoramiento Estratégico Integral',
|
|
diagnostico: 'Tenés recursos y voluntad, pero te falta una brújula estratégica que alinee tus esfuerzos.',
|
|
solucion: 'Necesitás una <strong>Consultoría de Marketing</strong>: te acompañamos en la toma de decisiones clave, optimizando tus recursos y alineando tu marketing con los objetivos de tu negocio.'
|
|
},
|
|
corporativo: {
|
|
titulo: 'Plan Corporativo 360°',
|
|
diagnostico: 'Tu empresa está en un nivel donde necesitás un equipo dedicado que gestione todo de forma integral.',
|
|
solucion: 'Te recomendamos el <strong>Plan Corporativo</strong>: dirección creativa y estratégica, branding integral, pauta multicanal, reportes con KPIs y un equipo dedicado exclusivamente a tu marca.'
|
|
}
|
|
};
|
|
|
|
// Estado
|
|
let preguntaActual = 0;
|
|
const respuestas = [];
|
|
|
|
// Iniciar cuestionario
|
|
btnComenzar.addEventListener('click', () => {
|
|
bienvenida.style.display = 'none';
|
|
quiz.style.display = 'block';
|
|
resultado.style.display = 'none';
|
|
preguntaActual = 0;
|
|
respuestas.length = 0;
|
|
renderizarPregunta();
|
|
});
|
|
|
|
// Renderizar pregunta actual
|
|
function renderizarPregunta() {
|
|
const pregunta = preguntas[preguntaActual];
|
|
const progreso = ((preguntaActual) / preguntas.length) * 100;
|
|
|
|
barraCompletado.style.width = `${progreso}%`;
|
|
textoProgreso.textContent = `Pregunta ${preguntaActual + 1} de ${preguntas.length}`;
|
|
|
|
let html = `
|
|
<div class="pregunta-contenedor">
|
|
<h3 class="pregunta-titulo">${pregunta.titulo}</h3>
|
|
<div class="opciones-contenedor">
|
|
`;
|
|
|
|
pregunta.opciones.forEach((opcion, index) => {
|
|
const seleccionada = respuestas[preguntaActual] === opcion.valor ? 'seleccionada' : '';
|
|
html += `
|
|
<button class="opcion-tarjeta ${seleccionada}" data-valor="${opcion.valor}" data-index="${index}">
|
|
<span class="opcion-numero">${index + 1}</span>
|
|
<span class="opcion-texto">${opcion.texto}</span>
|
|
</button>
|
|
`;
|
|
});
|
|
|
|
html += `</div>`;
|
|
|
|
// Navegación
|
|
html += `
|
|
<div class="pregunta-navegacion">
|
|
<button class="btn-anterior" ${preguntaActual === 0 ? 'style="visibility: hidden;"' : ''}>
|
|
← Anterior
|
|
</button>
|
|
<button class="btn-siguiente" ${!respuestas[preguntaActual] ? 'disabled' : ''}>
|
|
${preguntaActual === preguntas.length - 1 ? 'Ver resultado' : 'Siguiente →'}
|
|
</button>
|
|
</div>
|
|
`;
|
|
html += `</div>`;
|
|
|
|
contenedorPreguntas.innerHTML = html;
|
|
|
|
// Event listeners de opciones
|
|
const opciones = contenedorPreguntas.querySelectorAll('.opcion-tarjeta');
|
|
opciones.forEach(opcion => {
|
|
opcion.addEventListener('click', () => {
|
|
const valor = opcion.dataset.valor;
|
|
respuestas[preguntaActual] = valor;
|
|
|
|
// Actualizar UI
|
|
opciones.forEach(o => o.classList.remove('seleccionada'));
|
|
opcion.classList.add('seleccionada');
|
|
|
|
// Habilitar botón siguiente
|
|
const btnSiguiente = contenedorPreguntas.querySelector('.btn-siguiente');
|
|
if (btnSiguiente) btnSiguiente.disabled = false;
|
|
});
|
|
});
|
|
|
|
// Navegación
|
|
const btnAnterior = contenedorPreguntas.querySelector('.btn-anterior');
|
|
const btnSiguiente = contenedorPreguntas.querySelector('.btn-siguiente');
|
|
|
|
if (btnAnterior) {
|
|
btnAnterior.addEventListener('click', () => {
|
|
if (preguntaActual > 0) {
|
|
preguntaActual--;
|
|
renderizarPregunta();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (btnSiguiente) {
|
|
btnSiguiente.addEventListener('click', () => {
|
|
if (respuestas[preguntaActual]) {
|
|
if (preguntaActual < preguntas.length - 1) {
|
|
preguntaActual++;
|
|
renderizarPregunta();
|
|
} else {
|
|
mostrarResultado();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Calcular y mostrar resultado
|
|
function mostrarResultado() {
|
|
// Contar frecuencia de cada valor
|
|
const conteo = {};
|
|
respuestas.forEach(valor => {
|
|
conteo[valor] = (conteo[valor] || 0) + 1;
|
|
});
|
|
|
|
// Obtener el valor más frecuente
|
|
let valorGanador = Object.keys(conteo).reduce((a, b) =>
|
|
conteo[a] > conteo[b] ? a : b
|
|
);
|
|
|
|
const resultadoData = resultados[valorGanador] || resultados.consultoria;
|
|
|
|
// Actualizar barra al 100%
|
|
barraCompletado.style.width = '100%';
|
|
textoProgreso.textContent = `Completado`;
|
|
|
|
// Ocultar quiz y mostrar resultado
|
|
quiz.style.display = 'none';
|
|
resultado.style.display = 'block';
|
|
|
|
resultado.innerHTML = `
|
|
<div class="resultado-encabezado">
|
|
<h3 class="resultado-titulo">${resultadoData.titulo}</h3>
|
|
<p class="resultado-subtitulo">Según tus respuestas, esto es lo que más te conviene:</p>
|
|
</div>
|
|
<p class="resultado-diagnostico">${resultadoData.diagnostico}</p>
|
|
<div class="resultado-solucion">
|
|
${resultadoData.solucion}
|
|
</div>
|
|
<div class="resultado-acciones">
|
|
<a href="contacto.html" class="resultado-cta">
|
|
Hablemos sobre esto
|
|
<span aria-hidden="true">→</span>
|
|
</a>
|
|
<button class="btn-reiniciar" id="btn-reiniciar-resultado">
|
|
<span aria-hidden="true">↻</span>
|
|
Reiniciar diagnóstico
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
// Event listener del botón reiniciar
|
|
const btnReiniciar = document.getElementById('btn-reiniciar-resultado');
|
|
if (btnReiniciar) {
|
|
btnReiniciar.addEventListener('click', () => {
|
|
resultado.style.display = 'none';
|
|
bienvenida.style.display = 'flex';
|
|
preguntaActual = 0;
|
|
respuestas.length = 0;
|
|
barraCompletado.style.width = '0%';
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inicializar cuando el DOM esté listo
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initDiagnostico();
|
|
});
|
|
/* ============================================
|
|
ANIMACIÓN AUTOMÁTICA DE METODOLOGÍA
|
|
============================================ */
|
|
function initMetodologiaAnimation() {
|
|
const section = document.getElementById('metodologia');
|
|
if (!section) return;
|
|
|
|
const pasos = section.querySelectorAll('.paso-item');
|
|
if (pasos.length === 0) return;
|
|
|
|
let pasoActual = 0;
|
|
let intervalo;
|
|
const DURACION_PASO = 2500; // 2.5 segundos por paso
|
|
|
|
// Función para actualizar los estados
|
|
function actualizarEstados() {
|
|
pasos.forEach((paso, index) => {
|
|
// Remover todas las clases de estado
|
|
paso.classList.remove('active', 'previous');
|
|
|
|
// Calcular el índice del paso anterior (con loop)
|
|
const pasoAnterior = (pasoActual - 1 + pasos.length) % pasos.length;
|
|
|
|
// Asignar estados
|
|
if (index === pasoActual) {
|
|
paso.classList.add('active');
|
|
} else if (index === pasoAnterior) {
|
|
paso.classList.add('previous');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Función para avanzar al siguiente paso
|
|
function avanzarPaso() {
|
|
pasoActual = (pasoActual + 1) % pasos.length;
|
|
actualizarEstados();
|
|
}
|
|
|
|
// Función para iniciar la animación
|
|
function iniciarAnimacion() {
|
|
// Inicializar con el primer paso activo
|
|
actualizarEstados();
|
|
|
|
// Iniciar el intervalo
|
|
intervalo = setInterval(avanzarPaso, DURACION_PASO);
|
|
}
|
|
|
|
// Función para detener la animación
|
|
function detenerAnimacion() {
|
|
clearInterval(intervalo);
|
|
}
|
|
|
|
// IntersectionObserver para detectar cuando la sección es visible
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
// La sección está visible: iniciar animación
|
|
iniciarAnimacion();
|
|
} else {
|
|
// La sección no está visible: detener animación
|
|
detenerAnimacion();
|
|
}
|
|
});
|
|
}, {
|
|
threshold: 0.3 // Se activa cuando el 30% de la sección es visible
|
|
});
|
|
|
|
// Observar la sección
|
|
observer.observe(section);
|
|
|
|
// Inicializar el primer estado (sin animación aún)
|
|
actualizarEstados();
|
|
}
|
|
/* ============================================
|
|
EFECTO FOCO EN TARJETAS DE PLANES
|
|
============================================ */
|
|
function initPlanesFocusEffect() {
|
|
const grid = document.getElementById('planes-grid');
|
|
if (!grid) return;
|
|
|
|
const cards = grid.querySelectorAll('.plan-card');
|
|
if (cards.length === 0) return;
|
|
|
|
cards.forEach(card => {
|
|
card.addEventListener('mouseenter', () => {
|
|
grid.classList.add('is-hovering');
|
|
card.classList.add('is-focused');
|
|
});
|
|
|
|
card.addEventListener('mouseleave', () => {
|
|
grid.classList.remove('is-hovering');
|
|
card.classList.remove('is-focused');
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
/* ============================================
|
|
CARRUSEL AUTOMÁTICO DE PORTFOLIO
|
|
============================================ */
|
|
function initPortfolioCarousel() {
|
|
const slides = document.querySelectorAll('.carousel-slide');
|
|
const indicators = document.querySelectorAll('.indicator');
|
|
|
|
if (slides.length === 0) return;
|
|
|
|
let currentSlide = 0;
|
|
const INTERVAL = 4000; // 4 segundos
|
|
let intervalId;
|
|
|
|
function goToSlide(index) {
|
|
// Remover clase active de todos
|
|
slides.forEach(slide => slide.classList.remove('active'));
|
|
indicators.forEach(indicator => indicator.classList.remove('active'));
|
|
|
|
// Activar el slide actual
|
|
slides[index].classList.add('active');
|
|
indicators[index].classList.add('active');
|
|
|
|
currentSlide = index;
|
|
}
|
|
|
|
function nextSlide() {
|
|
const next = (currentSlide + 1) % slides.length;
|
|
goToSlide(next);
|
|
}
|
|
|
|
function startCarousel() {
|
|
intervalId = setInterval(nextSlide, INTERVAL);
|
|
}
|
|
|
|
function stopCarousel() {
|
|
clearInterval(intervalId);
|
|
}
|
|
|
|
// Click en indicadores
|
|
indicators.forEach((indicator, index) => {
|
|
indicator.addEventListener('click', () => {
|
|
stopCarousel();
|
|
goToSlide(index);
|
|
startCarousel();
|
|
});
|
|
});
|
|
|
|
// Iniciar carrusel
|
|
startCarousel();
|
|
}
|
|
|
|
/* ============================================
|
|
TIRA MARQUEE CON HOVER INTERACTIVO
|
|
============================================ */
|
|
function initMarqueeHover() {
|
|
const track = document.getElementById('marquee-track');
|
|
if (!track) return;
|
|
|
|
const icons = track.querySelectorAll('.marquee-icon');
|
|
if (icons.length === 0) return;
|
|
|
|
icons.forEach(icon => {
|
|
icon.addEventListener('mouseenter', () => {
|
|
track.classList.add('is-slow');
|
|
});
|
|
|
|
icon.addEventListener('mouseleave', () => {
|
|
track.classList.remove('is-slow');
|
|
});
|
|
});
|
|
}
|
|
|
|
/*==========================================
|
|
nav
|
|
=============================================*/
|
|
function initNavbarAutoHide() {
|
|
console.log('🔵 initNavbarAutoHide: iniciando...');
|
|
|
|
// Reintentar hasta que el header exista (por si se carga async)
|
|
let attempts = 0;
|
|
const MAX_ATTEMPTS = 50; // 5 segundos
|
|
|
|
const tryInit = () => {
|
|
const header = document.querySelector('.site-header');
|
|
console.log('🔵 intento', attempts, '- header:', header);
|
|
|
|
if (!header) {
|
|
attempts++;
|
|
if (attempts < MAX_ATTEMPTS) {
|
|
setTimeout(tryInit, 100);
|
|
} else {
|
|
console.error('❌ Navbar: no se encontró .site-header');
|
|
}
|
|
return;
|
|
}
|
|
|
|
console.log('✅ Navbar encontrado, registrando scroll listener...');
|
|
|
|
const hero = document.getElementById('hero');
|
|
const UMBRAL_HERO = 100; // píxeles mínimos antes de activar el auto-hide
|
|
let lastScrollY = window.scrollY;
|
|
let ticking = false;
|
|
|
|
const updateHeader = () => {
|
|
const currentScrollY = window.scrollY;
|
|
const delta = currentScrollY - lastScrollY;
|
|
|
|
console.log('📜 scrollY:', currentScrollY, '| delta:', delta);
|
|
|
|
// 1. Si el menú fullscreen está abierto: siempre visible
|
|
if (document.body.classList.contains('menu-open')) {
|
|
header.classList.remove('is-hidden');
|
|
lastScrollY = currentScrollY;
|
|
ticking = false;
|
|
return;
|
|
}
|
|
|
|
// 2. Si estamos dentro del hero (primeros 100px): siempre visible
|
|
if (currentScrollY <= UMBRAL_HERO) {
|
|
header.classList.remove('is-hidden');
|
|
lastScrollY = currentScrollY;
|
|
ticking = false;
|
|
return;
|
|
}
|
|
|
|
// 3. Ignorar micro-movimientos
|
|
if (Math.abs(delta) < 5) {
|
|
ticking = false;
|
|
return;
|
|
}
|
|
|
|
// 4. Scroll hacia abajo → ocultar
|
|
if (delta > 0) {
|
|
header.classList.add('is-hidden');
|
|
console.log(' → ocultando header');
|
|
}
|
|
// 5. Scroll hacia arriba → mostrar
|
|
else {
|
|
header.classList.remove('is-hidden');
|
|
console.log(' → mostrando header');
|
|
}
|
|
|
|
lastScrollY = currentScrollY;
|
|
ticking = false;
|
|
};
|
|
|
|
window.addEventListener('scroll', () => {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(updateHeader);
|
|
ticking = true;
|
|
}
|
|
}, { passive: true });
|
|
|
|
// Resetear al redimensionar
|
|
window.addEventListener('resize', () => {
|
|
lastScrollY = window.scrollY;
|
|
});
|
|
|
|
console.log('✅ Navbar auto-hide listo');
|
|
};
|
|
|
|
tryInit();
|
|
}
|
|
|
|
// Inicializar cuestionario al cargar la página
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Eliminar el bloque del diagnóstico (ya se maneja en initDiagnostico)
|
|
initMetodologiaAnimation();
|
|
initPlanesFocusEffect();
|
|
initPortfolioCarousel();
|
|
initMarqueeHover(); // ← además, esta NUNCA se está llamando
|
|
}); |