2D Animator | Character Artist | Illustrator



PORTFOLIO




ANIMATION

Select Shows & Projects I've Animated On

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; function updateMarquee() { const halfWidth = content.scrollWidth / 2; if (halfWidth > 0 && !isDragging) { currentX -= baseSpeed; if (currentX <= -halfWidth) { currentX += halfWidth; } else if (currentX > 0) { currentX -= 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; const halfWidth = content.scrollWidth / 2; if (halfWidth > 0) { if (currentX <= -halfWidth) { startX += halfWidth; currentX += halfWidth; } else if (currentX > 0) { startX -= halfWidth; currentX -= halfWidth; } } content.style.transform = `translate3d(${currentX}px, 0, 0)`; } 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 lyrics of 70's Rock, 80's Pop, and 90's R&B. Any guesses at the not-so-subtle references made in each one?





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.