/* ═══════════════════════════════════════════════════ AXISVIA LANDING V3 — main.js ═══════════════════════════════════════════════════ */ /* ── Navbar scroll effect ── */ const navbar = document.getElementById('navbar'); window.addEventListener('scroll', () => { navbar.classList.toggle('scrolled', window.scrollY > 40); }); /* ── Mobile menu ── */ const hamburger = document.getElementById('hamburger'); const mobileMenu = document.getElementById('mobileMenu'); hamburger.addEventListener('click', () => mobileMenu.classList.toggle('active')); document.querySelectorAll('.mobile-link').forEach(link => { link.addEventListener('click', () => mobileMenu.classList.remove('active')); }); /* ── Scroll reveal ── */ const revealObserver = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); }); }, { threshold: 0.1 }); document.querySelectorAll('.reveal').forEach(el => revealObserver.observe(el)); /* ── Animated counters ── */ function animateCounter(el, target, duration = 2000) { const start = performance.now(); const update = (now) => { const p = Math.min((now - start) / duration, 1); const eased = 1 - Math.pow(1 - p, 3); el.textContent = Math.floor(target * eased).toLocaleString('es-MX'); if (p < 1) requestAnimationFrame(update); }; requestAnimationFrame(update); } const counterObserver = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { const target = parseInt(e.target.dataset.target); animateCounter(e.target, target); counterObserver.unobserve(e.target); } }); }, { threshold: 0.5 }); document.querySelectorAll('.counter').forEach(el => counterObserver.observe(el)); /* ── Hero counter (starts immediately) ── */ const heroCounter = document.getElementById('heroCounter'); if (heroCounter) animateCounter(heroCounter, 10486623, 3000); /* ── Product tabs ── */ document.querySelectorAll('.product-tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.product-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.product-panel').forEach(p => p.classList.remove('active')); tab.classList.add('active'); const panel = document.getElementById('panel-' + tab.dataset.tab); if (panel) panel.classList.add('active'); }); }); /* ── Form submit → redirect to /thankyou (completes GA4 funnel) ── */ function handleFormSubmit(e) { e.preventDefault(); // Fire GA4 lead conversion before redirect if (typeof gtag !== 'undefined') { gtag('event', 'generate_lead', { event_category: 'lead_funnel', event_label: 'early_access_form', currency: 'MXN', value: 1 }); } // Give GA ~300ms to fire before navigating setTimeout(() => { window.location.href = '/thankyou'; }, 300); return false; } /* ═══════════════════════════════════════════════════ BRAIN VIZ 3D — Full-screen hero background Adapted from brain-viz-3d.html by Qwen - No UI overlays, no category filter - Large random tag pool, different selection each load - Drag to rotate, scroll to zoom, mouse attraction ═══════════════════════════════════════════════════ */ (function () { const canvas = document.getElementById('brainCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); // ── Full tag pool — vastness of the legal corpus ── const TAG_POOL = [ // Amparo / SCJN { tag: '#AmparoDirecto', count: 143000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#AmparoIndirecto', count: 125000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#SuspensiónActos', count: 67000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#DerechosHumanos', count: 89000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#ControlConvencional', count: 34000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#InterésLegítimo', count: 28000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#SentenciaDefinitiva', count: 52000, color: '#00c2ff', cat: 'Amparo' }, { tag: '#RecursoRevisión', count: 45000, color: '#00c2ff', cat: 'Amparo' }, // SCJN / Jurisprudencia { tag: '#Jurisprudencia', count: 311000, color: '#0891b2', cat: 'SCJN' }, { tag: '#TesisAislada', count: 189000, color: '#0891b2', cat: 'SCJN' }, { tag: '#ContradicciónTesis', count: 34000, color: '#0891b2', cat: 'SCJN' }, { tag: '#Precedente', count: 22000, color: '#0891b2', cat: 'SCJN' }, { tag: '#Jurisprudencia11Época', count: 88000, color: '#0891b2', cat: 'SCJN' }, { tag: '#AcciónInconstitucional', count: 8000, color: '#0891b2', cat: 'SCJN' }, { tag: '#ContradicciónCriteria', count: 18000, color: '#0891b2', cat: 'SCJN' }, // Leyes / Constitución { tag: '#Art1Constitucional', count: 95000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#Artículo14', count: 78000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#Artículo16', count: 112000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#Artículo27', count: 45000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#Artículo123', count: 67000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#CódigoCivilFederal', count: 34000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#CódigoPenalFederal', count: 42000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#LFT', count: 58000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#CNPP', count: 71000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#CFF', count: 39000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#LFPDPPP', count: 12000, color: '#06b6d4', cat: 'Leyes' }, { tag: '#LeyAmparo', count: 55000, color: '#06b6d4', cat: 'Leyes' }, // Civil { tag: '#JuicioCivil', count: 45000, color: '#c9a227', cat: 'Civil' }, { tag: '#Arrendamiento', count: 28000, color: '#c9a227', cat: 'Civil' }, { tag: '#Propiedad', count: 35000, color: '#c9a227', cat: 'Civil' }, { tag: '#Contratos', count: 52000, color: '#c9a227', cat: 'Civil' }, { tag: '#ResponsabilidadCivil', count: 31000, color: '#c9a227', cat: 'Civil' }, { tag: '#Sucesiones', count: 19000, color: '#c9a227', cat: 'Civil' }, { tag: '#Hipoteca', count: 16000, color: '#c9a227', cat: 'Civil' }, { tag: '#Usucapión', count: 12000, color: '#c9a227', cat: 'Civil' }, { tag: '#DañoMoral', count: 21000, color: '#c9a227', cat: 'Civil' }, { tag: '#Donación', count: 9000, color: '#c9a227', cat: 'Civil' }, // Penal { tag: '#Homicidio', count: 38000, color: '#ef4444', cat: 'Penal' }, { tag: '#Robo', count: 67000, color: '#ef4444', cat: 'Penal' }, { tag: '#Lesiones', count: 45000, color: '#ef4444', cat: 'Penal' }, { tag: '#Fraude', count: 29000, color: '#ef4444', cat: 'Penal' }, { tag: '#ViolenciaFamiliar', count: 34000, color: '#ef4444', cat: 'Penal' }, { tag: '#Narcotráfico', count: 21000, color: '#ef4444', cat: 'Penal' }, { tag: '#LavadoDinero', count: 12000, color: '#ef4444', cat: 'Penal' }, { tag: '#AbusoSexual', count: 18000, color: '#ef4444', cat: 'Penal' }, { tag: '#SecuestroExtorsión', count: 14000, color: '#ef4444', cat: 'Penal' }, { tag: '#PorteArmas', count: 11000, color: '#ef4444', cat: 'Penal' }, { tag: '#CorrupciónFuncionario', count: 8000, color: '#ef4444', cat: 'Penal' }, // Laboral { tag: '#DespidoInjustificado', count: 89000, color: '#00b87c', cat: 'Laboral' }, { tag: '#Reinstalación', count: 34000, color: '#00b87c', cat: 'Laboral' }, { tag: '#Indemnización', count: 67000, color: '#00b87c', cat: 'Laboral' }, { tag: '#Aguinaldo', count: 28000, color: '#00b87c', cat: 'Laboral' }, { tag: '#HorasExtras', count: 42000, color: '#00b87c', cat: 'Laboral' }, { tag: '#AcosoLaboral', count: 15000, color: '#00b87c', cat: 'Laboral' }, { tag: '#SeguridadSocial', count: 38000, color: '#00b87c', cat: 'Laboral' }, { tag: '#NuevoSistemaLaboral', count: 22000, color: '#00b87c', cat: 'Laboral' }, { tag: '#ConciliaciónLaboral', count: 17000, color: '#00b87c', cat: 'Laboral' }, // Familiar { tag: '#Divorcio', count: 56000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#PensiónAlimenticia', count: 78000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#GuardaCustodia', count: 45000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#PatriaPotestad', count: 32000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#Adopción', count: 12000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#RégimenVisitas', count: 38000, color: '#7c3aed', cat: 'Familiar' }, { tag: '#Filiación', count: 19000, color: '#7c3aed', cat: 'Familiar' }, // Mercantil { tag: '#Pagaré', count: 43000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#Cheque', count: 38000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#Quiebra', count: 15000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#SociedadMercantil', count: 29000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#PropiedadIndustrial', count: 18000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#ContratoMercantil', count: 34000, color: '#f59e0b', cat: 'Mercantil' }, { tag: '#CompetenciaDesleal', count: 11000, color: '#f59e0b', cat: 'Mercantil' }, // Administrativo / Fiscal { tag: '#SAT', count: 61000, color: '#10b981', cat: 'Fiscal' }, { tag: '#DefensaFiscal', count: 44000, color: '#10b981', cat: 'Fiscal' }, { tag: '#JuicioContencioso', count: 39000, color: '#10b981', cat: 'Fiscal' }, { tag: '#NullidadAdministrativa', count: 28000, color: '#10b981', cat: 'Fiscal' }, { tag: '#Licitación', count: 16000, color: '#10b981', cat: 'Fiscal' }, { tag: '#DerechoAduanero', count: 14000, color: '#10b981', cat: 'Fiscal' }, // Locales / Estatales { tag: '#STJJalisco', count: 80574, color: '#8b5cf6', cat: 'Local' }, { tag: '#PJCdMx', count: 150000, color: '#8b5cf6', cat: 'Local' }, { tag: '#PJNuevoLeón', count: 90000, color: '#8b5cf6', cat: 'Local' }, { tag: '#PJEsMéxico', count: 120000, color: '#8b5cf6', cat: 'Local' }, { tag: '#PJPuebla', count: 60000, color: '#8b5cf6', cat: 'Local' }, { tag: '#SentenciaLocal', count: 70000, color: '#8b5cf6', cat: 'Local' }, // Propiedad / Agrario { tag: '#PropiedadIntelectual', count: 24000, color: '#ec4899', cat: 'IP' }, { tag: '#DerechoAutor', count: 18000, color: '#ec4899', cat: 'IP' }, { tag: '#Marcas', count: 21000, color: '#ec4899', cat: 'IP' }, { tag: '#DerechodAgua', count: 9000, color: '#ec4899', cat: 'IP' }, { tag: '#DerechoAgrario', count: 31000, color: '#ec4899', cat: 'IP' }, { tag: '#Ejido', count: 25000, color: '#ec4899', cat: 'IP' }, ]; // Randomly pick N tags (different each page load) function pickRandomTags(pool, n) { const shuffled = [...pool].sort(() => Math.random() - 0.5); return shuffled.slice(0, n); } let W, H; let nodes = []; let connections = []; let mouseX = W / 2 || 0, mouseY = H / 2 || 0; let targetMouseX = mouseX, targetMouseY = mouseY; let isDragging = false, lastMouseX = 0, lastMouseY = 0; let rotX = 0, rotY = 0, targetRotX = 0, targetRotY = 0; let zoom = 1, targetZoom = 1; let time = 0; function resize() { W = canvas.width = canvas.offsetWidth; H = canvas.height = canvas.offsetHeight; buildNodes(); } function buildNodes() { // Pick 42 random tags for this session const selected = pickRandomTags(TAG_POOL, 42); nodes = selected.map((data) => { const x = W * 0.15 + Math.random() * W * 0.7; const y = H * 0.1 + Math.random() * H * 0.8; const z = (Math.random() - 0.5) * 350; const orbitSpeed = (0.00015 + Math.random() * 0.00025) * (Math.random() > 0.5 ? 1 : -1); const orbitR = 40 + Math.random() * 120; const angle = Math.random() * Math.PI * 2; return { ...data, x, y, z, baseX: x, baseY: y, baseZ: z, vx: 0, vy: 0, vz: 0, radius: 14 + Math.log10(data.count + 1) * 4 + Math.random() * 6, orbitAngle: angle, orbitRadius: orbitR, orbitSpeed, pulseOffset: Math.random() * Math.PI * 2, pulseSpeed: 0.0015 + Math.random() * 0.002, }; }); buildConnections(); } function buildConnections() { connections = []; for (let i = 0; i < nodes.length; i++) { const a = nodes[i]; const nearby = []; for (let j = i + 1; j < nodes.length; j++) { const b = nodes[j]; const d = Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z); if (d < 350) nearby.push({ node: b, d }); } nearby.sort((x, y) => x.d - y.d); // Connect to 5 nearest for denser neural network look nearby.slice(0, 5).forEach(({ node: b, d }) => { connections.push({ a, b, same: a.cat === b.cat, d }); }); } } function project(x, y, z) { const s = zoom * (1 + z / 900); return { px: x * s + (1 - s) * W / 2, py: y * s + (1 - s) * H / 2, s }; } function hexRgba(hex, a) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r},${g},${b},${a})`; } function draw() { ctx.clearRect(0, 0, W, H); // Subtle grid ctx.strokeStyle = 'rgba(42,54,85,0.07)'; ctx.lineWidth = 0.5; const gs = 90 * zoom; for (let x = 0; x < W; x += gs) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); } for (let y = 0; y < H; y += gs) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); } // Neural axon connections — thin, bright, neuron-style connections.forEach(({ a, b, same }) => { const pa = project(a.x, a.y, a.z); const pb = project(b.x, b.y, b.z); const dist = Math.hypot(pa.px - pb.px, pa.py - pb.py); if (dist > 500) return; // skip if too stretched const avgZ = (a.z + b.z) / 2; const depthA = 0.4 + (avgZ + 175) / 350 * 0.55; // Neural connections: white/node-color glow, very thin const baseAlpha = same ? 0.55 : 0.28; const alpha = Math.max(0.05, Math.min(0.65, baseAlpha * depthA * (1 - dist / 500))); const col = same ? a.color : '#8ba4cc'; const g = ctx.createLinearGradient(pa.px, pa.py, pb.px, pb.py); g.addColorStop(0, hexRgba(col, alpha * 0.4)); g.addColorStop(0.5, hexRgba(col, alpha)); g.addColorStop(1, hexRgba(col, alpha * 0.4)); ctx.beginPath(); ctx.moveTo(pa.px, pa.py); ctx.lineTo(pb.px, pb.py); ctx.strokeStyle = g; ctx.lineWidth = same ? 0.8 : 0.5; ctx.stroke(); }); // Floating particles for (let i = 0; i < 25; i++) { const angle = time * 0.0004 + i * 0.4; const r = 90 + i * 18; const px = W / 2 + Math.cos(angle) * r; const py = H / 2 + Math.sin(angle) * r; const a = 0.15 + Math.sin(time * 0.0018 + i) * 0.08; ctx.beginPath(); ctx.arc(px, py, 1.2, 0, Math.PI * 2); ctx.fillStyle = `rgba(0,212,170,${a})`; ctx.fill(); } // Nodes (sorted by z for depth) [...nodes].sort((a, b) => a.z - b.z).forEach(node => { const { px, py, s } = project(node.x, node.y, node.z); const pulse = 1 + Math.sin(time * node.pulseSpeed + node.pulseOffset) * 0.12; const depthA = 0.45 + (node.z + 175) / 350 * 0.5; // Glow const glowR = node.radius * 2.5 * s * pulse; const glow = ctx.createRadialGradient(px, py, 0, px, py, glowR); glow.addColorStop(0, hexRgba(node.color, 0.22 * depthA)); glow.addColorStop(0.5, hexRgba(node.color, 0.10 * depthA)); glow.addColorStop(1, 'transparent'); ctx.beginPath(); ctx.arc(px, py, glowR, 0, Math.PI * 2); ctx.fillStyle = glow; ctx.fill(); // Core circle const nr = node.radius * s * pulse; ctx.beginPath(); ctx.arc(px, py, nr, 0, Math.PI * 2); ctx.fillStyle = hexRgba(node.color, 0.18 * depthA); ctx.strokeStyle = hexRgba(node.color, 0.7 * depthA); ctx.lineWidth = 1.4 * s; ctx.fill(); ctx.stroke(); // Tag label const fontSize = Math.max(9, Math.round(10 * s * depthA)); ctx.font = `500 ${fontSize}px Inter, sans-serif`; ctx.fillStyle = `rgba(220,235,255,${0.75 * depthA})`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; let label = node.tag; if (label.length > 17) label = label.slice(0, 14) + '…'; ctx.fillText(label, px, py); }); } function update() { time++; mouseX += (targetMouseX - mouseX) * 0.07; mouseY += (targetMouseY - mouseY) * 0.07; zoom += (targetZoom - zoom) * 0.08; rotX += (targetRotX - rotX) * 0.05; rotY += (targetRotY - rotY) * 0.05; nodes.forEach(n => { n.orbitAngle += n.orbitSpeed; const ox = Math.cos(n.orbitAngle) * n.orbitRadius; const oy = Math.sin(n.orbitAngle) * n.orbitRadius; const tx = n.baseX + ox; const ty = n.baseY + oy; // Gentle mouse attraction (only when close) const dx = mouseX - n.x, dy = mouseY - n.y; const dist = Math.hypot(dx, dy); if (dist < 280 && !isDragging) { const f = (280 - dist) / 280; n.vx += dx * f * 0.0004; n.vy += dy * f * 0.0004; } n.vx += (tx - n.x) * 0.0018; n.vy += (ty - n.y) * 0.0018; n.vz += (n.baseZ - n.z) * 0.002; n.vx *= 0.94; n.vy *= 0.94; n.vz *= 0.94; n.x += n.vx; n.y += n.vy; n.z += n.vz; }); } function loop() { draw(); update(); requestAnimationFrame(loop); } // ── Events ── canvas.addEventListener('mousemove', e => { const r = canvas.getBoundingClientRect(); targetMouseX = e.clientX - r.left; targetMouseY = e.clientY - r.top; if (isDragging) { const dx = e.clientX - lastMouseX; const dy = e.clientY - lastMouseY; nodes.forEach(n => { n.baseX += dx * 0.6; n.baseY += dy * 0.6; }); lastMouseX = e.clientX; lastMouseY = e.clientY; } }); canvas.addEventListener('mousedown', e => { isDragging = true; lastMouseX = e.clientX; lastMouseY = e.clientY; canvas.style.cursor = 'grabbing'; }); window.addEventListener('mouseup', () => { isDragging = false; canvas.style.cursor = 'default'; }); // NO wheel listener — let page scroll work natively // Touch: only track position for mouse attraction, no zoom capture canvas.addEventListener('touchmove', e => { if (e.touches.length === 1) { const t = e.touches[0]; const r = canvas.getBoundingClientRect(); targetMouseX = t.clientX - r.left; targetMouseY = t.clientY - r.top; } // Do NOT preventDefault — let native scroll work }, { passive: true }); window.addEventListener('resize', resize); // Init resize(); loop(); })();