developer-portfolios/js/script.js

680 lines
26 KiB
JavaScript
Raw Normal View History

2025-03-31 20:26:11 +00:00
document.addEventListener('DOMContentLoaded', function() {
// Project data
const projects = [
{
"title": "Crypto Tracker App",
"image": "cryptotracker.png",
"projectImg": "cryptotracker.png",
"description": "An app that allows yu to buy & sell crypto",
"link": "https://qms85.github.io/CryptoTrackerPro/"
},
{
"title": "Digital Divide Records",
"image": "javascript",
"projectImg": "digitaldividerecords.png",
"description": "A South African House Music Record Label",
"link": "https://digitaldividerecords-pty-ltd.github.io/DigitalDivideRecords/"
},
{
"title": "Drag Me",
"image": "nodejs",
"projectImg": "dragme.png",
"description": "A div element, that you can drag around the screen",
"link": "https://qms85.github.io/DragMeAroundTheScreen/"
},
{
"title": "Entrepreneurship",
"image": "react",
"projectImg": "entrepreneurship.png",
"description": "A guide to Entrepreneurship, using Replit .",
"link": "https://entrepreneurship-withreplit.streamlit.app/"
},
{
"title": "GitHub Profile Search",
"image": "javascript",
"projectImg": "githubprofilesearch.png",
"description": "A Web app that searches for Github profiles,enter username & enter",
"link": "https://qms85.github.io/Github-User-Search/"
},
{
"title": "GKroon Investment Calculator",
"image": "nodejs",
"projectImg": "gkrooninvestmentcalculator.png",
"description": "An investment calculator, in $USD",
"link": "https://gkroon-web.github.io/Investment-Calculator/"
},
{
"title": "GKroon Password Generator",
"image": "react",
"projectImg": "gkroonpasswordgenerator.png",
"description": "This app generates passwords up to 24 characters",
"link": "https://qms85.github.io/Password-Generator/"
},
{
"title": "GKroon",
"image": "javascript",
"projectImg": "gkroonwebsite.png",
"description": "A business website for GKroon",
"link": "https://g-kroon.github.io/GKroon/"
},
{
"title": "Music Player",
"image": "nodejs",
"projectImg": "musicplayer.png",
"description": "A mp3 music player. Press Play & enjoy the groove",
"link": "https://qms85.github.io/MusicPlayer/"
},
{
"title": "Meme Generator",
"image": "react",
"projectImg": "memegenerator.png",
"description": "Upload your images, add text & download your new meme",
"link": "https://qms85.github.io/MemeGenerator/"
},
{
"title": "Replit Capabilities Guide",
"image": "javascript",
"projectImg": "replitcapabilitiesguide.png",
"description": "A comprehensive guide to Repit.",
"link": "https://qms85.github.io/ReplitCapabilitiesGuide/"
},
{
"title": "Rectangular Run",
"image": "nodejs",
"projectImg": "rectangularrun.png",
"description": "A simple javascript game, inspired by the classic Dino Run game. Only this version uses rectangles",
"link": "https://gkroon-web.github.io/RectangleRun/"
},
{
"title": "Sample Artist EPK",
"image": "react",
"projectImg": "sampleartistepk.png",
"description": "A sample artist electronic press kit (EPK)",
"link": "https://digitaldividerecords-pty-ltd.github.io/SampleArtistEPK/"
},
{
"title": "Calculator",
"image": "react",
"projectImg": "calculator.png",
"description": "A simple calculator",
"link": "https://qms85.github.io/GitHubCalculator/"
}
];
// Skills data with percentages
const skills = [
{"name": "HTML", "percentage": 95, "icon": "devicon-html5-plain colored"},
{"name": "CSS", "percentage": 90, "icon": "devicon-css3-plain colored"},
{"name": "JavaScript", "percentage": 85, "icon": "devicon-javascript-plain colored"},
{"name": "React", "percentage": 80, "icon": "devicon-react-original colored"},
{"name": "Node.js", "percentage": 45, "icon": "devicon-nodejs-plain colored"},
{"name": "Express.js", "percentage": 45, "icon": "devicon-express-original colored"},
{"name": "D3.js", "percentage": 70, "icon": "devicon-d3js-plain colored"},
{"name": "Bootstrap", "percentage": 70, "icon": "devicon-bootstrap-plain colored"},
{"name": "Tailwind CSS", "percentage": 70, "icon": "devicon-tailwindcss-plain colored"},
{"name": "Python", "percentage": 45, "icon": "devicon-python-plain colored"}
];
// Tools data
const tools = [
{"name": "HTML", "icon": "devicon-html5-plain colored"},
{"name": "CSS", "icon": "devicon-css3-plain colored"},
{"name": "JavaScript", "icon": "devicon-javascript-plain colored"},
{"name": "React", "icon": "devicon-react-original colored"},
{"name": "Node.js", "icon": "devicon-nodejs-plain colored"},
{"name": "Express.js", "icon": "devicon-express-original colored"},
{"name": "D3.js", "icon": "devicon-d3js-plain colored"},
{"name": "Bootstrap", "icon": "devicon-bootstrap-plain colored"},
{"name": "Tailwind CSS", "icon": "devicon-tailwindcss-plain colored"},
{"name": "Python", "icon": "devicon-python-plain colored"},
{"name": "GitHub", "icon": "devicon-github-original colored"},
{"name": "Replit", "icon": "devicon-replit-original colored"},
{"name": "VS Code", "icon": "devicon-vscode-plain colored"}
];
// Social media links
const socialLinks = [
{"platform": "WhatsApp", "icon": "fab fa-whatsapp", "link": "https://wa.me/+2766119925"},
{"platform": "Facebook", "icon": "fab fa-facebook-f", "link": "https://facebook.com/2jonathanpeters"},
{"platform": "Twitter", "icon": "fab fa-twitter", "link": "https://twitter.com/DJJonnas85"},
{"platform": "LinkedIn", "icon": "fab fa-linkedin-in", "link": "https://linkedin.com/in/2jonathanpeters"},
{"platform": "Instagram", "icon": "fab fa-instagram", "link": "https://instagram.com/jonathanpeters"},
{"platform": "GitHub", "icon": "fab fa-github", "link": "https://github.com/QMS85"},
{"platform": "Replit", "icon": "devicon-replit-plain", "link": "https://replit.com/DJJonnas85"}
];
// Rendering functions
function renderProjects() {
const projectsContainer = document.getElementById('projects-container');
if (!projectsContainer) return;
let projectsHTML = '';
projects.forEach(project => {
let iconClass = '';
// Map image names to Devicon classes
switch(project.image) {
case 'react':
iconClass = 'devicon-react-original colored';
break;
case 'javascript':
iconClass = 'devicon-javascript-plain colored';
break;
case 'nodejs':
iconClass = 'devicon-nodejs-plain colored';
break;
default:
iconClass = 'devicon-devicon-plain colored';
}
const projectImage = project.projectImg ?
`<img src="${project.projectImg}" alt="${project.title}" class="project-image">` :
`<i class="${iconClass}"></i>`;
projectsHTML += `
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
${projectImage}
<h3>${project.title}</h3>
</div>
<div class="flip-card-back">
<h3>${project.title}</h3>
<p class="typing-text-container">
<span class="typing-text">${project.description}</span>
</p>
<a href="${project.link}" class="btn view-project-btn" target="_blank">View Project</a>
</div>
</div>
</div>
`;
});
projectsContainer.innerHTML = projectsHTML;
}
function renderSkills() {
const skillsContainer = document.getElementById('skills-container');
if (!skillsContainer) return;
let skillsHTML = '';
skills.forEach(skill => {
skillsHTML += `
<div class="skill-item">
<div class="skill-info">
<div class="skill-name">
<i class="${skill.icon}"></i>
<span>${skill.name}</span>
</div>
<div class="skill-percentage">${skill.percentage}%</div>
</div>
<div class="skill-bar">
<div class="skill-progress" style="width: ${skill.percentage}%"></div>
</div>
</div>
`;
});
skillsContainer.innerHTML = skillsHTML;
}
function renderTools() {
const toolsContainer = document.getElementById('tools-container');
if (!toolsContainer) return;
let toolsHTML = '';
tools.forEach(tool => {
toolsHTML += `
<div class="tool-item">
<i class="${tool.icon}"></i>
<span>${tool.name}</span>
</div>
`;
});
toolsContainer.innerHTML = toolsHTML;
}
function renderSocialLinks() {
const socialLinksContainer = document.getElementById('social-links-container');
if (!socialLinksContainer) return;
let socialLinksHTML = '';
socialLinks.forEach(social => {
socialLinksHTML += `
<a href="${social.link}" target="_blank" title="${social.platform}">
<i class="${social.icon}"></i>
</a>
`;
});
socialLinksContainer.innerHTML = socialLinksHTML;
}
// Initialize animations for elements when they come into view
const animateOnScroll = () => {
const elements = document.querySelectorAll('.section-header, .projects-grid, .skills-content, .tools-grid, .about-content, .contact-content');
elements.forEach(element => {
const elementPosition = element.getBoundingClientRect().top;
const windowHeight = window.innerHeight;
if (elementPosition < windowHeight - 100) {
element.style.opacity = '1';
element.style.transform = 'translateY(0)';
}
});
};
// Set initial styles for animation
const initAnimations = () => {
const elements = document.querySelectorAll('.section-header, .projects-grid, .skills-content, .tools-grid, .about-content, .contact-content');
elements.forEach(element => {
element.style.opacity = '0';
element.style.transform = 'translateY(20px)';
element.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
});
};
// Animate progress bars when skills section is in view
const animateSkillBars = () => {
const skillsSection = document.getElementById('skills');
if (!skillsSection) return;
const skillBars = document.querySelectorAll('.skill-progress');
const sectionPosition = skillsSection.getBoundingClientRect().top;
const windowHeight = window.innerHeight;
if (sectionPosition < windowHeight - 100) {
skillBars.forEach(bar => {
const width = bar.style.width;
bar.style.width = '0';
setTimeout(() => {
bar.style.width = width;
}, 100);
});
}
};
// Smooth scrolling for navigation links
const smoothScroll = () => {
const links = document.querySelectorAll('a[href^="#"]');
links.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const targetElement = document.querySelector(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 80,
behavior: 'smooth'
});
}
});
});
};
// EmailJS initialization and form submission handling
const handleFormSubmission = () => {
const contactForm = document.getElementById('contact-form');
const emailStatus = document.getElementById('email-status');
if (!contactForm) return;
// Initialize EmailJS with your user ID
// Replace 'YOUR_EMAILJS_USER_ID' with your actual EmailJS user ID/public key
emailjs.init('k8FMxcW3OlbaRfu2u');
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
// Disable submit button and show loading state
const submitBtn = document.getElementById('submit-btn');
const originalBtnText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
// Send email using EmailJS
// Replace 'YOUR_EMAILJS_SERVICE_ID' and 'YOUR_EMAILJS_TEMPLATE_ID' with your actual values
emailjs.sendForm('service_y8eilob', 'template_0fbrelq', this)
.then(function(response) {
// Show success message
emailStatus.textContent = 'Thank you! Your message has been sent successfully.';
emailStatus.className = 'email-status success';
// Reset form
contactForm.reset();
// Hide success message after 5 seconds
setTimeout(() => {
emailStatus.style.display = 'none';
}, 5000);
console.log('SUCCESS!', response.status, response.text);
}, function(error) {
// Show error message
emailStatus.textContent = 'Oops! Something went wrong. Please try again later.';
emailStatus.className = 'email-status error';
console.log('FAILED...', error);
})
.finally(() => {
// Re-enable submit button
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText;
});
});
};
// Add active state to navigation links based on scroll position with micro-interactions
const setActiveNavLink = () => {
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('nav ul li a');
const header = document.querySelector('header');
// Store previous section for transition effects
let previousSection = '';
let scrollingTimeout;
window.addEventListener('scroll', () => {
let current = '';
const scrollTop = window.pageYOffset;
// Clear the timeout on scroll
clearTimeout(scrollingTimeout);
// Add scrolling class to header for potential scroll-based effects
header.classList.add('scrolling');
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (scrollTop >= (sectionTop - 200)) {
current = section.getAttribute('id');
}
});
// Only update if we have a change in section
if (current !== previousSection) {
// Remove active class from all links first
navLinks.forEach(link => {
link.classList.remove('active');
// Remove any transition classes
link.classList.remove('fadeIn', 'bounce');
});
// Find the active link
const activeLink = Array.from(navLinks).find(link =>
link.getAttribute('href') === `#${current}`
);
// If we have an active link, add classes with animation
if (activeLink) {
activeLink.classList.add('active');
// Add a quick animation effect
activeLink.classList.add('bounce');
setTimeout(() => {
activeLink.classList.remove('bounce');
}, 500);
}
// Update previous section
previousSection = current;
}
// Remove scrolling class after scrolling stops
scrollingTimeout = setTimeout(() => {
header.classList.remove('scrolling');
}, 150);
});
// Add click event for smooth scrolling and animation
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Remove active class and animations from all links
navLinks.forEach(l => {
l.classList.remove('active', 'fadeIn', 'bounce');
});
// Add active class and animation to clicked link
this.classList.add('active', 'fadeIn');
// Find the target section
const targetId = this.getAttribute('href').substring(1);
const targetSection = document.getElementById(targetId);
if (targetSection) {
e.preventDefault();
// Smooth scroll to section
window.scrollTo({
top: targetSection.offsetTop - 100,
behavior: 'smooth'
});
// Update URL without page reload
history.pushState(null, null, `#${targetId}`);
// Update previousSection
previousSection = targetId;
}
});
});
};
// Enhance flip cards for touch devices
const enhanceFlipCards = () => {
const flipCards = document.querySelectorAll('.flip-card');
flipCards.forEach(card => {
// For touch devices
card.addEventListener('touchstart', function() {
this.querySelector('.flip-card-inner').style.transform = 'rotateY(180deg)';
});
// Return to front after delay
card.addEventListener('mouseleave', function() {
setTimeout(() => {
this.querySelector('.flip-card-inner').style.transform = 'rotateY(0deg)';
}, 1000);
});
});
};
// Add CSS class to style active navigation link
const styleActiveNavLinks = () => {
const style = document.createElement('style');
style.textContent = `
nav ul li a.active {
color: var(--primary-light);
}
nav ul li a.active::after {
width: 100%;
}
`;
document.head.appendChild(style);
};
// Handle CV download notification
const handleCVDownload = () => {
const downloadBtn = document.querySelector('.download-cv-btn');
const notification = document.getElementById('cv-notification');
if (!downloadBtn || !notification) return;
downloadBtn.addEventListener('click', () => {
// Show the notification
notification.classList.add('show');
// Hide the notification after 3 seconds
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
});
};
// Initialize particles animation
const initParticles = () => {
// Check if particles.js is loaded
if (typeof particlesJS !== 'undefined') {
// Particle configuration for both header and footer
const particleConfig = {
"particles": {
"number": {
"value": 50,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": ["#FF4500", "#32CD32", "#FF0000"] // Orange, Lime-Green, Red
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
}
},
"opacity": {
"value": 0.5,
"random": true,
"anim": {
"enable": true,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 3,
"random": true,
"anim": {
"enable": true,
"speed": 2,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#32CD32",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 2,
"direction": "none",
"random": true,
"straight": false,
"out_mode": "bounce",
"bounce": false,
"attract": {
"enable": true,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "repulse"
},
"onclick": {
"enable": false,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 100,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
};
// Initialize particles for header
particlesJS('particles-js', particleConfig);
// Initialize particles for footer
particlesJS('footer-particles-js', particleConfig);
} else {
console.error('particles.js not loaded');
}
};
// Initialize all functionality
const init = () => {
// Render content from data
renderProjects();
renderSkills();
renderTools();
renderSocialLinks();
// Initialize animations and interactions
initAnimations();
animateOnScroll(); // Initial check on page load
smoothScroll();
handleFormSubmission();
setActiveNavLink();
styleActiveNavLinks();
enhanceFlipCards();
handleCVDownload();
initParticles(); // Initialize particles
// Listen for scroll events
window.addEventListener('scroll', () => {
animateOnScroll();
animateSkillBars();
});
// Handle window resize events
window.addEventListener('resize', () => {
animateOnScroll();
});
};
// Initialize everything
init();
});