Foto de perfil de Jonas Freitas

,

Foto de perfil de Jonas Freitas

See More

${text} `; } aiMessages.appendChild(messageDiv); aiMessages.scrollTop = aiMessages.scrollHeight; if (component) { setTimeout(() => { const componentContainer = messageDiv.querySelector(`[id^="ai-component-"]`); if (componentContainer) { renderComponent(component, componentContainer); } }, 300); } }; const addTypingIndicator = () => { const typingDiv = document.createElement('div'); typingDiv.id = 'typing-indicator'; typingDiv.className = 'flex gap-3 items-start'; typingDiv.innerHTML = ` Jonas Freitas

`; aiMessages.appendChild(typingDiv); aiMessages.scrollTop = aiMessages.scrollHeight; }; const removeTypingIndicator = () => { const typingIndicator = document.getElementById('typing-indicator'); if (typingIndicator) { typingIndicator.remove(); } }; const renderComponent = (componentType, container) => { const t = data.translations[currentLang]; switch(componentType) { case 'tech-stack': if (t.techStack && t.techStack.items) { container.innerHTML = `
${t.techStack.items.slice(0, 8).map(item => `
${item.name} ${item.name} `).join('')} `; } break; case 'experience': const recentJobs = t.experience.slice(0, 3); container.innerHTML = `
${recentJobs.map(job => `

${job.company} ${job.period}
${job.title}

${job.description} `).join('')} `; break; case 'services': container.innerHTML = `

${t.services.map(service => `

${service.name}

${service.description} `).join('')} `; break; case 'about': container.innerHTML = `

${data.personalInfo.name}

${data.personalInfo.name}

${t.title}

${t.summary} `; break; case 'contact': container.innerHTML = `

${data.personalInfo.contact.email} LinkedIn WhatsApp `; break; } }; const handleUserMessage = async (message) => { if (!message.trim()) return; if (!chat) { addMessage(currentLang === 'pt' ? '⚠️ Por favor, configure sua API key do Google Gemini nas configurações primeiro.' : '⚠️ Please configure your Google Gemini API key in settings first.', false); return; } addMessage(message, true); aiInput.value = ''; aiInput.disabled = true; aiSendBtn.disabled = true; addTypingIndicator(); try { const result = await chat.sendMessage(message); const response = await result.response; let responseText = response.text(); removeTypingIndicator(); // Check for component markers const componentMatch = responseText.match(/\{\{SHOW:(.*?)\}\}/); let componentToShow = null; if (componentMatch) { componentToShow = componentMatch[1]; responseText = responseText.replace(/\{\{SHOW:.*?\}\}/g, '').trim(); } // Convert markdown-style formatting to HTML responseText = responseText .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/\n/g, '
'); addMessage(responseText, false, componentToShow); } catch (error) { console.error('Error sending message:', error); removeTypingIndicator(); addMessage(currentLang === 'pt' ? '❌ Desculpe, ocorreu um erro ao processar sua mensagem. Verifique sua API key.' : '❌ Sorry, an error occurred while processing your message. Please check your API key.', false); } aiInput.disabled = false; aiSendBtn.disabled = false; aiInput.focus(); }; // AI Mode event listeners aiModeToggle.addEventListener('click', () => { aiChatContainer.classList.remove('hidden'); aiChatContainer.classList.add('flex'); document.body.style.overflow = 'hidden'; // Render suggestions renderSuggestions(); if (!loadApiKey()) { setTimeout(() => { aiSettingsModal.classList.remove('hidden'); }, 500); } }); aiCloseBtn.addEventListener('click', () => { aiChatContainer.classList.add('hidden'); aiChatContainer.classList.remove('flex'); document.body.style.overflow = 'auto'; }); aiSendBtn.addEventListener('click', () => { handleUserMessage(aiInput.value); }); aiInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleUserMessage(aiInput.value); } }); // Initialize on load loadApiKey(); if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') { gsap.registerPlugin(ScrollTrigger); } const applyAnimations = () => { if (typeof gsap === 'undefined') { return; } const selectorsToKill = ['.hero-panel', '.contact-link', '.service-card', '.tech-item', '.timeline-card', '#about .glass-panel']; selectorsToKill.forEach(selector => gsap.killTweensOf(selector)); if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.getAll().forEach(trigger => trigger.kill()); } if (document.querySelector('.hero-panel')) { gsap.from('.hero-panel', { opacity: 0, y: 40, duration: 1.2, ease: 'power4.out' }); } if (document.querySelectorAll('.contact-link').length) { gsap.from('.contact-link', { opacity: 0, y: 16, duration: 0.8, ease: 'power3.out', stagger: 0.08, delay: 0.3 }); } const animateCollection = (targets, configWithScroll, fallbackConfig) => { const elements = document.querySelectorAll(targets); if (!elements.length) return; if (typeof ScrollTrigger !== 'undefined') { const { trigger: triggerSelector, start, ...animationConfig } = configWithScroll; gsap.from(targets, { ...animationConfig, scrollTrigger: { trigger: triggerSelector || targets, start: start || 'top 80%', once: true } }); } else { gsap.from(targets, fallbackConfig); } }; animateCollection('.service-card', { opacity: 0, y: 40, duration: 1, ease: 'power3.out', stagger: 0.12, trigger: '#services', start: 'top 82%' }, { opacity: 0, y: 40, duration: 1, ease: 'power3.out', stagger: 0.12, delay: 0.4 }); animateCollection('.tech-item', { opacity: 0, y: 32, duration: 0.9, ease: 'power3.out', stagger: 0.1, trigger: '#tech-stack', start: 'top 85%' }, { opacity: 0, y: 32, duration: 0.9, ease: 'power3.out', stagger: 0.1, delay: 0.5 }); if (document.querySelectorAll('.timeline-card').length) { if (typeof ScrollTrigger !== 'undefined') { gsap.utils.toArray('.timeline-card').forEach(card => { gsap.from(card, { opacity: 0, y: 48, duration: 1, ease: 'power3.out', scrollTrigger: { trigger: card, start: 'top 85%', once: true } }); }); } else { gsap.from('.timeline-card', { opacity: 0, y: 48, duration: 1, ease: 'power3.out', stagger: 0.12, delay: 0.4 }); } } animateCollection('#about .glass-panel', { opacity: 0, y: 40, duration: 1, ease: 'power3.out', trigger: '#about', start: 'top 80%' }, { opacity: 0, y: 40, duration: 1, ease: 'power3.out', delay: 0.4 }); if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } }; const themeToggleBtn = document.getElementById('theme-toggle'); const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon'); const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon'); // Set initial icon based on theme if (localStorage.theme === 'dark') { themeToggleLightIcon.classList.remove('hidden'); } else { themeToggleDarkIcon.classList.remove('hidden'); } themeToggleBtn.addEventListener('click', function() { // Toggle icons themeToggleDarkIcon.classList.toggle('hidden'); themeToggleLightIcon.classList.toggle('hidden'); // Toggle theme and save to local storage if (document.documentElement.classList.contains('dark')) { document.documentElement.classList.remove('dark'); localStorage.setItem('theme', 'light'); } else { document.documentElement.classList.add('dark'); localStorage.setItem('theme', 'dark'); } }); const ICONS = { email: ``, linkedin: ``, whatsapp: `` }; let timelineObserver = null; const renderContent = () => { const t = data.translations[currentLang]; document.documentElement.lang = currentLang === 'pt' ? 'pt-BR' : 'en'; document.querySelectorAll('[data-translate]').forEach(el => { const key = el.dataset.translate; if (t.ui[key]) el.textContent = t.ui[key]; }); document.querySelector('[data-i18n="personalInfo.name"]').textContent = data.personalInfo.name; // Hero document.getElementById('hero-image').src = data.personalInfo.image; document.getElementById('about-image').src = data.personalInfo.image; document.getElementById('hero-title').textContent = t.title; document.getElementById('hero-subtitle').textContent = t.heroSubtitle; document.getElementById('hero-summary').textContent = t.summary; document.getElementById('hero-cta-services').textContent = t.ui.heroCTAServices; document.getElementById('hero-cta-contact').textContent = t.ui.heroCTAContact; // Contact links in Hero const contactLinksContainer = document.getElementById('contact-links'); contactLinksContainer.innerHTML = ` ${ICONS.email} ${ICONS.linkedin} ${ICONS.whatsapp} `; // Services const servicesContainer = document.getElementById('services-container'); servicesContainer.innerHTML = ''; t.services.forEach(service => { const item = document.createElement('div'); item.className = 'glass-card service-card p-8 flex flex-col gap-4 text-left'; item.innerHTML = `

${service.name}

${service.description} `; servicesContainer.appendChild(item); }); // Tech Stack const techStackContainer = document.getElementById('tech-stack-container'); techStackContainer.innerHTML = ''; if (t.techStack && t.techStack.items) { const gridLayoutClasses = [ 'md:col-span-2 md:row-span-1', // Item 1 'md:col-span-2 md:row-span-1', // Item 2 'md:col-span-1 md:row-span-2', // Item 3 (tall) 'md:col-span-2 md:row-span-1', // Item 4 'md:col-span-1 md:row-span-2', // Item 5 (tall) 'md:col-span-2 md:row-span-1', // Item 6 ]; t.techStack.items.forEach((item, index) => { const itemEl = document.createElement('div'); const layoutClass = gridLayoutClasses[index % 6]; const baseClasses = 'col-span-1 glass-tile tech-item p-6 flex flex-col items-center justify-center gap-4 text-center'; itemEl.className = `${baseClasses} ${layoutClass}`; let invertClass = item.invertInDark ? 'dark:invert' : ''; itemEl.innerHTML = ` ${item.name} logo ${item.name} `; techStackContainer.appendChild(itemEl); }); } // Experience const experienceContainer = document.getElementById('experience-container'); const experienceByYear = {}; t.experience.forEach(job => { const yearKey = job.period.split(' ')[0]; if (!experienceByYear[yearKey]) { experienceByYear[yearKey] = []; } experienceByYear[yearKey].push(job); }); const getYearValue = (year) => { const numeric = parseInt(year.replace(/\D/g, ''), 10); return Number.isNaN(numeric) ? 0 : numeric; }; const sortedYears = Object.keys(experienceByYear).sort((a, b) => getYearValue(b) - getYearValue(a)); const navHtml = `

${sortedYears.map(year => `${year}`).join('')} `; const timelineHtml = sortedYears.map((year, yearIndex) => { const jobs = experienceByYear[year]; const entriesHtml = jobs.map((job, jobIndex) => { const period = job.period.trim().endsWith('—') ? `${job.period.trim()} ${t.ui.present}` : job.period; const alignLeft = (yearIndex + jobIndex) % 2 === 0; const cardHtml = `
${period}

${job.company}

${job.title}

${job.description} `; const connectorHtml = `