KANOcx
Your Boutique Consulting Partner for Operations, CX, and Digital Performance
"Focus on your passion while we handle complex operations, digital strategy and customer experience."
(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
};
})();Why Choose Us
Passion and Purpose

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

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

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);
})();All Services in 1 Agency
Let us help you unlock marketing and processess that simplify workflows & grow your business.

Deploy AI solutions that adapt quickly, learn fast, and scale with your business needs.
Streamline tasks and boost efficiency with powerful, scalable AI-powered automation tools for growing teams and projects.
Gain deep, real-time data insights with advanced AI analytics to guide smarter strategies, decisions, and scalable business growth.

Enhance customer experience with AI-driven virtual assistants available for support and engagement.
Simple & Scalable
A transparent process of collaboration and feedback.
We begin by examining your existing workflows to identify where AI can deliver the greatest impact.

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

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