script.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Mobile Navigation Toggle
  2. const hamburger = document.querySelector('.hamburger');
  3. const navMenu = document.querySelector('.nav-menu');
  4. hamburger.addEventListener('click', () => {
  5. hamburger.classList.toggle('active');
  6. navMenu.classList.toggle('active');
  7. });
  8. // Close mobile menu when clicking on a link
  9. document.querySelectorAll('.nav-link').forEach(link => {
  10. link.addEventListener('click', () => {
  11. hamburger.classList.remove('active');
  12. navMenu.classList.remove('active');
  13. });
  14. });
  15. // Smooth scrolling for navigation links
  16. document.querySelectorAll('a[href^="#"]').forEach(anchor => {
  17. anchor.addEventListener('click', function (e) {
  18. e.preventDefault();
  19. const target = document.querySelector(this.getAttribute('href'));
  20. if (target) {
  21. target.scrollIntoView({
  22. behavior: 'smooth',
  23. block: 'start'
  24. });
  25. }
  26. });
  27. });
  28. // Active navigation link on scroll
  29. window.addEventListener('scroll', () => {
  30. let current = '';
  31. const sections = document.querySelectorAll('section');
  32. sections.forEach(section => {
  33. const sectionTop = section.offsetTop;
  34. const sectionHeight = section.clientHeight;
  35. if (scrollY >= (sectionTop - 200)) {
  36. current = section.getAttribute('id');
  37. }
  38. });
  39. document.querySelectorAll('.nav-link').forEach(link => {
  40. link.classList.remove('active');
  41. if (link.getAttribute('href').slice(1) === current) {
  42. link.classList.add('active');
  43. }
  44. });
  45. });
  46. // Header background on scroll
  47. window.addEventListener('scroll', () => {
  48. const navbar = document.querySelector('.navbar');
  49. if (window.scrollY > 50) {
  50. navbar.style.background = 'rgba(255, 255, 255, 0.95)';
  51. navbar.style.backdropFilter = 'blur(10px)';
  52. } else {
  53. navbar.style.background = 'white';
  54. navbar.style.backdropFilter = 'none';
  55. }
  56. });
  57. // Form submission handling
  58. const contactForm = document.querySelector('.contact-form form');
  59. if (contactForm) {
  60. contactForm.addEventListener('submit', function(e) {
  61. e.preventDefault();
  62. // Get form data
  63. const formData = new FormData(this);
  64. const name = this.querySelector('input[type="text"]').value;
  65. const email = this.querySelector('input[type="email"]').value;
  66. const phone = this.querySelector('input[type="tel"]').value;
  67. const service = this.querySelector('select').value;
  68. const message = this.querySelector('textarea').value;
  69. // Simple validation
  70. if (!name || !email || !message) {
  71. showNotification('Por favor, completa todos los campos obligatorios.', 'error');
  72. return;
  73. }
  74. // Email validation
  75. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  76. if (!emailRegex.test(email)) {
  77. showNotification('Por favor, introduce un email válido.', 'error');
  78. return;
  79. }
  80. // Simulate form submission (replace with actual endpoint)
  81. showNotification('¡Mensaje enviado con éxito! Nos pondremos en contacto contigo pronto.', 'success');
  82. this.reset();
  83. });
  84. }
  85. // Newsletter form handling
  86. const newsletterForm = document.querySelector('.newsletter-form');
  87. if (newsletterForm) {
  88. newsletterForm.addEventListener('submit', function(e) {
  89. e.preventDefault();
  90. const email = this.querySelector('input[type="email"]').value;
  91. if (!email) {
  92. showNotification('Por favor, introduce tu email.', 'error');
  93. return;
  94. }
  95. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  96. if (!emailRegex.test(email)) {
  97. showNotification('Por favor, introduce un email válido.', 'error');
  98. return;
  99. }
  100. // Simulate newsletter subscription
  101. showNotification('¡Gracias por suscribirte a nuestro newsletter!', 'success');
  102. this.reset();
  103. });
  104. }
  105. // Notification system
  106. function showNotification(message, type = 'info') {
  107. // Remove existing notifications
  108. const existingNotification = document.querySelector('.notification');
  109. if (existingNotification) {
  110. existingNotification.remove();
  111. }
  112. // Create notification element
  113. const notification = document.createElement('div');
  114. notification.className = `notification notification-${type}`;
  115. notification.textContent = message;
  116. // Add styles
  117. notification.style.cssText = `
  118. position: fixed;
  119. top: 20px;
  120. right: 20px;
  121. padding: 15px 20px;
  122. border-radius: 8px;
  123. color: white;
  124. font-weight: 500;
  125. z-index: 10000;
  126. transform: translateX(100%);
  127. transition: transform 0.3s ease;
  128. max-width: 300px;
  129. `;
  130. // Set background color based on type
  131. switch(type) {
  132. case 'success':
  133. notification.style.background = '#10b981';
  134. break;
  135. case 'error':
  136. notification.style.background = '#ef4444';
  137. break;
  138. default:
  139. notification.style.background = '#3b82f6';
  140. }
  141. // Add to page
  142. document.body.appendChild(notification);
  143. // Animate in
  144. setTimeout(() => {
  145. notification.style.transform = 'translateX(0)';
  146. }, 100);
  147. // Remove after 5 seconds
  148. setTimeout(() => {
  149. notification.style.transform = 'translateX(100%)';
  150. setTimeout(() => {
  151. notification.remove();
  152. }, 300);
  153. }, 5000);
  154. }
  155. // Intersection Observer for animations
  156. const observerOptions = {
  157. threshold: 0.1,
  158. rootMargin: '0px 0px -50px 0px'
  159. };
  160. const observer = new IntersectionObserver((entries) => {
  161. entries.forEach(entry => {
  162. if (entry.isIntersecting) {
  163. entry.target.style.opacity = '1';
  164. entry.target.style.transform = 'translateY(0)';
  165. }
  166. });
  167. }, observerOptions);
  168. // Observe elements for animation
  169. document.addEventListener('DOMContentLoaded', () => {
  170. const animatedElements = document.querySelectorAll('.service-card, .project-card, .testimonial-card');
  171. animatedElements.forEach(el => {
  172. el.style.opacity = '0';
  173. el.style.transform = 'translateY(30px)';
  174. el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
  175. observer.observe(el);
  176. });
  177. });
  178. // Lazy loading for images
  179. const imageObserver = new IntersectionObserver((entries) => {
  180. entries.forEach(entry => {
  181. if (entry.isIntersecting) {
  182. const img = entry.target;
  183. img.style.opacity = '0';
  184. img.style.transition = 'opacity 0.5s ease';
  185. img.onload = () => {
  186. img.style.opacity = '1';
  187. };
  188. // Force load if not already loaded
  189. if (img.complete) {
  190. img.style.opacity = '1';
  191. }
  192. imageObserver.unobserve(img);
  193. }
  194. });
  195. });
  196. // Observe all images
  197. document.addEventListener('DOMContentLoaded', () => {
  198. const images = document.querySelectorAll('img');
  199. images.forEach(img => imageObserver.observe(img));
  200. });
  201. // Parallax effect for hero section
  202. window.addEventListener('scroll', () => {
  203. const scrolled = window.pageYOffset;
  204. const hero = document.querySelector('.hero');
  205. if (hero) {
  206. hero.style.transform = `translateY(${scrolled * 0.5}px)`;
  207. }
  208. });
  209. // Add loading animation
  210. window.addEventListener('load', () => {
  211. document.body.style.opacity = '0';
  212. document.body.style.transition = 'opacity 0.5s ease';
  213. setTimeout(() => {
  214. document.body.style.opacity = '1';
  215. }, 100);
  216. });
  217. // Counter animation for statistics
  218. function animateCounter(element, target, duration = 2000) {
  219. let start = 0;
  220. const increment = target / (duration / 16);
  221. const timer = setInterval(() => {
  222. start += increment;
  223. if (start >= target) {
  224. element.textContent = target;
  225. clearInterval(timer);
  226. } else {
  227. element.textContent = Math.floor(start);
  228. }
  229. }, 16);
  230. }
  231. // Initialize counters when in viewport
  232. const counterObserver = new IntersectionObserver((entries) => {
  233. entries.forEach(entry => {
  234. if (entry.isIntersecting && !entry.target.classList.contains('counted')) {
  235. const target = parseInt(entry.target.textContent);
  236. entry.target.classList.add('counted');
  237. animateCounter(entry.target, target);
  238. }
  239. });
  240. }, { threshold: 0.5 });
  241. // Add hover effects to cards
  242. document.addEventListener('DOMContentLoaded', () => {
  243. const cards = document.querySelectorAll('.service-card, .project-card, .testimonial-card');
  244. cards.forEach(card => {
  245. card.addEventListener('mouseenter', function() {
  246. this.style.transform = 'translateY(-10px) scale(1.02)';
  247. });
  248. card.addEventListener('mouseleave', function() {
  249. this.style.transform = 'translateY(0) scale(1)';
  250. });
  251. });
  252. });
  253. // Add active class to current navigation item
  254. document.addEventListener('DOMContentLoaded', () => {
  255. const currentPath = window.location.hash || '#inicio';
  256. const activeLink = document.querySelector(`.nav-link[href="${currentPath}"]`);
  257. if (activeLink) {
  258. activeLink.classList.add('active');
  259. }
  260. });