KANOcx
FeaturesPricingServicesContactUpdates
Get Started
LOCAL SOLUTIONS FOR LOCAL BUSINESSES

KANOcx

Your Boutique Consulting Partner for Operations, CX, and Digital Performance

Get StartedCall Now

"Focus on your passion while we handle complex operations, digital strategy and customer experience."

Founder of KANOcx
(function() {
  if (window.textRevealInitialized) return;
  window.textRevealInitialized = true;

  const CONFIG = {
    triggerPosition: 50,
    animationLength: 100,
    startColor: '#000000',
    endColor: '#e6e6e6',
    textOpacity: 0.2,
    revealType: 'letter',
    scrollSmoothness: 1,
    elementSelection: 'paragraphs',
    animationSpeed: 0.6
  };

  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
  
  let rafId = null;
  let cachedElements = new Map();
  let observer = null;
  
  function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function() {
      const context = this;
      const args = arguments;
      if (!lastRan) {
        func.apply(context, args);
        lastRan = Date.now();
      } else {
        clearTimeout(lastFunc);
        lastFunc = setTimeout(function() {
          if ((Date.now() - lastRan) >= limit) {
            func.apply(context, args);
            lastRan = Date.now();
          }
        }, limit - (Date.now() - lastRan));
      }
    };
  }

  document.addEventListener('DOMContentLoaded', initTextReveal);
  
  function initTextReveal() {
    const revealSections = document.querySelectorAll('[data-text-reveal-section]');
    
    if (!revealSections.length) return;
    
    setupIntersectionObserver();
    revealSections.forEach(processTextRevealSection);
    
    window.addEventListener('scroll', throttledHandleScroll, { passive: true });
    
    requestAnimationFrame(handleScroll);
  }
  
  function setupIntersectionObserver() {
    observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          if (!cachedElements.has(entry.target)) {
            cacheElementData(entry.target);
          }
        }
      });
    }, {
      root: null,
      rootMargin: '50px',
      threshold: 0
    });
  }
  
  function cacheElementData(element) {
    const rect = element.getBoundingClientRect();
    const windowHeight = window.innerHeight;
    
    cachedElements.set(element, {
      element: element,
      rect: rect,
      lastUpdate: Date.now(),
      windowHeight: windowHeight
    });
  }
  
  function processTextRevealSection(section) {
    const startColor = section.getAttribute('data-text-reveal-start-color') || CONFIG.startColor;
    const endColor = section.getAttribute('data-text-reveal-end-color') || CONFIG.endColor;
    const triggerPosition = parseFloat(section.getAttribute('data-text-reveal-trigger-position')) || CONFIG.triggerPosition;
    const animationLength = parseFloat(section.getAttribute('data-text-reveal-animation-length')) || CONFIG.animationLength;
    const revealType = section.getAttribute('data-text-reveal-type') || CONFIG.revealType;
    const targetSelector = section.getAttribute('data-text-reveal-target') || null;
    
    let textElements = [];
    
    if (targetSelector) {
      const targetElement = document.querySelector(targetSelector);
      if (targetElement) {
        textElements = [targetElement];
      }
    } else {
      if (CONFIG.elementSelection === 'headings') {
        textElements = Array.from(section.querySelectorAll('h1, h2, h3, h4, h5, h6, .brxe-heading'));
      } else if (CONFIG.elementSelection === 'paragraphs') {
        textElements = Array.from(section.querySelectorAll('p, .brxe-text, .brxe-text-basic'));
      } else {
        textElements = Array.from(section.querySelectorAll(
          '.brxe-heading, .brxe-text, .brxe-text-basic, ' + 
          '.bricks-heading, .bricks-text, ' +
          '.has-text, .text-wrapper, ' +
          'h1, h2, h3, h4, h5, h6, p, .text'
        ));
        
        if (!textElements.length) {
          const allElements = Array.from(section.querySelectorAll('*'));
          
          textElements = allElements.filter(element => {
            if (element.tagName.match(/^(DIV|SECTION|ARTICLE|ASIDE|FIGURE|HEADER|FOOTER|NAV|MAIN|UL|OL|FORM|TABLE)$/i)) {
              return false;
            }
            
            const hasDirectText = Array.from(element.childNodes).some(node => 
              node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0
            );
            
            return hasDirectText;
          });
        }
      }
    }
    
    if (!textElements.length) {
      return;
    }
    
    section.dataset.textRevealProcessed = 'true';
    section.dataset.textRevealTriggerPosition = triggerPosition;
    section.dataset.textRevealAnimationLength = animationLength;
    section.dataset.textRevealType = revealType;
    
    textElements.forEach(element => {
      if (element.textContent && element.textContent.trim()) {
        processTextElement(element, section, startColor, endColor, revealType);
        if (observer) {
          observer.observe(element);
        }
      }
    });
  }
  
  function processTextElement(element, section, startColor, endColor, revealType) {
    if (element.dataset.textRevealProcessed === 'true') {
      return;
    }
    
    const originalText = element.textContent.trim();
    
    if (!originalText) {
      return;
    }
    
    const computedStyle = window.getComputedStyle(element);
    const originalFontSize = computedStyle.fontSize;
    const originalFontWeight = computedStyle.fontWeight;
    const originalLineHeight = computedStyle.lineHeight;
    const originalFontFamily = computedStyle.fontFamily;
    const originalTextAlign = computedStyle.textAlign;
    
    element.innerHTML = '';
    
    const textContainer = document.createElement('div');
    textContainer.className = 'text-reveal-container';
    textContainer.style.cssText = `
      display: block;
      width: 100%;
      font-size: ${originalFontSize};
      font-weight: ${originalFontWeight};
      line-height: ${originalLineHeight};
      font-family: ${originalFontFamily};
      text-align: ${originalTextAlign};
    `;
    
    const transitionDuration = Math.max(CONFIG.animationSpeed, 0.01);
    
    if (revealType === 'letter') {
      const words = originalText.split(/\s+/);
      let letterIndex = 0;
      const lettersTotal = originalText.replace(/\s+/g, '').length;
      
      const wordFlowContainer = document.createElement('span');
      wordFlowContainer.className = 'text-reveal-word-flow';
      wordFlowContainer.style.cssText = `
        display: inline;
        width: auto;
      `;
      textContainer.appendChild(wordFlowContainer);
      
      words.forEach((word, wordIndex) => {
        const wordContainer = document.createElement('span');
        wordContainer.className = 'text-reveal-word-container';
        wordContainer.style.cssText = `
          display: inline-block;
          margin-right: 0.25em;
          white-space: nowrap;
        `;
        
        for (let i = 0; i < word.length; i++) {
          const letter = word[i];
          
          const letterContainer = document.createElement('span');
          letterContainer.className = 'text-reveal-letter';
          letterContainer.style.cssText = `
            position: relative;
            display: inline-block;
          `;
          
          const initialLetterSpan = document.createElement('span');
          initialLetterSpan.className = 'text-reveal-initial';
          initialLetterSpan.textContent = letter;
          initialLetterSpan.style.cssText = `
            position: absolute;
            top: 0;
            left: 0;
            color: ${startColor};
            opacity: 1;
            transition: opacity ${transitionDuration}s ease;
          `;
          
          const revealedLetterSpan = document.createElement('span');
          revealedLetterSpan.className = 'text-reveal-revealed';
          revealedLetterSpan.textContent = letter;
          revealedLetterSpan.style.cssText = `
            position: relative;
            color: ${endColor};
            opacity: 0;
            transition: opacity ${transitionDuration}s ease;
          `;
          
          letterContainer.dataset.index = letterIndex;
          letterContainer.dataset.total = lettersTotal;
          letterContainer.dataset.revealType = revealType;
          
          letterIndex++;
          
          letterContainer.appendChild(initialLetterSpan);
          letterContainer.appendChild(revealedLetterSpan);
          wordContainer.appendChild(letterContainer);
        }
        
        wordFlowContainer.appendChild(wordContainer);
        
        if (wordIndex < words.length - 1) {
          const spaceElement = document.createElement('span');
          spaceElement.className = 'text-reveal-space';
          spaceElement.style.cssText = `
            display: inline-block;
            width: 0.25em;
          `;
          wordFlowContainer.appendChild(spaceElement);
        }
      });
    } else if (revealType === 'line') {
      const lineContainer = document.createElement('span');
      lineContainer.className = 'text-reveal-line';
      lineContainer.style.cssText = `
        position: relative;
        display: inline-block;
        width: 100%;
      `;
      
      const initialLineSpan = document.createElement('span');
      initialLineSpan.className = 'text-reveal-initial';
      initialLineSpan.textContent = originalText;
      initialLineSpan.style.cssText = `
        position: absolute;
        top: 0;
        left: 0;
        color: ${startColor};
        opacity: 1;
        transition: opacity ${transitionDuration}s ease;
        width: 100%;
      `;
      
      const revealedLineSpan = document.createElement('span');
      revealedLineSpan.className = 'text-reveal-revealed';
      revealedLineSpan.textContent = originalText;
      revealedLineSpan.style.cssText = `
        position: relative;
        color: ${endColor};
        opacity: 0;
        transition: opacity ${transitionDuration}s ease;
        width: 100%;
      `;
      
      lineContainer.dataset.index = 0;
      lineContainer.dataset.total = 1;
      lineContainer.dataset.revealType = revealType;
      
      lineContainer.appendChild(initialLineSpan);
      lineContainer.appendChild(revealedLineSpan);
      textContainer.appendChild(lineContainer);
    } else {
      const words = originalText.split(/\s+/);
      
      const wordFlowContainer = document.createElement('span');
      wordFlowContainer.className = 'text-reveal-word-flow';
      wordFlowContainer.style.cssText = `
        display: inline;
        width: auto;
      `;
      textContainer.appendChild(wordFlowContainer);
      
      words.forEach((word, index) => {
        const wordContainer = document.createElement('span');
        wordContainer.className = 'text-reveal-word';
        wordContainer.style.cssText = `
          position: relative;
          margin: 0 0.25em 0 0;
          display: inline-block;
        `;
        
        const initialWordSpan = document.createElement('span');
        initialWordSpan.className = 'text-reveal-initial';
        initialWordSpan.textContent = word;
        initialWordSpan.style.cssText = `
          position: absolute;
          top: 0;
          left: 0;
          color: ${startColor};
          opacity: 1;
          transition: opacity ${transitionDuration}s ease;
        `;
        
        const revealedWordSpan = document.createElement('span');
        revealedWordSpan.className = 'text-reveal-revealed';
        revealedWordSpan.textContent = word;
        revealedWordSpan.style.cssText = `
          position: relative;
          color: ${endColor};
          opacity: 0;
          transition: opacity ${transitionDuration}s ease;
        `;
        
        wordContainer.dataset.index = index;
        wordContainer.dataset.total = words.length;
        wordContainer.dataset.revealType = revealType;
        
        wordContainer.appendChild(initialWordSpan);
        wordContainer.appendChild(revealedWordSpan);
        wordFlowContainer.appendChild(wordContainer);
        
        if (index < words.length - 1) {
          const spaceElement = document.createElement('span');
          spaceElement.className = 'text-reveal-space';
          spaceElement.style.cssText = `
            display: inline-block;
            width: 0.25em;
          `;
          wordFlowContainer.appendChild(spaceElement);
        }
      });
    }
    
    element.appendChild(textContainer);
    element.dataset.textRevealProcessed = 'true';
  }
  
  const throttledHandleScroll = throttle(handleScroll, 16);
  
  function handleScroll() {
    if (rafId) cancelAnimationFrame(rafId);
    
    rafId = requestAnimationFrame(() => {
      const revealSections = document.querySelectorAll('[data-text-reveal-section][data-text-reveal-processed="true"]');
      
      revealSections.forEach(section => {
        const windowHeight = window.innerHeight;
        
        const textElements = section.querySelectorAll('[data-text-reveal-processed="true"]');
        
        textElements.forEach(element => {
          let cachedData = cachedElements.get(element);
          
          if (!cachedData || Date.now() - cachedData.lastUpdate > 100) {
            cachedData = {
              rect: element.getBoundingClientRect(),
              lastUpdate: Date.now()
            };
            cachedElements.set(element, cachedData);
          }
          
          const elementRect = cachedData.rect;
          const elementCenter = elementRect.top + (elementRect.height / 2);
          
          if (elementRect.bottom < 0 || elementRect.top > windowHeight) {
            return;
          }
          
          const triggerPosition = parseFloat(section.dataset.textRevealTriggerPosition || CONFIG.triggerPosition);
          const triggerPoint = (windowHeight * triggerPosition) / 100;
          
          const animationLength = parseFloat(section.dataset.textRevealAnimationLength || CONFIG.animationLength);
          const animationDistance = (windowHeight * animationLength) / 100;
          
          let scrollProgress = (elementCenter - triggerPoint) / animationDistance;
          scrollProgress = scrollProgress * (1 / CONFIG.animationSpeed);
          
          let elementProgress = clamp(scrollProgress, 0, 1);
          const progress = elementProgress;
          
          const revealType = section.dataset.textRevealType || CONFIG.revealType;
          
          if (revealType === 'letter') {
            const letters = element.querySelectorAll('.text-reveal-letter');
            const totalLetters = letters.length;
            
            const letterCountFromData = parseInt(letters[0]?.dataset.total || totalLetters);
            
            letters.forEach((letter) => {
              let letterProgress;
              
              const originalLetterIndex = parseInt(letter.dataset.index || 0);
              const letterIndex = letterCountFromData - 1 - originalLetterIndex;
              
              const segmentSize = 1 / letterCountFromData;
              const letterStart = letterIndex * segmentSize;
              const letterEnd = (letterIndex + 1) * segmentSize;
              
              if (progress <= letterStart) {
                letterProgress = 0;
              } else if (progress >= letterEnd) {
                letterProgress = 1;
              } else {
                letterProgress = (progress - letterStart) / segmentSize;
              }
              
              const revealedLetter = letter.querySelector('.text-reveal-revealed');
              const initialLetter = letter.querySelector('.text-reveal-initial');
              
              if (revealedLetter) revealedLetter.style.opacity = letterProgress;
              if (initialLetter) initialLetter.style.opacity = 1 - letterProgress;
            });
          } else if (revealType === 'line') {
            const lines = element.querySelectorAll('.text-reveal-line');
            
            lines.forEach(line => {
              const revealedLine = line.querySelector('.text-reveal-revealed');
              const initialLine = line.querySelector('.text-reveal-initial');
              
              if (revealedLine) revealedLine.style.opacity = progress;
              if (initialLine) initialLine.style.opacity = 1 - progress;
            });
          } else {
            const words = element.querySelectorAll('.text-reveal-word');
            const totalWords = words.length;
            
            words.forEach((word, index) => {
              let wordProgress;
              
              const originalWordIndex = parseInt(word.dataset.index || 0);
              const wordIndex = totalWords - 1 - originalWordIndex;
              
              const segmentSize = 1 / totalWords;
              const wordStart = wordIndex * segmentSize;
              const wordEnd = (wordIndex + 1) * segmentSize;
              
              if (progress <= wordStart) {
                wordProgress = 0;
              } else if (progress >= wordEnd) {
                wordProgress = 1;
              } else {
                wordProgress = (progress - wordStart) / segmentSize;
              }
              
              const revealedWord = word.querySelector('.text-reveal-revealed');
              const initialWord = word.querySelector('.text-reveal-initial');
              
              if (revealedWord) revealedWord.style.opacity = wordProgress;
              if (initialWord) initialWord.style.opacity = 1 - wordProgress;
            });
          }
        });
      });
    });
  }
  
  window.addEventListener('resize', throttle(() => {
    cachedElements.clear();
    handleScroll();
  }, 100), { passive: true });
  
  window.destroyTextReveal = function() {
    if (rafId) cancelAnimationFrame(rafId);
    window.removeEventListener('scroll', throttledHandleScroll);
    window.removeEventListener('resize', handleScroll);
    
    if (observer) {
      observer.disconnect();
      observer = null;
    }
    
    cachedElements.clear();
    
    const sections = document.querySelectorAll('[data-text-reveal-section][data-text-reveal-processed="true"]');
    sections.forEach(section => {
      section.removeAttribute('data-text-reveal-processed');
      section.removeAttribute('data-text-reveal-trigger-position');
      section.removeAttribute('data-text-reveal-animation-length');
      section.removeAttribute('data-text-reveal-type');
      
      const textElements = section.querySelectorAll('[data-text-reveal-processed="true"]');
      textElements.forEach(element => {
        const textContainers = element.querySelectorAll('.text-reveal-word, .text-reveal-word-container, .text-reveal-letter, .text-reveal-line, .text-reveal-space');
        let originalText = '';
        
        if (textContainers.length > 0) {
          const firstContainer = textContainers[0];
          
          if (firstContainer.classList.contains('text-reveal-line')) {
            const textElement = firstContainer.querySelector('.text-reveal-revealed');
            if (textElement) originalText = textElement.textContent;
          } else if (firstContainer.classList.contains('text-reveal-word')) {
            element.querySelectorAll('.text-reveal-word').forEach((container, index) => {
              if (index > 0) originalText += ' ';
              const textEl = container.querySelector('.text-reveal-revealed');
              if (textEl) originalText += textEl.textContent;
            });
          } else if (firstContainer.classList.contains('text-reveal-word-container')) {
            element.querySelectorAll('.text-reveal-word-container').forEach((container, index) => {
              if (index > 0) originalText += ' ';
              let wordText = '';
              container.querySelectorAll('.text-reveal-letter').forEach(letter => {
                const textEl = letter.querySelector('.text-reveal-revealed');
                if (textEl) wordText += textEl.textContent;
              });
              originalText += wordText;
            });
          } else {
            textContainers.forEach(container => {
              if (container.classList.contains('text-reveal-space')) {
                originalText += ' ';
              } else if (container.classList.contains('text-reveal-letter')) {
                const textEl = container.querySelector('.text-reveal-revealed');
                if (textEl) originalText += textEl.textContent;
              }
            });
          }
        }
        
        element.innerHTML = originalText.trim();
        element.removeAttribute('data-text-reveal-processed');
      });
    });
    
    window.textRevealInitialized = false;
  };

  if (document.readyState !== 'loading') {
    initTextReveal();
  }
})();
(function() {
  window.AvatarCircles = window.AvatarCircles || {};
  
  function isMobileDevice() {
    const userAgent = navigator.userAgent || navigator.vendor || window.opera;
    if (/android|iphone|ipad|ipod|blackberry|windows phone/i.test(userAgent)) {
      return true;
    }
    
    if (window.innerWidth < 768) {
      return true;
    }
    
    return false;
  }
  
  function initAvatarCircles() {
    // ✅ CORREGIDO: Ya no bloqueamos móviles aquí
    // if (isMobileDevice()) {
    //   console.log('Mobile device detected, Avatar Circles will not initialize');
    //   return;
    // }
    
    const containers = document.querySelectorAll('[data-avatar-circles]');
    
    containers.forEach((container) => {
      try {
        // Skip if already initialized
        if (container.hasAttribute('data-circles-initialized')) {
          return;
        }
        
        // Mark as initialized immediately to prevent duplicate processing
        container.setAttribute('data-circles-initialized', 'true');
        
        
        container.setAttribute('data-avatar-image-1', 'https://randomuser.me/api/portraits/women/44.jpg');
        container.setAttribute('data-avatar-profile-1', '#');
        container.setAttribute('data-avatar-bg-1', 'transparent');
        
        container.setAttribute('data-avatar-extra', '0');
        container.setAttribute('data-avatar-extra-url', '#');
        container.setAttribute('data-counter-style', 'circle');
        container.setAttribute('data-abbreviate-numbers', 'true');
        container.setAttribute('data-extra-text-color', '#ffffff');
        container.setAttribute('data-animation-type', 'none');
        container.setAttribute('data-animation-speed', '500');
        container.setAttribute('data-animate-on-scroll', 'true');
        
        let avatarCount = 1;
        const avatars = [];
        
        while (true) {
          const imageUrl = container.getAttribute(`data-avatar-image-${avatarCount}`);
          if (!imageUrl) break;
          
          avatars.push({
            imageUrl,
            profileUrl: container.getAttribute(`data-avatar-profile-${avatarCount}`) || '#',
            backgroundColor: container.getAttribute(`data-avatar-bg-${avatarCount}`) || 'transparent'
          });
          avatarCount++;
        }
        
        if (avatars.length === 0) return;
        
        const numExtraPeople = parseInt(container.getAttribute('data-avatar-extra') || '0', 10);
        const counterStyle = container.getAttribute('data-counter-style') || 'circle';
        const abbreviateNumbers = container.getAttribute('data-abbreviate-numbers') === 'true';
        const extraTextColor = container.getAttribute('data-extra-text-color') || '#ffffff';
        const animationType = container.getAttribute('data-animation-type') || 'none';
        const animationSpeed = parseInt(container.getAttribute('data-animation-speed') || '500');
        const animateOnScroll = container.getAttribute('data-animate-on-scroll') === 'true';
        
        container.innerHTML = '';
        Object.assign(container.style, {
          display: 'flex',
          position: 'relative',
          zIndex: '10'
        });
        
        const avatarSize = 40;
        const borderWidth = 4;
        const borderColor = '#d9d9d9';
        const extraBgColor = '#4a6cf7';
        const extraHoverColor = '#3451b2';
        const overlap = 16;
        
        if (animationType !== 'none' && !document.querySelector('#avatar-circles-animations')) {
          const styleSheet = document.createElement('style');
          styleSheet.id = 'avatar-circles-animations';
          styleSheet.textContent = '            @keyframes fadeInUp {              from { opacity: 0; transform: translateY(20px); }              to { opacity: 1; transform: translateY(0); }            }                        @keyframes breathing {              0%, 100% { transform: scale(1); }              50% { transform: scale(1.05); }            }                        @keyframes glow {              0%, 100% { box-shadow: 0 0 5px rgba(255, 255, 255, 0.1); }              50% { box-shadow: 0 0 15px rgba(239, 96, 19, 0.6); }            }                        .avatar-cascade { opacity: 0; }            .avatar-cascade.animate { animation-name: fadeInUp; animation-fill-mode: forwards; }            .avatar-breathing img { animation: breathing 3s infinite ease-in-out; }            .avatar-glow img { animation: glow 3s infinite ease-in-out; }          ';
          document.head.appendChild(styleSheet);
        }
        
        avatars.forEach((avatar, index) => {
          const link = document.createElement('a');
          link.href = avatar.profileUrl;
          link.target = '_blank';
          link.rel = 'noopener noreferrer';
          
          if (animationType !== 'none') {
            link.classList.add('avatar-' + animationType);
            
            if (animationType === 'cascade') {
              const delay = index * (animationSpeed / 1000 / avatars.length);
              link.style.animationDelay = delay + 's';
              link.style.animationDuration = (animationSpeed / 1000) + 's';
            }
          }
          
          Object.assign(link.style, {
            marginLeft: index > 0 ? `-${overlap}px` : '0',
            transition: 'all 0.3s ease',
            position: 'relative',
            zIndex: avatars.length - index
          });
          
          const img = document.createElement('img');
          img.src = avatar.imageUrl;
          img.width = avatarSize;
          img.height = avatarSize;
          img.alt = `Avatar ${index + 1}`;
          
          Object.assign(img.style, {
            height: `${avatarSize}px`,
            width: `${avatarSize}px`,
            borderRadius: '50%',
            border: `${borderWidth}px solid ${borderColor}`,
            backgroundColor: avatar.backgroundColor,
            transition: 'all 0.3s ease',
            objectFit: 'cover'
          });
          
          link.appendChild(img);
          container.appendChild(link);
        });
        
        if (numExtraPeople > 0) {
          const morePeopleLink = document.createElement('a');
          morePeopleLink.href = container.getAttribute('data-avatar-extra-url') || '#';
          
          let displayText;
          if (abbreviateNumbers) {
            if (numExtraPeople >= 1000000) {
              displayText = (Math.floor(numExtraPeople / 100000) / 10).toFixed(1).replace(/\.0$/, '') + 'M+';
            } else if (numExtraPeople >= 1000) {
              displayText = (Math.floor(numExtraPeople / 100) / 10).toFixed(1).replace(/\.0$/, '') + 'K+';
            } else {
              displayText = numExtraPeople + '+';
            }
          } else {
            displayText = numExtraPeople + '+';
          }
          
          morePeopleLink.textContent = displayText;
          
          if (animationType !== 'none') {
            morePeopleLink.classList.add('avatar-' + animationType);
            
            if (animationType === 'cascade') {
              const delay = avatars.length * (animationSpeed / 1000 / (avatars.length + 1));
              morePeopleLink.style.animationDelay = delay + 's';
              morePeopleLink.style.animationDuration = (animationSpeed / 1000) + 's';
            }
          }
          
          let counterCSS = {
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            height: `${avatarSize}px`,
            width: `${avatarSize}px`,
            borderRadius: '50%',
            border: `${borderWidth}px solid ${borderColor}`,
            backgroundColor: extraBgColor,
            color: extraTextColor,
            fontSize: `${Math.max(12, avatarSize / 3)}px`,
            fontWeight: '600',
            textDecoration: 'none',
            marginLeft: `-${overlap}px`,
            transition: 'all 0.3s ease'
          };
          
          if (counterStyle === 'auto-expand') {
            const digitCount = displayText.length;
            const extraWidth = avatarSize * (1 + (digitCount > 2 ? (digitCount - 2) * 0.35 : 0));
            counterCSS.width = `${extraWidth}px`;
          } else if (counterStyle === 'pill') {
            counterCSS.borderRadius = `${avatarSize / 2}px`;
            counterCSS.minWidth = `${avatarSize}px`;
            counterCSS.padding = '0 10px';
          } else if (counterStyle === 'badge') {
            counterCSS = {
              ...counterCSS,
              height: `${Math.max(24, avatarSize * 0.7)}px`,
              minWidth: `${Math.max(24, avatarSize * 0.7)}px`,
              borderRadius: '20px',
              marginLeft: '5px',
              boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
              whiteSpace: 'nowrap',
              paddingLeft: `${Math.max(12, displayText.length * 3)}px`,
              paddingRight: `${Math.max(12, displayText.length * 3)}px`
            };
          }
          
          Object.assign(morePeopleLink.style, counterCSS);
          container.appendChild(morePeopleLink);
        }
        
        if (animationType === 'cascade' && animateOnScroll) {
          const observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
              if (entry.isIntersecting) {
                setTimeout(() => {
                  entry.target.querySelectorAll('.avatar-cascade').forEach(el => {
                    el.classList.add('animate');
                  });
                }, 100);
                observer.unobserve(entry.target);
              }
            });
          }, { threshold: 0.2 });
          
          observer.observe(container);
        } else if (animationType === 'cascade') {
          setTimeout(() => {
            container.querySelectorAll('.avatar-cascade').forEach(el => {
              el.classList.add('animate');
            });
          }, 100);
        }
      } catch (error) {
        console.warn('Error initializing avatar circles:', error);
      }
    });
  }
  
  // Single initialization strategy to prevent the flicker
  let initialized = false;
  
  function init() {
    // ✅ CORREGIDO: Ya no bloqueamos móviles aquí tampoco
    if (initialized) {
      return;
    }
    
    initialized = true;
    initAvatarCircles();
  }
  
  // Use only one event to prevent multiple initializations
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
  
  // Only use one additional initialization attempt to catch any late-loaded content
  window.addEventListener('load', init);
  
  window.AvatarCircles = {
    init: init,
    version: '1.0',
    isMobile: isMobileDevice
  };
})();
BENEFITS

Why Choose Us

Passion and Purpose

Data-Driven Strategy, Always.

We leverage integrated systems and streamlined reporting to deliver accurate, real-time performance metrics.

Specialized Small Business Marketing.

As a boutique agency focused on small businesses, we apply deep local market knowledge to tailor custom strategies designed for your unique goals.

Operational Freedom & Training.

We implement scalable processes, documented SOPs, and continuous optimization. We return your time so you can focus on running your core business.

(function(){
            const style = document.createElement('style');
            style.textContent = `
              .brand-carousel-preview {
                display: flex;
                align-items: center;
                overflow: hidden;
                position: relative;
                width: 100%;
                height: 100%;
              }
              
              .brand-carousel-track {
                display: flex;
                align-items: center;
                will-change: transform;
                padding: 0;
                position: relative;
                gap: 30px;
                backface-visibility: hidden;
                perspective: 1000px;
                transform: translateZ(0);
              }
              
              .brand-logo {
                height: 80px;
                width: auto;
                opacity: 0.9;
                transition: all 0.3s ease;
                display: block;
                max-width: none;
                filter: brightness(0.95) contrast(1.1);
                
                border-radius: 8px;
                flex-shrink: 0;
              }
              
              .brand-logo:hover {
                opacity: 1;
                transform: translateY(-2px);
                filter: brightness(1) contrast(1.2);
                
              }
              
              .carousel-blur-left,
              .carousel-blur-right {
                position: absolute;
                top: 0;
                bottom: 0;
                width: min(80px, 15%);
                pointer-events: none;
                z-index: 1;
              }
              
              .carousel-blur-left {
                left: 0;
                background: linear-gradient(to right, rgba(245,245,245,0.9), rgba(245,245,245,0));
              }
              
              .carousel-blur-right {
                right: 0;
                background: linear-gradient(to left, rgba(245,245,245,0.9), rgba(245,245,245,0));
              }
            `;
            document.head.appendChild(style);
            
            class InfiniteCarousel {
              constructor(container, options = {}) {
                this.container = container;
                this.track = container.querySelector('.brand-carousel-track');
                this.options = {
                  speed: options.speed || 1,
                  gap: options.gap || 30,
                  ...options
                };
                
                this.animationId = null;
                this.currentX = 0;
                this.logos = [];
                this.clones = [];
                this.containerWidth = 0;
                this.contentWidth = 0;
                this.isRunning = false;
                this.isScrolling = false;
                this.lastMeasureTime = 0;
                this.isVisible = true;
                
                this.init();
              }
              
              init() {
                this.logos = Array.from(this.track.children);
                if (this.logos.length === 0) return;
                
                this.preloadImages().then(() => {
                  this.measureDimensions();
                  this.createClones();
                  this.setupIntersectionObserver();
                  this.setupScrollDetection();
                  this.setupResizeHandler();
                  this.start();
                });
              }
              
              preloadImages() {
                const images = this.logos.filter(logo => logo.tagName === 'IMG');
                const promises = images.map(img => {
                  return new Promise((resolve) => {
                    if (img.complete && img.naturalWidth > 0) {
                      resolve();
                    } else {
                      const handleLoad = () => {
                        img.removeEventListener('load', handleLoad);
                        img.removeEventListener('error', handleError);
                        resolve();
                      };
                      
                      const handleError = () => {
                        img.removeEventListener('load', handleLoad);
                        img.removeEventListener('error', handleError);
                        if (img.src.includes('.svg')) {
                          console.warn('SVG failed to load on mobile:', img.src);
                        }
                        resolve();
                      };
                      
                      img.addEventListener('load', handleLoad);
                      img.addEventListener('error', handleError);
                      
                      setTimeout(() => {
                        if (!img.complete) {
                          handleError();
                        }
                      }, 3000);
                    }
                  });
                });
                
                return Promise.all(promises);
              }
              
              setupIntersectionObserver() {
                const observer = new IntersectionObserver((entries) => {
                  entries.forEach(entry => {
                    this.isVisible = entry.isIntersecting;
                    if (!this.isVisible) {
                      this.pause();
                    } else {
                      this.resume();
                    }
                  });
                }, {
                  threshold: 0.1,
                  rootMargin: '50px'
                });
                
                observer.observe(this.container);
                this.intersectionObserver = observer;
              }
              
              setupScrollDetection() {
                let scrollTimer = null;
                
                const handleScroll = () => {
                  this.isScrolling = true;
                  clearTimeout(scrollTimer);
                  
                  scrollTimer = setTimeout(() => {
                    this.isScrolling = false;
                  }, 150);
                };
                
                window.addEventListener('scroll', handleScroll, { passive: true });
                window.addEventListener('touchmove', handleScroll, { passive: true });
                
                this.scrollHandler = handleScroll;
              }
              
              setupResizeHandler() {
                let resizeTimer = null;
                let lastWidth = this.container.offsetWidth;
                
                const handleResize = () => {
                  if (this.isScrolling) return;
                  
                  const currentWidth = this.container.offsetWidth;
                  if (Math.abs(currentWidth - lastWidth) < 10) return;
                  
                  lastWidth = currentWidth;
                  clearTimeout(resizeTimer);
                  
                  resizeTimer = setTimeout(() => {
                    if (!this.isScrolling && this.isVisible) {
                      this.measureDimensions();
                      this.createClones();
                    }
                  }, 300);
                };
                
                window.addEventListener('resize', handleResize, { passive: true });
                this.resizeHandler = handleResize;
              }
              
              measureDimensions() {
                const now = Date.now();
                if (now - this.lastMeasureTime < 100) return;
                this.lastMeasureTime = now;
                
                this.containerWidth = this.container.offsetWidth;
                
                this.contentWidth = 0;
                this.logos.forEach(logo => {
                  if (logo.offsetWidth > 0) {
                    this.contentWidth += logo.offsetWidth + this.options.gap;
                  }
                });
                this.contentWidth = Math.max(this.contentWidth - this.options.gap, 100);
              }
              
              createClones() {
                if (this.isScrolling) return;
                
                this.clones.forEach(clone => clone.remove());
                this.clones = [];
                
                if (this.contentWidth === 0) return;
                
                const totalNeeded = Math.ceil((this.containerWidth * 2.5) / this.contentWidth) + 1;
                
                for (let i = 0; i < totalNeeded; i++) {
                  this.logos.forEach(logo => {
                    const clone = logo.cloneNode(true);
                    clone.classList.add('carousel-clone');
                    this.track.appendChild(clone);
                    this.clones.push(clone);
                  });
                }
              }
              
              start() {
                if (this.isRunning || !this.isVisible) return;
                this.isRunning = true;
                this.animate();
              }
              
              pause() {
                this.isRunning = false;
                if (this.animationId) {
                  cancelAnimationFrame(this.animationId);
                  this.animationId = null;
                }
              }
              
              resume() {
                if (!this.isRunning && this.isVisible) {
                  this.start();
                }
              }
              
              stop() {
                this.pause();
              }
              
              animate() {
                if (!this.isRunning || !this.isVisible) return;
                
                this.currentX -= this.options.speed * 0.5;
                
                if (Math.abs(this.currentX) >= this.contentWidth + this.options.gap) {
                  this.currentX = 0;
                }
                
                this.track.style.transform = `translateX(${this.currentX}px)`;
                
                this.animationId = requestAnimationFrame(() => this.animate());
              }
              
              destroy() {
                this.stop();
                this.clones.forEach(clone => clone.remove());
                this.clones = [];
                
                if (this.intersectionObserver) {
                  this.intersectionObserver.disconnect();
                }
                
                if (this.resizeHandler) {
                  window.removeEventListener('resize', this.resizeHandler);
                }
                
                if (this.scrollHandler) {
                  window.removeEventListener('scroll', this.scrollHandler);
                  window.removeEventListener('touchmove', this.scrollHandler);
                }
              }
            }
            
            function init() {
              const c = document.querySelector('[data-brand-carousel]');
              if(!c) {
                console.warn('Brand Carousel: Container with data-brand-carousel attribute not found');
                return;
              }
              
              if(c._carouselInstance) {
                c._carouselInstance.destroy();
              }
              c.innerHTML = '';
              
              const preview = document.createElement('div');
              preview.className = 'brand-carousel-preview';
              
              const computedStyle = window.getComputedStyle(c);
              preview.style.width = '100%';
              preview.style.height = '100%';
              preview.style.position = 'relative';
              preview.style.display = 'flex';
              preview.style.alignItems = 'center';
              preview.style.overflow = 'hidden';
              
              const track = document.createElement('div');
              track.className = 'brand-carousel-track';
              
              const leftBlur = document.createElement('div');
              leftBlur.className = 'carousel-blur-left';
              const rightBlur = document.createElement('div');
              rightBlur.className = 'carousel-blur-right';
              
              c.setAttribute('data-brand-1', 'https://library.bricksfusion.com/wp-content/uploads/2025/06/pitch.svg');
              c.setAttribute('data-brand-2', 'https://library.bricksfusion.com/wp-content/uploads/2025/06/pinia.svg');
              c.setAttribute('data-brand-3', 'https://library.bricksfusion.com/wp-content/uploads/2025/06/obsidian.svg');
              c.setAttribute('data-brand-4', 'https://library.bricksfusion.com/wp-content/uploads/2025/06/atlassian-1.svg');
              
              let logos = [];
              for(let i = 1; i <= 8; i++) {
                const src = c.getAttribute(`data-brand-${i}`);
                if(src) {
                  logos.push({
                    src: src,
                    alt: `Brand ${i}`
                  });
                }
              }
              
              if (logos.length === 0) {
                logos = [
                  { src: "https://www.svgrepo.com/show/303205/html-5-logo.svg", alt: "HTML5" },
                  { src: "https://www.svgrepo.com/show/303481/css-3-logo.svg", alt: "CSS3" },
                  { src: "https://www.svgrepo.com/show/303206/javascript-logo.svg", alt: "JavaScript" },
                  { src: "https://www.svgrepo.com/show/303266/nodejs-icon-logo.svg", alt: "Node.js" }
                ];
              }
              
              logos.forEach(logo => {
                const img = document.createElement('img');
                img.src = logo.src;
                img.alt = logo.alt;
                img.className = 'brand-logo';
                track.appendChild(img);
              });
              
              preview.appendChild(leftBlur);
              preview.appendChild(track);
              preview.appendChild(rightBlur);
              c.appendChild(preview);
              
              setTimeout(() => {
                c._carouselInstance = new InfiniteCarousel(preview, {
                  speed: 1,
                  gap: 30
                });
              }, 100);
            }
            
            function reinitCarousel() {
              const carousel = document.querySelector('[data-brand-carousel]');
              if (carousel?._carouselInstance) carousel._carouselInstance.destroy();
              setTimeout(init, 100);
            }
            
            if (document.readyState === 'loading') {
              document.addEventListener('DOMContentLoaded', init);
            } else {
              init();
            }
            
            document.addEventListener('bricks/content_loaded', reinitCarousel);
            
            let resizeTimer;
            window.addEventListener('resize', () => {
              clearTimeout(resizeTimer);
              resizeTimer = setTimeout(reinitCarousel, 200);
            });
            
            setTimeout(init, 100);
          })();
FEATURES

All Services in 1 Agency

Let us help you unlock marketing and processess that simplify workflows & grow your business.

Cutting-Edge Marketing Approach

Deploy AI solutions that adapt quickly, learn fast, and scale with your business needs.

Automated Workflows

Streamline tasks and boost efficiency with powerful, scalable AI-powered automation tools for growing teams and projects.

Insightful Analytics

Gain deep, real-time data insights with advanced AI analytics to guide smarter strategies, decisions, and scalable business growth.

White Glove Support

Enhance customer experience with AI-driven virtual assistants available for support and engagement.

Get StartedSee Our Services
PROCESS

Simple & Scalable

A transparent process of collaboration and feedback.

Business Assessment

We begin by examining your existing workflows to identify where AI can deliver the greatest impact.

01
Marry Your Message + Launch

Our team develops custom AI systems built around your goals, ensuring safe and reliable deployment.

02
Ongoing Support & Optimization

After deployment, we provide support and refine your AI systems to keep them performing at their best.

03