Current directory: /home/klas4s23/domains/585455.klas4s23.mid-ica.nl/public_html/Gastenboek/uploads
/* ============================================
ARTEM KOSIKHIN - CREATIVE PORTFOLIO
JavaScript Animations & Interactions
============================================ */
/* ============================================
SECURITY PROTECTION
============================================ */
// Anti-scraping and security measures
(function() {
'use strict';
// Disable right-click context menu
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
return false;
});
// Disable common keyboard shortcuts for scraping
document.addEventListener('keydown', (e) => {
// Disable F12, Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U, Ctrl+S
if (
e.keyCode === 123 || // F12
(e.ctrlKey && e.shiftKey && e.keyCode === 73) || // Ctrl+Shift+I
(e.ctrlKey && e.shiftKey && e.keyCode === 74) || // Ctrl+Shift+J
(e.ctrlKey && e.keyCode === 85) || // Ctrl+U
(e.ctrlKey && e.keyCode === 83) // Ctrl+S
) {
e.preventDefault();
return false;
}
});
// Detect and prevent DevTools
const detectDevTools = () => {
const threshold = 160;
const widthThreshold = window.outerWidth - window.innerWidth > threshold;
const heightThreshold = window.outerHeight - window.innerHeight > threshold;
if (widthThreshold || heightThreshold) {
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;background:#0a0a0f;color:#fff;font-family:monospace;font-size:20px;">Access Denied</div>';
}
};
// Check periodically for DevTools
setInterval(detectDevTools, 1000);
// Disable text selection on sensitive elements
document.addEventListener('selectstart', (e) => {
const sensitiveElements = ['IMG', 'CODE', 'PRE'];
if (sensitiveElements.includes(e.target.tagName)) {
e.preventDefault();
return false;
}
});
// Prevent drag and drop of images
document.addEventListener('dragstart', (e) => {
if (e.target.tagName === 'IMG') {
e.preventDefault();
return false;
}
});
// Anti-debugging protection
const antiDebug = () => {
const startTime = performance.now();
debugger;
const endTime = performance.now();
if (endTime - startTime > 100) {
window.location.href = 'about:blank';
}
};
// Run anti-debug check periodically
setInterval(antiDebug, 2000);
// Console protection
const protectConsole = () => {
const emptyFunc = () => {};
const consoleMethods = ['log', 'debug', 'info', 'warn', 'error', 'trace'];
consoleMethods.forEach(method => {
window.console[method] = emptyFunc;
});
};
protectConsole();
// Prevent iFrame embedding
if (window.top !== window.self) {
window.top.location = window.self.location;
}
// Watermark content with invisible markers
const addWatermark = () => {
const watermark = document.createElement('div');
watermark.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;opacity:0.01;z-index:99999;';
watermark.textContent = `© ${new Date().getFullYear()} Artem Kosikhin - Unauthorized copying prohibited`;
document.body.appendChild(watermark);
};
// Add rate limiting for rapid requests
let requestCount = 0;
const resetInterval = 60000; // 1 minute
setInterval(() => {
requestCount = 0;
}, resetInterval);
// Monitor for suspicious activity
document.addEventListener('click', () => {
requestCount++;
if (requestCount > 200) {
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;background:#0a0a0f;color:#fff;font-family:monospace;font-size:20px;">Suspicious Activity Detected</div>';
}
});
// Apply watermark when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addWatermark);
} else {
addWatermark();
}
})();
// Wait for DOM to be fully loaded
document.addEventListener('DOMContentLoaded', () => {
// Initialize preloader first
initPreloader();
// Initialize all features
initCustomCursor();
initParticles();
initNavbar();
initScrollAnimations();
initTiltEffect();
initMagneticButtons();
initSmoothScroll();
initMobileMenu();
initThemeToggle();
initBackToTop();
initContactForm();
});
/* ============================================
CUSTOM CURSOR
============================================ */
function initCustomCursor() {
const cursor = document.querySelector('.cursor');
const follower = document.querySelector('.cursor-follower');
if (!cursor || !follower) return;
let mouseX = 0, mouseY = 0;
let cursorX = 0, cursorY = 0;
let followerX = 0, followerY = 0;
// Track mouse position
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
// Animate cursor with smooth following
function animateCursor() {
// Cursor follows immediately
cursorX += (mouseX - cursorX) * 0.2;
cursorY += (mouseY - cursorY) * 0.2;
cursor.style.left = cursorX + 'px';
cursor.style.top = cursorY + 'px';
// Follower follows with more delay (looser)
followerX += (mouseX - followerX) * 0.04;
followerY += (mouseY - followerY) * 0.04;
follower.style.left = followerX + 'px';
follower.style.top = followerY + 'px';
requestAnimationFrame(animateCursor);
}
animateCursor();
// Add hover effect on interactive elements
const interactiveElements = document.querySelectorAll('a, button, .magnetic, .project-card, .tool-item, .contact-card, .info-card');
interactiveElements.forEach(el => {
el.addEventListener('mouseenter', () => {
cursor.style.transform = 'scale(2)';
follower.classList.add('hovering');
});
el.addEventListener('mouseleave', () => {
cursor.style.transform = 'scale(1)';
follower.classList.remove('hovering');
});
});
}
/* ============================================
PARTICLES BACKGROUND
============================================ */
function initParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
const particleCount = 50;
const colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe'];
for (let i = 0; i < particleCount; i++) {
createParticle(particlesContainer, colors);
}
}
function createParticle(container, colors) {
const particle = document.createElement('div');
particle.classList.add('particle');
// Random properties
const size = Math.random() * 6 + 2;
const color = colors[Math.floor(Math.random() * colors.length)];
const left = Math.random() * 100;
const delay = Math.random() * 15;
const duration = Math.random() * 10 + 10;
particle.style.cssText = `
width: ${size}px;
height: ${size}px;
background: ${color};
left: ${left}%;
animation-delay: ${delay}s;
animation-duration: ${duration}s;
`;
container.appendChild(particle);
}
/* ============================================
NAVBAR SCROLL EFFECT
============================================ */
function initNavbar() {
const navbar = document.querySelector('.navbar');
if (!navbar) return;
let lastScroll = 0;
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
lastScroll = currentScroll;
});
}
/* ============================================
SCROLL-TRIGGERED ANIMATIONS
============================================ */
function initScrollAnimations() {
// Elements to animate
const animatedElements = document.querySelectorAll(
'.reveal-up, .about-description, .info-card, .project-card, .contact-card, .tool-item, .section-header'
);
// Intersection Observer options
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
// Create observer
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
// Add staggered delay for siblings
const delay = entry.target.dataset.delay || index * 0.1;
entry.target.style.transitionDelay = `${delay}s`;
entry.target.classList.add('visible');
// Animate counting numbers if present
const counter = entry.target.querySelector('[data-count]');
if (counter) {
animateCounter(counter);
}
}
});
}, observerOptions);
// Observe all elements
animatedElements.forEach(el => {
el.classList.add('reveal-up');
observer.observe(el);
});
// Parallax effect for floating shapes
initParallax();
// Section focus effect - fade out sections as you scroll past
initSectionFocus();
}
function initSectionFocus() {
const sections = document.querySelectorAll('section');
const hero = document.querySelector('.hero');
// Initialize sections (skip hero)
sections.forEach((section, index) => {
if (section === hero) {
// Hero always visible, no scroll animations
return;
}
section.classList.add('section-scroll-in');
});
// Simple intersection observer (skip hero)
const sectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const section = entry.target;
// Never apply scroll effects to hero
if (section === hero) return;
const rect = entry.boundingClientRect;
if (entry.isIntersecting && entry.intersectionRatio > 0.15) {
section.classList.remove('section-scroll-out', 'section-scroll-in');
section.classList.add('section-visible');
} else if (!entry.isIntersecting) {
if (rect.top < 0) {
section.classList.remove('section-visible', 'section-scroll-in');
section.classList.add('section-scroll-out');
} else {
section.classList.remove('section-visible', 'section-scroll-out');
section.classList.add('section-scroll-in');
}
}
});
}, {
threshold: [0, 0.15, 0.3],
rootMargin: '-5% 0px -5% 0px'
});
// Only observe non-hero sections
sections.forEach(section => {
if (section !== hero) {
sectionObserver.observe(section);
}
});
}
function initParallax() {
const shapes = document.querySelectorAll('.shape');
window.addEventListener('scroll', () => {
const scrollY = window.pageYOffset;
shapes.forEach((shape, index) => {
const speed = (index + 1) * 0.05;
shape.style.transform = `translateY(${scrollY * speed}px)`;
});
});
}
function animateCounter(element) {
const target = parseInt(element.dataset.count);
const duration = 2000;
const step = target / (duration / 16);
let current = 0;
const updateCounter = () => {
current += step;
if (current < target) {
element.textContent = Math.floor(current);
requestAnimationFrame(updateCounter);
} else {
element.textContent = target;
}
};
updateCounter();
}
/* ============================================
3D TILT EFFECT
============================================ */
function initTiltEffect() {
const tiltElements = document.querySelectorAll('[data-tilt]');
tiltElements.forEach(el => {
el.addEventListener('mouseenter', (e) => {
el.style.transition = 'none';
});
el.addEventListener('mousemove', handleTilt);
el.addEventListener('mouseleave', resetTilt);
});
}
function handleTilt(e) {
const el = e.currentTarget;
const rect = el.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 10;
const rotateY = (centerX - x) / 10;
el.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`;
}
function resetTilt(e) {
const el = e.currentTarget;
el.style.transition = 'transform 0.5s ease';
el.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)';
}
/* ============================================
MAGNETIC BUTTONS
============================================ */
function initMagneticButtons() {
const magneticElements = document.querySelectorAll('.magnetic');
magneticElements.forEach(el => {
el.addEventListener('mousemove', handleMagnetic);
el.addEventListener('mouseleave', resetMagnetic);
});
}
function handleMagnetic(e) {
const el = e.currentTarget;
const rect = el.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
el.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;
}
function resetMagnetic(e) {
e.currentTarget.style.transform = 'translate(0, 0)';
}
/* ============================================
SMOOTH SCROLL
============================================ */
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const target = document.querySelector(targetId);
if (target) {
// Close mobile menu if open
const navLinks = document.querySelector('.nav-links');
const menuToggle = document.querySelector('.menu-toggle');
if (navLinks && navLinks.classList.contains('active')) {
navLinks.classList.remove('active');
menuToggle.classList.remove('active');
}
// Calculate scroll position with navbar offset
const navbarHeight = 80;
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset;
const offsetPosition = targetPosition - navbarHeight;
// Smooth scroll to target with offset
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
}
/* ============================================
MOBILE MENU
============================================ */
function initMobileMenu() {
const menuToggle = document.querySelector('.menu-toggle');
const navLinks = document.querySelector('.nav-links');
if (!menuToggle || !navLinks) return;
menuToggle.addEventListener('click', () => {
menuToggle.classList.toggle('active');
navLinks.classList.toggle('active');
// Prevent body scroll when menu is open
document.body.style.overflow = navLinks.classList.contains('active') ? 'hidden' : '';
});
}
/* ============================================
TEXT ANIMATION UTILITIES
============================================ */
function splitTextIntoSpans(element) {
const text = element.textContent;
element.innerHTML = '';
text.split('').forEach((char, index) => {
const span = document.createElement('span');
span.textContent = char === ' ' ? '\u00A0' : char;
span.style.animationDelay = `${index * 0.05}s`;
element.appendChild(span);
});
}
/* ============================================
TYPING EFFECT
============================================ */
function typeWriter(element, text, speed = 100) {
let i = 0;
element.textContent = '';
function type() {
if (i < text.length) {
element.textContent += text.charAt(i);
i++;
setTimeout(type, speed);
}
}
type();
}
/* ============================================
SCROLL PROGRESS INDICATOR
============================================ */
function initScrollProgress() {
const progressBar = document.createElement('div');
progressBar.className = 'scroll-progress';
progressBar.style.cssText = `
position: fixed;
top: 0;
left: 0;
height: 3px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: 10000;
transition: width 0.1s ease;
`;
document.body.appendChild(progressBar);
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const progress = (scrollTop / scrollHeight) * 100;
progressBar.style.width = `${progress}%`;
});
}
// Initialize scroll progress
initScrollProgress();
// Initialize Showcase Gallery
initShowcaseGallery();
// Initialize Tech Stack
initTechStack();
/* ============================================
INTERACTIVE TECH STACK
============================================ */
function initTechStack() {
const skillCards = document.querySelectorAll('.skill-card');
const toolPills = document.querySelectorAll('.tool-pill');
const detailCard = document.getElementById('skillDetail');
const typingText = document.querySelector('.typing-text');
if (!skillCards.length) return;
// Skill card interactions
skillCards.forEach(card => {
const skillName = card.querySelector('.skill-name')?.textContent || '';
const level = card.dataset.level || '0';
const years = card.dataset.years || '1+';
// Animate level bars on scroll into view
const levelFill = card.querySelector('.level-fill');
if (levelFill) {
levelFill.style.width = '0';
}
// Mouse enter - show details
card.addEventListener('mouseenter', () => {
// Update terminal text
if (typingText) {
typingText.textContent = `${skillName} → ${level}% proficiency, ${years} years experience`;
}
// Show detail card
if (detailCard) {
const nameEl = detailCard.querySelector('.detail-skill-name');
const yearsEl = detailCard.querySelector('.detail-years');
const barFill = detailCard.querySelector('.detail-bar-fill');
const levelText = detailCard.querySelector('.detail-level');
if (nameEl) nameEl.textContent = skillName;
if (yearsEl) yearsEl.textContent = years + ' years';
if (barFill) barFill.style.width = level + '%';
if (levelText) levelText.textContent = `Proficiency: ${level}%`;
detailCard.classList.add('visible');
}
});
card.addEventListener('mouseleave', () => {
if (detailCard) {
detailCard.classList.remove('visible');
}
if (typingText) {
typingText.textContent = 'Hover over any skill to see details...';
}
});
// 3D tilt effect
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = (y - centerY) / 15;
const rotateY = (centerX - x) / 15;
card.style.transform = `translateY(-8px) scale(1.02) perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
});
});
// Animate skill bars on scroll
const skillsObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const cards = entry.target.querySelectorAll('.skill-card');
cards.forEach((card, index) => {
setTimeout(() => {
const levelFill = card.querySelector('.level-fill');
if (levelFill) {
levelFill.style.width = levelFill.style.getPropertyValue('--level');
}
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 100);
});
skillsObserver.unobserve(entry.target);
}
});
}, { threshold: 0.2 });
const skillCategories = document.querySelectorAll('.skill-category');
skillCategories.forEach(category => {
const cards = category.querySelectorAll('.skill-card');
cards.forEach(card => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
card.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
});
skillsObserver.observe(category);
});
// Tool pills interaction
toolPills.forEach(pill => {
pill.addEventListener('mouseenter', () => {
const toolName = pill.dataset.tool || pill.textContent.trim();
if (typingText) {
typingText.textContent = `Tool: ${toolName} → Click to learn more`;
}
});
pill.addEventListener('mouseleave', () => {
if (typingText) {
typingText.textContent = 'Hover over any skill to see details...';
}
});
});
}
function initShowcaseGallery() {
const cards = document.querySelectorAll('.showcase-card');
if (!cards.length) return;
// Staggered reveal animation on scroll
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const cardObserver = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
// Add staggered delay based on card position
const card = entry.target;
const cardIndex = Array.from(cards).indexOf(card);
setTimeout(() => {
card.classList.add('visible');
}, cardIndex * 100);
cardObserver.unobserve(card);
}
});
}, observerOptions);
// Prepare cards for animation
cards.forEach(card => {
card.style.opacity = '0';
card.style.transform = 'translateY(40px)';
cardObserver.observe(card);
});
// Add visible animation styles
const style = document.createElement('style');
style.textContent = `
.showcase-card.visible {
opacity: 1 !important;
transform: translateY(0) !important;
transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.23, 1, 0.32, 1);
}
`;
document.head.appendChild(style);
// Subtle parallax effect on card images
cards.forEach(card => {
const image = card.querySelector('.card-image img');
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
if (image) {
image.style.transform = `scale(1.08) translate(${x * 10}px, ${y * 10}px)`;
}
});
card.addEventListener('mouseleave', () => {
if (image) {
image.style.transform = '';
}
});
});
}
/* ============================================
EASTER EGG - KONAMI CODE
============================================ */
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
let konamiIndex = 0;
document.addEventListener('keydown', (e) => {
if (e.key === konamiCode[konamiIndex]) {
konamiIndex++;
if (konamiIndex === konamiCode.length) {
activateEasterEgg();
konamiIndex = 0;
}
} else {
konamiIndex = 0;
}
});
function activateEasterEgg() {
// Create Steam-style achievement popup
const achievement = document.createElement('div');
achievement.className = 'steam-achievement';
achievement.innerHTML = `
<div class="steam-achievement-icon">
<svg viewBox="0 0 24 24" fill="currentColor" class="trophy-icon">
<path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/>
</svg>
</div>
<div class="steam-achievement-content">
<div class="steam-achievement-header">
<span class="steam-icon">🎮</span> Achievement Unlocked
</div>
<div class="steam-achievement-title">Secret Developer</div>
<div class="steam-achievement-desc">You know the Konami code!</div>
</div>
`;
document.body.appendChild(achievement);
// Add styles if not already added
if (!document.getElementById('steam-achievement-styles')) {
const style = document.createElement('style');
style.id = 'steam-achievement-styles';
style.textContent = `
.steam-achievement {
position: fixed;
bottom: 30px;
right: -400px;
width: 340px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 1px solid #3a3a5c;
border-radius: 4px;
display: flex;
align-items: center;
gap: 15px;
padding: 12px 15px;
z-index: 100000;
box-shadow:
0 0 20px rgba(0, 0, 0, 0.5),
0 0 60px rgba(102, 126, 234, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
animation: steamSlideIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
font-family: 'Space Grotesk', Arial, sans-serif;
}
.steam-achievement::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.5), transparent);
}
@keyframes steamSlideIn {
0% {
right: -400px;
opacity: 0;
}
100% {
right: 30px;
opacity: 1;
}
}
@keyframes steamSlideOut {
0% {
right: 30px;
opacity: 1;
}
100% {
right: -400px;
opacity: 0;
}
}
.steam-achievement-icon {
width: 64px;
height: 64px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.trophy-icon {
width: 36px;
height: 36px;
color: #fff;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
position: relative;
z-index: 1;
}
.steam-achievement-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 255, 255, 0.1) 50%,
transparent 70%
);
animation: iconShine 2s ease-in-out infinite;
}
@keyframes iconShine {
0% { transform: translateX(-100%) rotate(45deg); }
100% { transform: translateX(100%) rotate(45deg); }
}
.steam-achievement-content {
flex: 1;
min-width: 0;
}
.steam-achievement-header {
font-size: 11px;
color: #8b8b8b;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 5px;
}
.steam-icon {
font-size: 12px;
}
.steam-achievement-title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
margin-bottom: 2px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.steam-achievement-desc {
font-size: 12px;
color: #6b6b6b;
}
.steam-achievement.hiding {
animation: steamSlideOut 0.4s ease-in forwards;
}
`;
document.head.appendChild(style);
}
// Play Steam-like sound
playAchievementSound();
// Remove after delay
setTimeout(() => {
achievement.classList.add('hiding');
setTimeout(() => achievement.remove(), 400);
}, 4000);
}
function playAchievementSound() {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Steam-like achievement sound (two-tone chime)
const playTone = (freq, startTime, duration) => {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = freq;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.15, audioContext.currentTime + startTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + startTime + duration);
oscillator.start(audioContext.currentTime + startTime);
oscillator.stop(audioContext.currentTime + startTime + duration);
};
// Two pleasant tones like Steam
playTone(880, 0, 0.15); // A5
playTone(1108.73, 0.1, 0.2); // C#6
} catch(e) {
// Audio not supported, fail silently
}
}
/* ============================================
PRELOADER - RETRO BOOT SCREEN
============================================ */
function initPreloader() {
const preloader = document.getElementById('preloader');
const progressFill = document.querySelector('.boot-progress-fill');
const percentageText = document.querySelector('.boot-percentage');
const bootTerminal = document.querySelector('.boot-terminal');
const cursor = document.querySelector('.cursor');
const follower = document.querySelector('.cursor-follower');
if (!preloader) return;
// Hide custom cursor during preloader
if (cursor) cursor.style.opacity = '0';
if (follower) follower.style.opacity = '0';
// Hide scrollbar during preloader
document.body.style.overflow = 'hidden';
// Easter egg: Type "lol" during preloader
let typedKeys = '';
let partyModeActivated = false;
const keyHandler = (e) => {
typedKeys += e.key.toLowerCase();
if (typedKeys.length > 10) typedKeys = typedKeys.slice(-10);
if (typedKeys.includes('lol') && !partyModeActivated) {
partyModeActivated = true;
sessionStorage.setItem('partyMode', 'true');
// Add party pack line
const partyLine = document.createElement('div');
partyLine.className = 'boot-line party-line';
partyLine.innerHTML = '[<span class="boot-ok">OK</span>] Loading partypack addons...';
partyLine.style.animation = 'bootLineIn 0.3s ease forwards';
bootTerminal.appendChild(partyLine);
}
};
document.addEventListener('keydown', keyHandler);
let progress = 0;
let baseDuration = 5000;
const interval = 50;
const progressInterval = setInterval(() => {
const duration = partyModeActivated ? 6000 : baseDuration;
const increment = 100 / (duration / interval);
progress += increment + (Math.random() * 0.8 - 0.4);
progress = Math.min(progress, partyModeActivated ? 100 : 99);
if (progressFill) {
progressFill.style.width = `${progress}%`;
}
if (percentageText) {
percentageText.textContent = `${Math.floor(progress)}%`;
}
}, interval);
window.addEventListener('load', () => {
const finishPreloader = () => {
clearInterval(progressInterval);
document.removeEventListener('keydown', keyHandler);
if (progressFill) progressFill.style.width = '100%';
if (percentageText) percentageText.textContent = '100%';
setTimeout(() => {
preloader.classList.add('loaded');
if (cursor) cursor.style.opacity = '1';
if (follower) follower.style.opacity = '0.5';
document.body.style.overflow = '';
// Apply party mode if activated
if (partyModeActivated || sessionStorage.getItem('partyMode') === 'true') {
document.body.classList.add('party-mode');
sessionStorage.removeItem('partyMode');
}
}, 400);
};
const waitTime = partyModeActivated ? 6000 : 5000;
setTimeout(finishPreloader, waitTime);
});
}
/* ============================================
THEME TOGGLE
============================================ */
function initThemeToggle() {
const themeToggle = document.getElementById('themeToggle');
if (!themeToggle) return;
// Check for saved theme preference
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
/* ============================================
BACK TO TOP BUTTON
============================================ */
function initBackToTop() {
const backToTop = document.getElementById('backToTop');
if (!backToTop) return;
window.addEventListener('scroll', () => {
if (window.scrollY > 500) {
backToTop.classList.add('visible');
} else {
backToTop.classList.remove('visible');
}
});
backToTop.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
/* ============================================
CONTACT FORM
============================================ */
function initContactForm() {
const form = document.getElementById('contactForm');
const formStatus = document.getElementById('formStatus');
if (!form) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const submitBtn = form.querySelector('.submit-btn');
const originalText = submitBtn.querySelector('.btn-text').textContent;
// Show loading state
submitBtn.querySelector('.btn-text').textContent = 'Sending...';
submitBtn.disabled = true;
try {
const formData = new FormData(form);
const response = await fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json'
}
});
if (response.ok) {
formStatus.textContent = '✓ Message sent successfully! I\'ll get back to you soon.';
formStatus.className = 'form-status success';
form.reset();
} else {
throw new Error('Form submission failed');
}
} catch (error) {
formStatus.textContent = '✕ Oops! Something went wrong. Please try again or email me directly.';
formStatus.className = 'form-status error';
}
// Reset button
submitBtn.querySelector('.btn-text').textContent = originalText;
submitBtn.disabled = false;
// Hide status after 5 seconds
setTimeout(() => {
formStatus.className = 'form-status';
}, 5000);
});
}
/* ============================================
CONSOLE MESSAGE
============================================ */
console.log(`
%c Welcome to Artem's Portfolio!
%c
Built with passion and lots of coffee ☕
Check out the source code and let's connect!
%cPsst... try the Konami code! (↑ ↑ ↓ ↓ ← → ← → B A)
`,
'color: #667eea; font-size: 24px; font-weight: bold;',
'color: #f093fb; font-size: 14px;',
'color: #4ade80; font-size: 12px; font-style: italic;'
);