// ===================================================== // ROC AGENCY - SISTEMA RESPONSIVO E OTIMIZAÇÃO DE PERFORMANCE // ===================================================== (function() { 'use strict'; // Configuração global de performance - OTIMIZADA var PERFORMANCE_CONFIG = { mobile: { maxParticles: 8, // Reduzido de 20 targetFPS: 20, // Reduzido de 30 connectionDistance: 60, // Reduzido de 80 trailLength: 2 // Reduzido de 5 }, desktop: { maxParticles: 15, // Reduzido de 50 targetFPS: 30, // Reduzido de 45 connectionDistance: 80, // Reduzido de 120 trailLength: 3 // Reduzido de 8 }, lowMemory: { maxParticles: 3, // Reduzido de 10 targetFPS: 15, // Reduzido de 25 connectionDistance: 40, // Reduzido de 60 trailLength: 1 // Reduzido de 3 } }; // Utilitários globais var Utils = { debounce: function(func, wait) { var timeout; return function executedFunction() { var args = arguments; var context = this; var later = function() { clearTimeout(timeout); func.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, throttle: function(func, limit) { var inThrottle; return function() { var args = arguments; var context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(function() { inThrottle = false; }, limit); } }; }, isPageVisible: function() { return document.visibilityState === 'visible'; }, getPerformanceLevel: function() { var ua = navigator.userAgent; var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua); var isLowMemory = navigator.deviceMemory && navigator.deviceMemory < 4; var isSlowConnection = navigator.connection && (navigator.connection.effectiveType === 'slow-2g' || navigator.connection.effectiveType === '2g' || navigator.connection.saveData); if (isLowMemory || isSlowConnection) return 'lowMemory'; if (isMobile) return 'mobile'; return 'desktop'; } }; function ResponsiveController() { var self = this; this.deviceInfo = this.detectDevice(); this.connectionInfo = this.getConnectionInfo(); this.performancePreferences = this.detectPerformancePreferences(); this.init(); } ResponsiveController.prototype = { // Device Detection and Capabilities detectDevice: function() { var ua = navigator.userAgent; var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua); var isTablet = /iPad|Android(?=.*Tablet)|Windows(?=.*Touch)/i.test(ua); var isFoldable = screen.width > screen.height && window.matchMedia('(orientation: landscape)').matches; return { isMobile: isMobile && !isTablet, isTablet: isTablet, isDesktop: !isMobile && !isTablet, isFoldable: isFoldable, hasTouch: 'ontouchstart' in window, pixelRatio: window.devicePixelRatio || 1, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, colorDepth: screen.colorDepth, isRetina: window.devicePixelRatio > 1.5 }; }, // Network and Performance Detection getConnectionInfo: function() { if ('connection' in navigator) { var conn = navigator.connection; return { effectiveType: conn.effectiveType, downlink: conn.downlink, rtt: conn.rtt, saveData: conn.saveData, isSlowConnection: conn.effectiveType === 'slow-2g' || conn.effectiveType === '2g' || conn.saveData }; } return { effectiveType: 'unknown', isSlowConnection: false }; }, // Performance Preferences Detection detectPerformancePreferences: function() { return { prefersReducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches, prefersHighContrast: window.matchMedia('(prefers-contrast: high)').matches, prefersColorScheme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' }; }, // Initialize all responsive features init: function() { this.setupViewportAdaptation(); this.setupPerformanceOptimizations(); this.setupImageOptimization(); this.setupTouchOptimizations(); this.setupAccessibility(); this.setupResizeObserver(); this.initNavigationSystem(); this.initScrollEffects(); this.initIntersectionObserver(); this.initInteractiveElements(); this.initHolographicSystem(); this.initParticleSystem(); }, // Viewport-based adaptations setupViewportAdaptation: function() { var setViewportProperties = function() { var vw = window.innerWidth; var vh = window.innerHeight; document.documentElement.style.setProperty('--vw', (vw * 0.01) + 'px'); document.documentElement.style.setProperty('--vh', (vh * 0.01) + 'px'); document.documentElement.style.setProperty('--viewport-ratio', vw / vh); // Adaptive spacing based on viewport size var minSpacing = Math.min(vw, vh); document.documentElement.style.setProperty('--adaptive-spacing', (minSpacing * 0.02) + 'px'); }; setViewportProperties(); window.addEventListener('resize', Utils.debounce(setViewportProperties, 250)); window.addEventListener('orientationchange', function() { setTimeout(setViewportProperties, 100); }); }, // Performance-based optimizations setupPerformanceOptimizations: function() { if (this.connectionInfo.isSlowConnection || this.deviceInfo.isMobile) { document.documentElement.classList.add('low-performance'); this.adaptImageQuality(0.8); } else { document.documentElement.classList.add('high-performance'); this.adaptImageQuality(1.0); } if (navigator.deviceMemory && navigator.deviceMemory < 4) { document.documentElement.classList.add('low-memory'); } }, adaptImageQuality: function(quality) { document.documentElement.style.setProperty('--image-quality', quality); }, // Placeholder methods for other functionalities setupImageOptimization: function() {}, setupTouchOptimizations: function() {}, setupAccessibility: function() {}, setupResizeObserver: function() {}, initNavigationSystem: function() {}, initScrollEffects: function() {}, initIntersectionObserver: function() {}, initInteractiveElements: function() {}, // Sistema Holográfico Avançado com Métricas Animadas initHolographicSystem: function() { var self = this; var metricsUpdateInterval = null; var isVisible = true; var metrics = { roas: { current: 8.9, min: 7.2, max: 12.4, suffix: 'x' }, ctr: { current: 4.2, min: 3.1, max: 6.8, suffix: '%' }, conv: { current: 12.8, min: 9.5, max: 18.2, suffix: '%' }, cpc: { current: 0.32, min: 0.24, max: 0.45, prefix: '$' } }; // Pausar animações quando página não está visível document.addEventListener('visibilitychange', function() { isVisible = !document.hidden; if (isVisible && !metricsUpdateInterval) { startMetricsAnimation(); } else if (!isVisible && metricsUpdateInterval) { clearInterval(metricsUpdateInterval); metricsUpdateInterval = null; } }); function startMetricsAnimation() { Object.keys(metrics).forEach(function(metricKey) { var element = document.querySelector('[data-metric="' + metricKey + '"]'); if (element) { self.animateMetric(element, metrics[metricKey]); } }); } // Iniciar apenas se página está visível if (Utils.isPageVisible()) { startMetricsAnimation(); } }, animateMetric: function(element, config) { var self = this; // Usar requestAnimationFrame para animação mais suave function updateMetric() { if (!Utils.isPageVisible()) { setTimeout(updateMetric, 5000); // Verificar novamente em 5s return; } var variation = (Math.random() - 0.5) * 0.3; var newValue = Math.max(config.min, Math.min(config.max, config.current + (config.current * variation))); var prefix = config.prefix || ''; var suffix = config.suffix || ''; var formattedValue = newValue.toFixed(1); element.textContent = prefix + formattedValue + suffix; // Efeito de flash para indicar atualização element.style.color = '#62f1ff'; element.style.transition = 'color 0.3s ease'; setTimeout(function() { element.style.color = ''; }, 300); // Próxima atualização setTimeout(updateMetric, 3000 + Math.random() * 2000); } updateMetric(); }, // Sistema de Partículas Global Otimizado initParticleSystem: function() { var canvas = document.getElementById('globalParticlesCanvas'); if (!canvas) { console.warn('Canvas globalParticlesCanvas não encontrado'); return; } console.log('Inicializando sistema de partículas global...'); var ctx = canvas.getContext('2d'); var particles = []; var animationId = null; var isVisible = true; var performanceLevel = Utils.getPerformanceLevel(); var config = PERFORMANCE_CONFIG[performanceLevel]; // Pausar/retomar baseado na visibilidade da página + scroll var isScrolling = false; var scrollTimeout; document.addEventListener('visibilitychange', function() { isVisible = !document.hidden; if (isVisible && !animationId && !isScrolling) { animate(); } else if (!isVisible && animationId) { cancelAnimationFrame(animationId); animationId = null; } }); // Pausar durante scroll intenso window.addEventListener('scroll', function() { isScrolling = true; if (animationId) { cancelAnimationFrame(animationId); animationId = null; } clearTimeout(scrollTimeout); scrollTimeout = setTimeout(function() { isScrolling = false; if (isVisible && !animationId) { animate(); } }, 150); }); // Configurar canvas para tela inteira function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; console.log('Canvas global redimensionado: ' + canvas.width + 'x' + canvas.height); } resizeCanvas(); window.addEventListener('resize', Utils.throttle(resizeCanvas, 250)); // Classe Partícula Alienígena (Convertida para função construtora) function AlienParticle() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 1.5; this.vy = (Math.random() - 0.5) * 1.5; this.size = Math.random() * 3 + 1; this.opacity = Math.random() * 0.8 + 0.2; this.type = Math.floor(Math.random() * 4); this.pulse = Math.random() * Math.PI * 2; this.energy = Math.random() * 0.05 + 0.01; this.trail = []; this.maxTrail = config.trailLength; } AlienParticle.prototype = { getParticleStyle: function() { var colors = [ { color: 'rgba(98, 241, 255, ', glow: '#62f1ff' }, { color: 'rgba(0, 255, 163, ', glow: '#00ffa3' }, { color: 'rgba(123, 97, 255, ', glow: '#7b61ff' }, { color: 'rgba(255, 100, 255, ', glow: '#ff64ff' } ]; return colors[this.type]; }, update: function() { // Salvar posição anterior para trilha this.trail.push({ x: this.x, y: this.y, opacity: this.opacity }); if (this.trail.length > this.maxTrail) { this.trail.shift(); } // Movimento orgânico alienígena this.pulse += this.energy; this.vx += Math.sin(this.pulse) * 0.1; this.vy += Math.cos(this.pulse * 0.8) * 0.1; this.x += this.vx; this.y += this.vy; // Wrap around na tela if (this.x < 0) this.x = canvas.width; if (this.x > canvas.width) this.x = 0; if (this.y < 0) this.y = canvas.height; if (this.y > canvas.height) this.y = 0; // Pulsação de energia this.opacity = 0.5 + Math.sin(this.pulse * 2) * 0.3; this.size = (1 + Math.sin(this.pulse) * 0.3) * (1 + this.type * 0.5); }, drawTrail: function() { var style = this.getParticleStyle(); var self = this; this.trail.forEach(function(point, index) { var alpha = (index / self.trail.length) * self.opacity * 0.3; var size = self.size * (index / self.trail.length) * 0.5; ctx.save(); ctx.globalAlpha = alpha; ctx.fillStyle = style.color + alpha + ')'; ctx.shadowBlur = 5; ctx.shadowColor = style.glow; ctx.beginPath(); ctx.arc(point.x, point.y, size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); }, draw: function() { var style = this.getParticleStyle(); // Desenhar trilha apenas se não for modo de baixa performance if (performanceLevel !== 'lowMemory') { this.drawTrail(); } // Desenhar partícula principal ctx.save(); ctx.globalAlpha = this.opacity; // Brilho externo ctx.shadowBlur = 15 + Math.sin(this.pulse) * 5; ctx.shadowColor = style.glow; // Gradiente radial var gradient = ctx.createRadialGradient( this.x, this.y, 0, this.x, this.y, this.size * 2 ); gradient.addColorStop(0, style.color + this.opacity + ')'); gradient.addColorStop(0.7, style.color + (this.opacity * 0.5) + ')'); gradient.addColorStop(1, 'transparent'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); // Núcleo brilhante ctx.shadowBlur = 5; ctx.fillStyle = style.color + '1)'; ctx.beginPath(); ctx.arc(this.x, this.y, this.size * 0.3, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } }; // Criar partículas baseado na configuração de performance var baseParticleCount = Math.floor(canvas.width * canvas.height / 12000); var particleCount = Math.min(config.maxParticles, Math.max(10, baseParticleCount)); console.log('Criando ' + particleCount + ' partículas alienígenas (modo ' + performanceLevel + ')'); for (var i = 0; i < particleCount; i++) { particles.push(new AlienParticle()); } // Loop de animação otimizado var lastFrameTime = 0; var targetFrameTime = 1000 / config.targetFPS; function animate() { if (!isVisible) return; if (!canvas.width || !canvas.height) { resizeCanvas(); if (!canvas.width || !canvas.height) return; } // Fundo com fade suave para trilhas ctx.fillStyle = 'rgba(10, 10, 10, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Desenhar conexões energéticas - OTIMIZADO com menor complexidade if (performanceLevel !== 'lowMemory') { var maxConnections = Math.floor(particles.length / 3); // Limitar conexões var connectionsDrawn = 0; for (var i = 0; i < particles.length && connectionsDrawn < maxConnections; i++) { for (var j = i + 1; j < particles.length && connectionsDrawn < maxConnections; j++) { var dx = particles[i].x - particles[j].x; var dy = particles[i].y - particles[j].y; var distance = dx * dx + dy * dy; // Usar distância ao quadrado para evitar sqrt var maxDistanceSquared = config.connectionDistance * config.connectionDistance; if (distance < maxDistanceSquared) { var opacity = (maxDistanceSquared - distance) / maxDistanceSquared * 0.15; // Reduzir opacidade if (opacity > 0.05) { // Só desenhar se opacity significativa var style1 = particles[i].getParticleStyle(); ctx.save(); ctx.globalAlpha = opacity; ctx.strokeStyle = style1.glow + '30'; // Cor simples ctx.lineWidth = 0.5; // Linha mais fina ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); ctx.restore(); connectionsDrawn++; } } } } } // Atualizar e desenhar partículas particles.forEach(function(particle) { particle.update(); particle.draw(); }); // Throttling adaptativo baseado na performance com FPS dinâmico var now = performance.now(); var elapsed = now - lastFrameTime; // Sistema de FPS adaptativo if (elapsed > targetFrameTime * 2) { // Se está muito lento, reduzir ainda mais o FPS targetFrameTime = 1000 / (config.targetFPS * 0.5); } else if (elapsed < targetFrameTime * 0.5) { // Se está rápido, pode aumentar um pouco targetFrameTime = 1000 / config.targetFPS; } if (elapsed > targetFrameTime) { lastFrameTime = now - (elapsed % targetFrameTime); animationId = requestAnimationFrame(animate); } else { // Skip frame se ainda não é hora setTimeout(function() { animationId = requestAnimationFrame(animate); }, targetFrameTime - elapsed); } } console.log('FPS alvo: ' + config.targetFPS + ' (modo ' + performanceLevel + ')'); // Iniciar animação apenas se página está visível if (Utils.isPageVisible()) { animate(); } // Observer para pausar quando canvas não está visível if ('IntersectionObserver' in window) { var observer = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.isIntersecting && !animationId && isVisible) { animate(); } else if (!entry.isIntersecting && animationId) { cancelAnimationFrame(animationId); animationId = null; } }); }); observer.observe(canvas); } } }; // Initialize the responsive controller when DOM is ready function initializeApp() { console.log('Inicializando sistemas ROC Agency...'); new ResponsiveController(); // Set current year (unificado) var yearElement = document.getElementById('current-year'); if (yearElement) { yearElement.textContent = new Date().getFullYear(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeApp); } else { initializeApp(); } // Sistema de navegação e interface document.addEventListener('DOMContentLoaded', function() { // Menu toggle functionality var menuToggle = document.querySelector('.menu-toggle'); var navMenu = document.querySelector('.nav-menu'); var header = document.querySelector('header'); if (menuToggle && navMenu) { menuToggle.addEventListener('click', function() { menuToggle.classList.toggle('active'); navMenu.classList.toggle('active'); }); } // Header scroll effect if (header) { window.addEventListener('scroll', Utils.throttle(function() { if (window.scrollY > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } }, 100)); } // Animación de elementos al hacer scroll var fadeElements = document.querySelectorAll('.fade-in'); if ('IntersectionObserver' in window && fadeElements.length > 0) { var fadeInObserver = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 }); fadeElements.forEach(function(el) { fadeInObserver.observe(el); }); } // Testimonials slider var testimonialTrack = document.querySelector('.testimonials-track'); var testimonialDots = document.querySelectorAll('.testimonial-dot'); var currentTestimonial = 0; var testimonialInterval = null; function showTestimonial(index) { if (!testimonialTrack || !testimonialDots.length) return; testimonialTrack.style.transform = 'translateX(-' + (index * 100) + '%)'; testimonialDots.forEach(function(dot) { dot.classList.remove('active'); }); if (testimonialDots[index]) { testimonialDots[index].classList.add('active'); } currentTestimonial = index; } if (testimonialDots.length > 0) { testimonialDots.forEach(function(dot, index) { dot.addEventListener('click', function() { showTestimonial(index); // Resetar interval quando usuário clica if (testimonialInterval) { clearInterval(testimonialInterval); } startTestimonialRotation(); }); }); function startTestimonialRotation() { testimonialInterval = setInterval(function() { if (!Utils.isPageVisible()) return; // Pausar se página não visível currentTestimonial = (currentTestimonial + 1) % testimonialDots.length; showTestimonial(currentTestimonial); }, 5000); } startTestimonialRotation(); // Pausar quando página não está visível document.addEventListener('visibilitychange', function() { if (document.hidden && testimonialInterval) { clearInterval(testimonialInterval); testimonialInterval = null; } else if (!document.hidden && !testimonialInterval) { startTestimonialRotation(); } }); } // Form fallback para Netlify (melhorado) var contactForm = document.querySelector('form[name="contacto"]'); if (contactForm) { contactForm.addEventListener('submit', function(e) { if (!navigator.onLine) { e.preventDefault(); alert('Parece que no tienes conexión a internet. Por favor, intenta nuevamente o contáctanos por WhatsApp.'); return; } // Se não estamos em Netlify, redirigir para WhatsApp if (!window.location.hostname.includes('netlify')) { e.preventDefault(); var formData = new FormData(this); var message = 'Hola, me gustaría conversar con ROC Digital Agency sobre soluciones digitales para mi empresa.\n' + 'Nombre: ' + (formData.get('nombre') || 'No informado') + '\n' + 'Email: ' + (formData.get('email') || 'No informado') + '\n' + 'Teléfono: ' + (formData.get('telefono') || 'No informado') + '\n' + 'Empresa: ' + (formData.get('empresa') || 'No informada') + '\n' + 'Mensaje: ' + (formData.get('mensaje') || 'Sin mensaje específico') + '\n' + 'Por favor, espero su respuesta profesional.'; var whatsappURL = 'https://wa.me/56948145497?text=' + encodeURIComponent(message); window.open(whatsappURL, '_blank'); } }); } // Smooth scroll para anchor links com offset para header fixo var anchorLinks = document.querySelectorAll('a[href^="#"]'); anchorLinks.forEach(function(anchor) { anchor.addEventListener('click', function(e) { e.preventDefault(); var targetId = this.getAttribute('href'); if (targetId === '#') return; var targetElement = document.querySelector(targetId); var header = document.querySelector('header'); if (targetElement && header) { var headerHeight = header.offsetHeight; var targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerHeight; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); // Fechar menu móvel se estiver aberto var navMenu = document.querySelector('.nav-menu'); var menuToggle = document.querySelector('.menu-toggle'); if (navMenu && navMenu.classList.contains('active') && menuToggle) { menuToggle.classList.remove('active'); navMenu.classList.remove('active'); } } }); }); // Funcionalidade interativa para seção Transform Marketing // Service tabs functionality var serviceTabs = document.querySelectorAll('.service-tab'); var serviceContents = document.querySelectorAll('.service-content'); serviceTabs.forEach(function(tab) { tab.addEventListener('click', function() { var targetService = tab.getAttribute('data-service'); // Remove active class de todas as tabs serviceTabs.forEach(function(t) { t.classList.remove('active'); }); // Adiciona active class na tab clicada tab.classList.add('active'); // Esconde todos os service contents serviceContents.forEach(function(content) { content.classList.add('hidden'); }); // Mostra o target service content var targetContent = document.getElementById('service-content-' + targetService); if (targetContent) { targetContent.classList.remove('hidden'); } }); }); // Model accordion functionality var modelItems = document.querySelectorAll('.model-item'); var modelTitles = document.querySelectorAll('.model-title'); modelTitles.forEach(function(title) { title.addEventListener('click', function() { var modelItem = title.parentElement; var modelContent = modelItem.querySelector('.model-content'); // Verificar se este item já está ativo var isActive = modelItem.classList.contains('active'); // Fechar todos os model items modelItems.forEach(function(item) { item.classList.remove('active'); var content = item.querySelector('.model-content'); if (content) { content.classList.add('hidden'); } }); // Se este item não estava ativo, abrir if (!isActive && modelContent) { modelItem.classList.add('active'); modelContent.classList.remove('hidden'); } }); }); // Sistema de fallback e otimização de imagens (melhorado) function setupImageSystem() { var serviceImages = document.querySelectorAll('.service-image img'); var modelImages = document.querySelectorAll('.model-image img'); function createImagePlaceholder(container, message) { message = message || 'Imagen no disponible'; var placeholder = document.createElement('div'); placeholder.className = 'image-placeholder'; placeholder.innerHTML = '
' + message + '
' + '