André Guindi is a 2D animator and illustrator creating expressive, high-energy character animation for major networks while taking on your custom art commissions—and periodically coding nostalgia into this unnecessarily whimsical website.



PORTFOLIO




ANIMATION

Responsible for all character animation.

Selected Series & Projects I've Animated

Image 1Image 2Image 3Image 4Image 5Image 6Image 7Image 8Image 9Image 10Image 11Image 1Image 2Image 3Image 4Image 5Image 6Image 7Image 8Image 9Image 10Image 11
(function initMarquee() { const container = document.getElementById('marqueeContainer'); const content = document.getElementById('marqueeContent'); if (!container || !content) { setTimeout(initMarquee, 50); return; } let currentX = 0; let baseSpeed = 1.5; let isDragging = false; let startX = 0; let halfWidth = content.scrollWidth / 2; window.addEventListener('resize', () => { halfWidth = content.scrollWidth / 2; }); function updateMarquee() { if (halfWidth > 0) { if (!isDragging) { currentX -= baseSpeed; } if (currentX <= -halfWidth) { currentX += halfWidth; if (isDragging) startX += halfWidth; } else if (currentX > 0) { currentX -= halfWidth; if (isDragging) startX -= halfWidth; } content.style.transform = `translate3d(${currentX}px, 0, 0)`; } requestAnimationFrame(updateMarquee); } function startDrag(e) { isDragging = true; container.setPointerCapture(e.pointerId); startX = e.clientX - currentX; } function moveDrag(e) { if (!isDragging) return; currentX = e.clientX - startX; } function stopDrag(e) { if (!isDragging) return; isDragging = false; try { container.releasePointerCapture(e.pointerId); } catch(err) {} } container.addEventListener('pointerdown', startDrag); container.addEventListener('pointermove', moveDrag); container.addEventListener('pointerup', stopDrag); container.addEventListener('pointercancel', stopDrag); requestAnimationFrame(updateMarquee); })();



ILLUSTRATIONS

A selection of personal work, commissions, and fan art.





Hey there, beautiful (◡‿◡ʃ💖ƪ)
BROS BEFORE FOES is an art series designed to make you laugh, leave you enamored, trigger your nerd-rage, and/or satiate your deepest, darkest Rule 27 memelord subreddit fan-fictions—or at least make you blush.
You're welcome, I think?
BBF was created in 2017 as an expression of resistance towards the escalation of polarizing emnity in the world, and because we could all benefit from a good bro-hug and a little love. Okay, A LOT of love. This art series endeavours to blur the boundaries of heteronormativity through iconoclastic images that will—at the very least—spark a conversation about gender expression, feminism, and emotional connections.Each bromance piece is inspired by music of 70's Rock, 80's Pop, and 90's R&B. Any guesses at the not-so-subtle references made in each one?


 
Creating BROS BEFORE FOES has been unbelievably fulfilling. Half the joy was making myself chortle during the creative process while drawing these bromances into existence. Yet, nothing compares to experiencing people's eyes light up, followed by immediate laughter and joy—or wincing & cringing—upon seeing newly finished pieces for the first time.
Getting to meet so many darling people who support my art by welcoming it into their homes has transformed vending at markets and conventions into a wonderful community of collectors I'm excited to see at future events.It is also a profound privilege to have shared many of these pieces with the legendary actors and creators who brought these characters to life. I am eternally grateful and humbled by these unforgettable experiences, and I thank you all for your incredible kindness and generosity.
I can't wait to create more! 💖
Which dramatic duo should I draw next?





ABOUT

André Guindi is a 2D animator, illustrator, character designer, concept artist, occasional voice-actor, and forever DM—contractually soul-bound to an abyssal patron in exchange for arcane secrets and the ability to manipulate conjured pixels at will.On the rare occasion André isn't drawing for clients he is most likely drawing fan art for his own enjoyment—or rarer still: catching up on his embarrassingly large backlog of 90s JRPGs.He would rather be drawing.

Andre Guindi 2D art cartoon character design



COMMISSIONS

Need artwork for your project? Want your precious blorbo or original character brought to life? Email me at [email protected] or fill out the form below to get started. Let's chat!

Form above not working - or looking janky because you're on mobile?
CLICK HERE!




Get in touch!




- test -

SETTINGS

Window colour

const selector = '#container02, #container03, #container04, #container05, #container06, #container07, #container08, #container09, #container10, #container11, #container12, #container13, #container14, #container15, #container16, #container19, #container20, #container21, #container22, #container26, #container29, #container30, #container31, #buttons01, #buttons05, #buttons06, #buttons08'; const color1 = document.getElementById('color1'); const color2 = document.getElementById('color2'); const angle = document.getElementById('angle'); const resetBtn = document.getElementById('reset-gradient'); const ColourReset = new Audio('https://github.com/cdills/ff-vii-steam-deck-audio/raw/refs/heads/master/ffvii-sound-effects-pack/confirmation_negative.wav'); ColourReset.preload = 'auto'; function hexToRgba(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } function getPaintTargets(target) { const buttonIds = ['buttons05', 'buttons06', 'buttons08']; const fullWidthIds = ['container02', 'container03']; if (target.id === 'buttons01') { return target.classList.contains('active') ? [target] : []; } if (buttonIds.includes(target.id)) { return Array.from(target.querySelectorAll('li a')); } else if (fullWidthIds.includes(target.id)) { return [target]; } else if (target.id === 'container30') { return Array.from(target.querySelectorAll('.wrapper > .inner > div')); } else { const inner = target.querySelector('.wrapper > .inner'); return inner ? [inner] : []; } } function syncGroupGradients(targets, groupIds, gradient) { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; const groupElements = Array.from(targets).filter(t => groupIds.includes(t.id)); groupElements.forEach(el => { const rect = el.getBoundingClientRect(); if (rect.width > 0) { minX = Math.min(minX, rect.left + window.scrollX); minY = Math.min(minY, rect.top + window.scrollY); maxX = Math.max(maxX, rect.left + window.scrollX + rect.width); maxY = Math.max(maxY, rect.top + window.scrollY + rect.height); } }); if (minX !== Infinity) { groupElements.forEach(target => { const elements = getPaintTargets(target); elements.forEach(el => { const rect = target.getBoundingClientRect(); el.style.backgroundImage = gradient; el.style.backgroundRepeat = 'no-repeat'; el.style.backgroundSize = `${maxX - minX}px ${maxY - minY}px`; el.style.backgroundPosition = `-${(rect.left + window.scrollX) - minX}px -${(rect.top + window.scrollY) - minY}px`; }); }); } } function updateGradient() { if (!localStorage.getItem('userGradient')) return; if (!color1 || !color2 || !angle) return; const targets = document.querySelectorAll(selector); const c1 = color1.value; const c2 = color2.value; const ang = angle.value; const standardGradient = `linear-gradient(${ang}deg, ${c1}, ${c2})`; const glassGradient = `linear-gradient(${ang}deg, ${hexToRgba(c1, 0.85)}, ${hexToRgba(c2, 0.85)})`; const groupIds1 = ['container10', 'container11', 'container12', 'container13']; const groupIds2 = ['container21', 'container22', 'container29']; function getGroupBounds(groupIds) { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; const groupElements = Array.from(targets).filter(t => groupIds.includes(t.id)); groupElements.forEach(el => { const rect = el.getBoundingClientRect(); if (rect.width > 0) { minX = Math.min(minX, rect.left + window.scrollX); minY = Math.min(minY, rect.top + window.scrollY); maxX = Math.max(maxX, rect.left + window.scrollX + rect.width); maxY = Math.max(maxY, rect.top + window.scrollY + rect.height); } }); return { minX, minY, maxX, maxY }; } const bounds1 = getGroupBounds(groupIds1); const bounds2 = getGroupBounds(groupIds2); targets.forEach(target => { if (target.id === 'buttons01' && !target.classList.contains('active')) { target.style.removeProperty('background-image'); target.style.setProperty('background', 'transparent', 'important'); const items = target.querySelectorAll('ul, li, a'); items.forEach(el => { el.style.removeProperty('background-image'); el.style.setProperty('background', 'transparent', 'important'); }); return; } const elements = getPaintTargets(target); elements.forEach(el => { const grad = (target.id === 'buttons01') ? glassGradient : standardGradient; el.style.setProperty('background-image', grad, 'important'); el.style.backgroundRepeat = 'no-repeat'; if (groupIds1.includes(target.id) || groupIds2.includes(target.id)) { const bounds = groupIds1.includes(target.id) ? bounds1 : bounds2; const rect = target.getBoundingClientRect(); el.style.backgroundSize = `${bounds.maxX - bounds.minX}px ${bounds.maxY - bounds.minY}px`; el.style.backgroundPosition = `-${(rect.left + window.scrollX) - bounds.minX}px -${(rect.top + window.scrollY) - bounds.minY}px`; } else { el.style.backgroundSize = 'cover'; el.style.backgroundPosition = 'center center'; el.style.backgroundRepeat = 'repeat'; } }); }); localStorage.setItem('userGradient', JSON.stringify({ c1, c2, a: ang })); } function handleInputUpdate() { localStorage.setItem('userGradient', JSON.stringify({ c1: color1.value, c2: color2.value, a: angle.value })); updateGradient(); } color1.addEventListener('input', handleInputUpdate); color2.addEventListener('input', handleInputUpdate); angle.addEventListener('input', handleInputUpdate); function resetToDefault() { ColourReset.currentTime = 0; ColourReset.play(); localStorage.removeItem('userGradient'); color1.value = "#ff0080"; color2.value = "#7928ca"; angle.value = "135"; document.querySelectorAll(selector).forEach(target => { const elements = getPaintTargets(target); elements.forEach(el => { el.style.background = ''; el.style.backgroundImage = 'none'; el.style.removeProperty('background-image'); }); target.style.removeProperty('background-image'); target.querySelectorAll('li a, .wrapper > .inner').forEach(el => { el.style.background = ''; el.style.backgroundImage = 'none'; }); }); } const saved = JSON.parse(localStorage.getItem('userGradient')); if (saved) { color1.value = saved.c1; color2.value = saved.c2; angle.value = saved.a; } if (color1 && color2 && angle && resetBtn) { color1.addEventListener('input', updateGradient); color2.addEventListener('input', updateGradient); angle.addEventListener('input', updateGradient); resetBtn.addEventListener('click', resetToDefault); } window.addEventListener('resize', updateGradient);

Sound

ONOFF
window.isGlobalMuted = false; const unmuteSoundUrl = 'https://github.com/cdills/ff-vii-steam-deck-audio/raw/refs/heads/master/ffvii-sound-effects-pack/deck_ui_default_activation.wav'; const unmuteSound = new Audio(unmuteSoundUrl); unmuteSound.preload = 'auto'; unmuteSound.volume = 0.5; const originalPlay = HTMLAudioElement.prototype.play; HTMLAudioElement.prototype.play = function() { if (window.isGlobalMuted) { this.muted = true; return Promise.resolve(); } return originalPlay.apply(this, arguments); }; function toggleMasterMute() { window.isGlobalMuted = !window.isGlobalMuted; document.querySelectorAll('audio').forEach(el => { el.muted = window.isGlobalMuted; }); const onText = document.getElementById('sound-on-text'); const offText = document.getElementById('sound-off-text'); if (window.isGlobalMuted) { onText.classList.remove('active'); offText.classList.add('active'); } else { onText.classList.add('active'); offText.classList.remove('active'); unmuteSound.currentTime = 0; unmuteSound.play().catch(e => console.log("Playback blocked:", e)); } }


Artist, animator, & purveyor of the arcane.
Conjuring pixels and bringing drawings to life atop his wizard tower in Toronto, Canada.