main-01.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. // Initialize particles.js
  2. if (typeof particlesJS !== 'undefined' && document.getElementById('particles-js')) {
  3. particlesJS('particles-js', particlesConfig);
  4. }
  5. // Load SVG icon sprites
  6. (function loadIconSprite() {
  7. const script = document.createElement('script');
  8. script.src = 'assets/data/icons-sprite.js';
  9. script.async = false;
  10. document.head.appendChild(script);
  11. })();
  12. // Initialize all components when DOM loads
  13. document.addEventListener('DOMContentLoaded', async function() {
  14. try {
  15. // Load services data for Typed.js
  16. if (typeof Typed !== 'undefined' && document.getElementById('typed-element')) {
  17. try {
  18. // Initialize Typed.js
  19. const typed = new Typed('#typed-element', {
  20. strings: servicesData.services,
  21. typeSpeed: servicesData.typewriterConfig.typeSpeed,
  22. backSpeed: servicesData.typewriterConfig.deleteSpeed,
  23. startDelay: 1500,
  24. backDelay: servicesData.typewriterConfig.deleteDelay,
  25. loop: servicesData.typewriterConfig.loop,
  26. showCursor: servicesData.typewriterConfig.showCursor,
  27. cursorChar: servicesData.typewriterConfig.cursorChar,
  28. autoInsertCss: true,
  29. });
  30. window.serviceTyped = typed;
  31. } catch (error) {
  32. // Fallback with default strings
  33. const typed = new Typed('#typed-element', {
  34. strings: ['Web Development', 'Digital Marketing', 'SEO Optimization'],
  35. typeSpeed: 50,
  36. backSpeed: 30,
  37. backDelay: 1500,
  38. loop: true,
  39. showCursor: true,
  40. cursorChar: '|'
  41. });
  42. window.serviceTyped = typed;
  43. }
  44. }
  45. // Initialize Portfolio
  46. initPortfolioIsotope();
  47. // Refresh AOS
  48. setTimeout(() => {
  49. if (typeof AOS !== 'undefined') {
  50. AOS.refresh();
  51. }
  52. }, 100);
  53. } catch (error) {
  54. // Initialize AOS as fallback
  55. try {
  56. if (typeof AOS !== 'undefined') {
  57. AOS.init({
  58. duration: 800,
  59. delay: 100,
  60. once: false,
  61. mirror: true,
  62. offset: 50,
  63. easing: 'ease-out-cubic'
  64. });
  65. }
  66. } catch (aosError) {
  67. // Silent fail
  68. }
  69. }
  70. });
  71. /**
  72. * Enhanced portfolio initialization function
  73. */
  74. function initPortfolioIsotope() {
  75. const portfolioGrid = document.querySelector('.portfolio-grid');
  76. const filterButtons = document.querySelectorAll('.filter-btn');
  77. if (!portfolioGrid) {
  78. return;
  79. }
  80. if (filterButtons.length === 0) {
  81. console.warn('⚠️ No filter buttons found');
  82. return;
  83. }
  84. // Wait for images to load
  85. waitForImages(portfolioGrid).then(() => {
  86. // Create CustomIsotope instance
  87. const customIsotope = new CustomIsotope(portfolioGrid, {
  88. itemSelector: '.portfolio-item',
  89. gutter: 24, // Match your CSS gap
  90. transitionDuration: 500,
  91. hiddenStyle: {
  92. opacity: 0,
  93. transform: 'scale(0.9) translateY(20px)'
  94. },
  95. visibleStyle: {
  96. opacity: 1,
  97. transform: 'scale(1) translateY(0px)'
  98. }
  99. });
  100. // Store instance globally
  101. window.portfolioIsotope = customIsotope;
  102. window.customIsotope = customIsotope; // Additional alias
  103. // Setup filter buttons
  104. setupFilterButtons(customIsotope, filterButtons);
  105. // Refresh AOS after initial layout
  106. setTimeout(() => {
  107. if (typeof AOS !== 'undefined') {
  108. AOS.refresh();
  109. }
  110. }, 1000);
  111. }).catch(error => {
  112. console.error('❌ Error loading images:', error);
  113. // Initialize anyway after timeout
  114. setTimeout(() => {
  115. const customIsotope = new CustomIsotope(portfolioGrid);
  116. window.portfolioIsotope = customIsotope;
  117. setupFilterButtons(customIsotope, filterButtons);
  118. }, 1000);
  119. });
  120. }
  121. // Typed.js control functions
  122. function pauseTyping() {
  123. if (window.serviceTyped) {
  124. window.serviceTyped.stop();
  125. }
  126. }
  127. function resumeTyping() {
  128. if (window.serviceTyped) {
  129. window.serviceTyped.start();
  130. }
  131. }
  132. function resetTyping() {
  133. if (window.serviceTyped) {
  134. window.serviceTyped.reset();
  135. }
  136. }
  137. // AOS control functions
  138. function refreshAOS() {
  139. if (typeof AOS !== 'undefined') {
  140. AOS.refresh();
  141. }
  142. }
  143. function disableAOS() {
  144. if (typeof AOS !== 'undefined') {
  145. AOS.init({
  146. disable: true
  147. });
  148. }
  149. }
  150. function enableAOS() {
  151. if (typeof AOS !== 'undefined') {
  152. AOS.init({
  153. duration: 800,
  154. delay: 100,
  155. once: false,
  156. mirror: true,
  157. offset: 50,
  158. easing: 'ease-out-cubic'
  159. });
  160. }
  161. }
  162. // Handle page visibility changes
  163. document.addEventListener('visibilitychange', function() {
  164. if (document.hidden) {
  165. pauseTyping();
  166. } else {
  167. resumeTyping();
  168. refreshAOS();
  169. }
  170. });
  171. // Expose utility functions globally
  172. window.AOSUtils = {
  173. refresh: refreshAOS,
  174. disable: disableAOS,
  175. enable: enableAOS
  176. };
  177. window.TypingUtils = {
  178. pause: pauseTyping,
  179. resume: resumeTyping,
  180. reset: resetTyping
  181. };
  182. // ==========================================================================
  183. // RESPONSIVE HEADER
  184. // ==========================================================================
  185. function initResponsiveHeader() {
  186. const header = document.querySelector('.header');
  187. const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
  188. const navbar = document.getElementById('navbar');
  189. const body = document.body;
  190. const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
  191. const dropdownMenus = document.querySelectorAll('.dropdown-menu');
  192. if (!header || !mobileMenuToggle || !navbar) return;
  193. // Funciones del menú móvil
  194. function openMobileMenu() {
  195. navbar.classList.add('mobile-menu-open');
  196. mobileMenuToggle.classList.add('active');
  197. body.style.overflow = 'hidden';
  198. }
  199. function closeMobileMenu() {
  200. navbar.classList.remove('mobile-menu-open');
  201. mobileMenuToggle.classList.remove('active');
  202. body.style.overflow = '';
  203. closeAllDropdowns();
  204. }
  205. function toggleMobileMenu() {
  206. if (navbar.classList.contains('mobile-menu-open')) {
  207. closeMobileMenu();
  208. } else {
  209. openMobileMenu();
  210. }
  211. }
  212. // Event listeners del menú móvil
  213. mobileMenuToggle.addEventListener('click', function(e) {
  214. e.preventDefault();
  215. e.stopPropagation();
  216. toggleMobileMenu();
  217. });
  218. document.addEventListener('click', function(e) {
  219. const isClickInsideMenu = navbar.contains(e.target);
  220. const isClickOnToggle = mobileMenuToggle.contains(e.target);
  221. if (!isClickInsideMenu && !isClickOnToggle && navbar.classList.contains('mobile-menu-open')) {
  222. closeMobileMenu();
  223. }
  224. });
  225. const navLinks = document.querySelectorAll('.nav-link');
  226. navLinks.forEach(link => {
  227. link.addEventListener('click', function() {
  228. if (window.innerWidth <= 992) {
  229. closeMobileMenu();
  230. }
  231. });
  232. });
  233. function closeAllDropdowns() {
  234. dropdownMenus.forEach(menu => {
  235. menu.classList.remove('dropdown-open');
  236. });
  237. dropdownToggles.forEach(toggle => {
  238. toggle.classList.remove('active');
  239. });
  240. }
  241. function toggleDropdown(dropdownId, toggleElement) {
  242. const dropdownMenu = document.getElementById(dropdownId);
  243. if (!dropdownMenu) return;
  244. const isOpen = dropdownMenu.classList.contains('dropdown-open');
  245. closeAllDropdowns();
  246. if (!isOpen) {
  247. dropdownMenu.classList.add('dropdown-open');
  248. toggleElement.classList.add('active');
  249. }
  250. }
  251. // Event listeners de dropdown
  252. dropdownToggles.forEach(toggle => {
  253. toggle.addEventListener('click', function(e) {
  254. e.preventDefault();
  255. e.stopPropagation();
  256. const dropdownId = this.getAttribute('data-dropdown');
  257. toggleDropdown(dropdownId, this);
  258. });
  259. });
  260. document.addEventListener('click', function(e) {
  261. if (window.innerWidth <= 992) {
  262. const isClickInsideDropdown = Array.from(dropdownMenus).some(menu =>
  263. menu.contains(e.target)
  264. );
  265. const isClickOnToggle = Array.from(dropdownToggles).some(toggle =>
  266. toggle.contains(e.target)
  267. );
  268. if (!isClickInsideDropdown && !isClickOnToggle) {
  269. closeAllDropdowns();
  270. }
  271. }
  272. });
  273. const dropdownItems = document.querySelectorAll('.dropdown-item');
  274. dropdownItems.forEach(item => {
  275. item.addEventListener('click', function() {
  276. if (window.innerWidth <= 992) {
  277. closeAllDropdowns();
  278. closeMobileMenu();
  279. }
  280. });
  281. });
  282. function handleHeaderScroll() {
  283. if (window.scrollY > 100) {
  284. header.classList.add('header-scrolled');
  285. header.classList.remove('header-transparent');
  286. //修改logo
  287. var that = document.getElementById("lmdlogo");
  288. if (localStorage.getItem('theme') === 'dark') {
  289. that.src = "assets/images/lmdlogo/lmd222.png";
  290. } else {
  291. that.src = "assets/images/lmdlogo/lmd22.png";
  292. }
  293. } else {
  294. header.classList.remove('header-scrolled');
  295. header.classList.add('header-transparent');
  296. var that = document.getElementById("lmdlogo");
  297. that.src = "assets/images/lmdlogo/lmd2.png";
  298. }
  299. }
  300. if (window.scrollY > 100) {
  301. header.classList.add('header-scrolled');
  302. } else {
  303. header.classList.add('header-transparent');
  304. }
  305. window.addEventListener('scroll', handleHeaderScroll);
  306. function handleWindowResize() {
  307. if (window.innerWidth > 992) {
  308. closeMobileMenu();
  309. closeAllDropdowns();
  310. }
  311. }
  312. window.addEventListener('resize', handleWindowResize);
  313. document.addEventListener('keydown', function(e) {
  314. if (e.key === 'Escape') {
  315. if (navbar.classList.contains('mobile-menu-open')) {
  316. closeMobileMenu();
  317. }
  318. closeAllDropdowns();
  319. }
  320. });
  321. }
  322. initResponsiveHeader();
  323. // ==========================================================================
  324. // CUSTOMERS CAROUSEL
  325. // ==========================================================================
  326. document.addEventListener('DOMContentLoaded', function() {
  327. if (typeof UniversalSlider !== 'undefined' && document.querySelector('.customers-carousel2')) {
  328. const customersSlider = new UniversalSlider('.customers-carousel2', {
  329. slidesPerView: {
  330. desktop: 3,
  331. tablet: 3,
  332. mobile: 2
  333. },
  334. slideBy: 1,
  335. breakpoints: {
  336. mobile: 576,
  337. tablet: 992
  338. },
  339. autoplay: true,
  340. autoplayDelay: 3000,
  341. pagination: false,
  342. arrows: false,
  343. loop: true,
  344. touchEnabled: true,
  345. });
  346. }
  347. });
  348. // ==========================================================================
  349. // NEWSLETTER FORM SUBMISSION
  350. // ==========================================================================
  351. const newsletterForm = document.getElementById('newsletterForm');
  352. if (newsletterForm) {
  353. newsletterForm.addEventListener('submit', function(e) {
  354. e.preventDefault();
  355. const checkbox = this.querySelector('input[name="newsletter-agreement"]');
  356. if (checkbox && !checkbox.checked) {
  357. alert('Please agree to the Privacy Policy');
  358. return;
  359. }
  360. alert('Thank you for subscribing to our newsletter!');
  361. this.reset();
  362. });
  363. }
  364. // ==========================================================================
  365. // SCROLL TO TOP FUNCTIONALITY
  366. // ==========================================================================
  367. const scrollToTop = document.getElementById('scrollToTop');
  368. if (scrollToTop) {
  369. window.addEventListener('scroll', function() {
  370. if (window.pageYOffset > 300) {
  371. scrollToTop.classList.add('show');
  372. } else {
  373. scrollToTop.classList.remove('show');
  374. }
  375. });
  376. scrollToTop.addEventListener('click', function() {
  377. window.scrollTo({
  378. top: 0,
  379. behavior: 'smooth'
  380. });
  381. });
  382. }
  383. // Smooth scrolling for footer links
  384. document.querySelectorAll('.footer-link[href^="#"]').forEach(link => {
  385. link.addEventListener('click', function(e) {
  386. e.preventDefault();
  387. const target = document.querySelector(this.getAttribute('href'));
  388. if (target) {
  389. target.scrollIntoView({
  390. behavior: 'smooth',
  391. block: 'start'
  392. });
  393. }
  394. });
  395. });
  396. /**
  397. * Page Loader Controller - MultiIT
  398. * Handles the loading screen until all resources are loaded
  399. */
  400. class PageLoader {
  401. constructor() {
  402. this.loader = document.getElementById('page-loader');
  403. this.progressBar = document.querySelector('.progress-bar');
  404. this.isLoaded = false;
  405. this.minLoadTime = 1500; // Minimum loading time in ms
  406. this.maxLoadTime = 8000; // Maximum loading time in ms (fallback)
  407. this.startTime = Date.now();
  408. this.currentProgress = 0;
  409. this.loadingSteps = [];
  410. this.completedSteps = 0;
  411. // Only initialize if loader exists
  412. if (this.loader) {
  413. this.init();
  414. } else {
  415. console.warn('MultiIT Loader: #page-loader element not found');
  416. }
  417. }
  418. init() {
  419. try {
  420. // Add loading class to body
  421. document.body.classList.add('loading');
  422. // Start progress animation
  423. this.animateProgress();
  424. // Wait for page to load completely
  425. this.waitForPageLoad();
  426. // Safety timeout to prevent infinite loading
  427. this.setSafetyTimeout();
  428. } catch (error) {
  429. this.forceHide();
  430. }
  431. }
  432. waitForPageLoad() {
  433. const promises = [];
  434. // Wait for DOM to be ready
  435. if (document.readyState === 'loading') {
  436. promises.push(new Promise(resolve => {
  437. document.addEventListener('DOMContentLoaded', resolve, {
  438. once: true
  439. });
  440. this.loadingSteps.push('DOM Content Loaded');
  441. }));
  442. }
  443. // Wait for all resources (images, CSS, JS) to load
  444. if (document.readyState !== 'complete') {
  445. promises.push(new Promise(resolve => {
  446. window.addEventListener('load', resolve, {
  447. once: true
  448. });
  449. this.loadingSteps.push('Window Load');
  450. }));
  451. }
  452. // Wait for all images to load
  453. promises.push(this.waitForImages());
  454. this.loadingSteps.push('Images Loaded');
  455. // Wait for fonts to load (if using web fonts)
  456. if (document.fonts && document.fonts.ready) {
  457. promises.push(document.fonts.ready.catch(() => {
  458. console.warn('MultiIT Loader: Fonts failed to load');
  459. }));
  460. this.loadingSteps.push('Fonts Loaded');
  461. }
  462. // Wait for critical CSS to load
  463. promises.push(this.waitForCriticalAssets());
  464. this.loadingSteps.push('Critical Assets');
  465. // Execute when everything is loaded
  466. Promise.allSettled(promises).then((results) => {
  467. // Log any failed promises
  468. results.forEach((result, index) => {
  469. if (result.status === 'rejected') {
  470. console.warn(`MultiIT Loader: Step "${this.loadingSteps[index]}" failed:`,
  471. result.reason);
  472. }
  473. });
  474. this.isLoaded = true;
  475. this.checkMinimumLoadTime();
  476. });
  477. }
  478. waitForImages() {
  479. return new Promise(resolve => {
  480. const images = document.querySelectorAll('img[src], img[data-src]');
  481. if (images.length === 0) {
  482. resolve();
  483. return;
  484. }
  485. let loadedImages = 0;
  486. const totalImages = images.length;
  487. const imagePromises = Array.from(images).map((img, index) => {
  488. return new Promise(imageResolve => {
  489. const handleImageLoad = () => {
  490. loadedImages++;
  491. // Update progress based on image loading
  492. const imageProgress = (loadedImages / totalImages) *
  493. 30; // Images contribute 30% to progress
  494. this.updateProgressStep(imageProgress);
  495. imageResolve();
  496. };
  497. if (img.complete && img.naturalHeight !== 0) {
  498. handleImageLoad();
  499. } else {
  500. img.addEventListener('load', handleImageLoad, {
  501. once: true
  502. });
  503. img.addEventListener('error', () => {
  504. console.warn(
  505. `MultiIT Loader: Image failed to load: ${img.src || img.dataset.src}`
  506. );
  507. handleImageLoad(); // Continue even if image fails
  508. }, {
  509. once: true
  510. });
  511. // Timeout fallback for stuck images
  512. setTimeout(handleImageLoad, 3000);
  513. }
  514. });
  515. });
  516. Promise.all(imagePromises).then(resolve);
  517. });
  518. }
  519. waitForCriticalAssets() {
  520. return new Promise(resolve => {
  521. // Check for critical CSS files
  522. const criticalStylesheets = document.querySelectorAll(
  523. 'link[rel="stylesheet"][href*="critical"], link[rel="stylesheet"][href*="main"]');
  524. if (criticalStylesheets.length === 0) {
  525. resolve();
  526. return;
  527. }
  528. let loadedSheets = 0;
  529. const totalSheets = criticalStylesheets.length;
  530. criticalStylesheets.forEach(sheet => {
  531. if (sheet.sheet) {
  532. loadedSheets++;
  533. if (loadedSheets === totalSheets) resolve();
  534. } else {
  535. sheet.addEventListener('load', () => {
  536. loadedSheets++;
  537. if (loadedSheets === totalSheets) resolve();
  538. }, {
  539. once: true
  540. });
  541. sheet.addEventListener('error', () => {
  542. console.warn('MultiIT Loader: Critical stylesheet failed to load:',
  543. sheet.href);
  544. loadedSheets++;
  545. if (loadedSheets === totalSheets) resolve();
  546. }, {
  547. once: true
  548. });
  549. }
  550. });
  551. // Timeout fallback
  552. setTimeout(resolve, 2000);
  553. });
  554. }
  555. setSafetyTimeout() {
  556. setTimeout(() => {
  557. if (!this.isLoaded) {
  558. this.isLoaded = true;
  559. this.checkMinimumLoadTime();
  560. }
  561. }, this.maxLoadTime);
  562. }
  563. checkMinimumLoadTime() {
  564. const elapsed = Date.now() - this.startTime;
  565. const remaining = Math.max(0, this.minLoadTime - elapsed);
  566. // Complete progress bar
  567. this.setProgress(100);
  568. setTimeout(() => {
  569. this.hideLoader();
  570. }, remaining);
  571. }
  572. animateProgress() {
  573. const progressInterval = setInterval(() => {
  574. if (this.isLoaded) {
  575. this.setProgress(100);
  576. clearInterval(progressInterval);
  577. return;
  578. }
  579. // More realistic progress simulation
  580. const increment = Math.random() * 8 + 2; // 2-10% increments
  581. this.currentProgress = Math.min(this.currentProgress + increment, 85);
  582. this.setProgress(this.currentProgress);
  583. if (this.currentProgress >= 85) {
  584. clearInterval(progressInterval);
  585. }
  586. }, 300);
  587. }
  588. updateProgressStep(stepProgress) {
  589. // Update progress based on completed steps
  590. const baseProgress = (this.completedSteps / this.loadingSteps.length) * 70;
  591. this.currentProgress = Math.min(baseProgress + stepProgress, 90);
  592. this.setProgress(this.currentProgress);
  593. }
  594. setProgress(percentage) {
  595. if (this.progressBar) {
  596. // Smooth progress animation
  597. this.progressBar.style.width = `${Math.min(100, Math.max(0, percentage))}%`;
  598. }
  599. }
  600. hideLoader() {
  601. if (!this.loader) return;
  602. const loadTime = Date.now() - this.startTime;
  603. // Add loaded class for animation
  604. this.loader.classList.add('loaded');
  605. document.body.classList.remove('loading');
  606. document.body.classList.add('loaded');
  607. // Remove loader from DOM after animation
  608. setTimeout(() => {
  609. if (this.loader && this.loader.parentNode) {
  610. this.loader.parentNode.removeChild(this.loader);
  611. }
  612. }, 800);
  613. // Trigger custom event for other scripts
  614. this.dispatchLoadedEvent(loadTime);
  615. }
  616. dispatchLoadedEvent(loadTime) {
  617. try {
  618. const event = new CustomEvent('pageLoaded', {
  619. detail: {
  620. loadTime,
  621. timestamp: Date.now(),
  622. userAgent: navigator.userAgent,
  623. connection: navigator.connection ? {
  624. effectiveType: navigator.connection.effectiveType,
  625. downlink: navigator.connection.downlink
  626. } : null
  627. }
  628. });
  629. window.dispatchEvent(event);
  630. } catch (error) {
  631. console.warn('MultiIT Loader: Failed to dispatch pageLoaded event', error);
  632. }
  633. }
  634. // Public method to force hide loader
  635. forceHide() {
  636. this.isLoaded = true;
  637. this.hideLoader();
  638. }
  639. // Public method to show loader (for dynamic content loading)
  640. show() {
  641. if (this.loader) {
  642. this.loader.classList.remove('loaded');
  643. document.body.classList.add('loading');
  644. document.body.classList.remove('loaded');
  645. this.currentProgress = 0;
  646. this.setProgress(0);
  647. }
  648. }
  649. // Public method to update progress manually
  650. updateProgress(percentage) {
  651. this.setProgress(percentage);
  652. }
  653. }
  654. /* ========================================
  655. INITIALIZATION
  656. ======================================== */
  657. let pageLoader;
  658. function initializePageLoader() {
  659. try {
  660. pageLoader = new PageLoader();
  661. } catch (error) {
  662. console.error('MultiIT Loader: Failed to initialize', error);
  663. }
  664. }
  665. // Start immediately if DOM is already ready, otherwise wait
  666. if (document.readyState === 'loading') {
  667. document.addEventListener('DOMContentLoaded', initializePageLoader, {
  668. once: true
  669. });
  670. } else {
  671. initializePageLoader();
  672. }
  673. /* ========================================
  674. GLOBAL LOADER API
  675. ======================================== */
  676. // Enhanced global API
  677. window.MultiITLoader = {
  678. hide: () => pageLoader?.forceHide(),
  679. show: () => pageLoader?.show(),
  680. setProgress: (percentage) => pageLoader?.updateProgress(percentage),
  681. isLoaded: () => pageLoader?.isLoaded || false,
  682. getInstance: () => pageLoader
  683. };
  684. /* ========================================
  685. EVENT LISTENERS
  686. ======================================== */
  687. // Listen for page loaded event with enhanced logging
  688. window.addEventListener('pageLoaded', (event) => {
  689. const {
  690. loadTime,
  691. connection
  692. } = event.detail;
  693. const connectionInfo = connection ? `(${connection.effectiveType}, ${connection.downlink}Mbps)` : '';
  694. });
  695. // ✅ Inicializar AOS cuando el loader termine
  696. window.addEventListener('pageLoaded', () => {
  697. if (typeof AOS !== 'undefined') {
  698. AOS.init({
  699. duration: 800,
  700. delay: 100,
  701. once: false,
  702. mirror: true,
  703. offset: 50,
  704. easing: 'ease-out-cubic'
  705. });
  706. }
  707. });
  708. // Handle page visibility change (when user switches tabs)
  709. document.addEventListener('visibilitychange', () => {
  710. if (document.visibilityState === 'visible' && pageLoader) {
  711. // Page became visible again, ensure loader behavior is correct
  712. setTimeout(() => {
  713. if (pageLoader && document.readyState === 'complete') {
  714. pageLoader.forceHide();
  715. }
  716. }, 100);
  717. }
  718. });
  719. // Handle network connection changes (if supported)
  720. if (navigator.connection) {
  721. navigator.connection.addEventListener('change', () => {
  722. if (pageLoader && !pageLoader.isLoaded) {}
  723. });
  724. }
  725. // Handle errors globally
  726. window.addEventListener('error', (event) => {
  727. return;
  728. });
  729. // Performance timing
  730. window.addEventListener('load', () => {
  731. if (window.performance) {
  732. try {
  733. // Método moderno con Navigation Timing API Level 2
  734. if (performance.getEntriesByType && performance.getEntriesByType('navigation').length > 0) {
  735. const navigationEntry = performance.getEntriesByType('navigation')[0];
  736. }
  737. // Fallback para navegadores que no soportan Navigation Timing Level 2
  738. else if (performance.timing) {
  739. // Solo usar si es absolutamente necesario para compatibilidad
  740. const timing = performance.timing;
  741. const loadTime = timing.loadEventEnd - timing.fetchStart;
  742. }
  743. } catch (error) {
  744. console.warn('MultiIt Performance: Could not measure performance timing', error);
  745. }
  746. }
  747. });
  748. // --- THEME SWITCHER LOGIC ---
  749. // Wait for the initial HTML document to be completely loaded and parsed
  750. document.addEventListener('DOMContentLoaded', () => {
  751. // Select the DOM elements
  752. const themeToggleBtn = document.getElementById('theme-toggle');
  753. const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
  754. const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
  755. // Function to update the visibility of the icons
  756. const updateIcons = (isDarkMode) => {
  757. if (isDarkMode) {
  758. // If dark mode is active, show the light mode icon (sun)
  759. themeToggleDarkIcon.classList.add('hidden');
  760. themeToggleLightIcon.classList.remove('hidden');
  761. var that = document.getElementById("lmdlogo");
  762. if (window.scrollY > 100) {
  763. that.src = "assets/images/lmdlogo/lmd222.png";
  764. }
  765. } else {
  766. // Otherwise, show the dark mode icon (moon)
  767. themeToggleDarkIcon.classList.remove('hidden');
  768. themeToggleLightIcon.classList.add('hidden');
  769. var that = document.getElementById("lmdlogo");
  770. if (window.scrollY > 100) {
  771. that.src = "assets/images/lmdlogo/lmd22.png";
  772. }
  773. }
  774. };
  775. // Checks for a saved theme, defaulting to light mode.
  776. const applyInitialTheme = () => {
  777. // The new logic: The theme is light by default, and dark ONLY if explicitly saved as 'dark'.
  778. if (localStorage.getItem('theme') === 'dark') {
  779. document.body.classList.add('dark-mode');
  780. updateIcons(true);
  781. } else {
  782. // For all other cases (first visit or theme saved as 'light'), default to light mode.
  783. document.body.classList.remove('dark-mode');
  784. updateIcons(false);
  785. }
  786. };
  787. // Only add the event listener if the toggle button exists on the page
  788. if (themeToggleBtn) {
  789. themeToggleBtn.addEventListener('click', () => {
  790. // Toggle the 'dark-mode' class on the body.
  791. // .toggle() returns true if the class was added, false if removed.
  792. const isDarkMode = document.body.classList.toggle('dark-mode');
  793. // Save the user's preference to localStorage
  794. if (isDarkMode) {
  795. localStorage.setItem('theme', 'dark');
  796. } else {
  797. localStorage.setItem('theme', 'light');
  798. }
  799. // Update the button's icon to reflect the new state
  800. updateIcons(isDarkMode);
  801. });
  802. }
  803. applyInitialTheme();
  804. });