// Developer Jutra front-end bootstrap (function (window, document) { 'use strict'; const SELECTORS = { customCursor: '.custom-cursor', customCursorDot: '.custom-cursor-dot', cursorTrails: '#cursor-trails-container', scrollSection: '.scroll-section', floatingCode: '.floating-code', authorImage: '.author-image', techIcon: '.tech-icon', perspectiveCard: '.perspective-card', instructorCard: '.instructor-card', particles: '#particles-js', hero: '.hero', heroContent: '.hero-content', heroBackground: '.hero-background-image', popupOverlay: '#popupOverlay', popupClose: '#popupClose' }; const HOVER_TARGETS = 'a, button, .button, .cta-button, .floating-code, .author-image, .instructor-card, .tech-icon'; // Performance and accessibility checks const isMobile = () => window.innerWidth <= 768; const isReducedMotion = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches; const isOlderDevice = () => { // Check for older devices based on performance hints if (navigator.deviceMemory && navigator.deviceMemory < 4) return true; if (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4) return true; return false; }; function safeRun(name, init) { try { init(); } catch (error) { console.warn(`[DeveloperJutra] ${name} initialization failed`, error); } } function initCustomCursor() { // Disable custom cursor on mobile or when user prefers reduced motion if (isMobile() || isReducedMotion()) { return; } const cursor = document.querySelector(SELECTORS.customCursor); const cursorDot = document.querySelector(SELECTORS.customCursorDot); const trailsContainer = document.querySelector(SELECTORS.cursorTrails); if (!cursor || !cursorDot || !trailsContainer) { return; } const links = document.querySelectorAll(HOVER_TARGETS); const trailCount = 10; const trails = []; const createTrail = (x, y) => { const trail = document.createElement('div'); trail.className = 'custom-cursor-trail'; trail.style.left = `${x}px`; trail.style.top = `${y}px`; trailsContainer.appendChild(trail); setTimeout(() => { trail.style.opacity = '0'; trail.style.width = '2px'; trail.style.height = '2px'; setTimeout(() => trail.remove(), 500); }, 10); trails.push(trail); if (trails.length > trailCount) { const oldest = trails.shift(); if (oldest && oldest.parentNode) { oldest.remove(); } } }; document.addEventListener('mousemove', (event) => { const { clientX, clientY } = event; cursor.style.left = `${clientX}px`; cursor.style.top = `${clientY}px`; window.setTimeout(() => { cursorDot.style.left = `${clientX}px`; cursorDot.style.top = `${clientY}px`; }, 50); createTrail(clientX, clientY); }); links.forEach((link) => { link.addEventListener('mouseenter', () => document.body.classList.add('link-hover')); link.addEventListener('mouseleave', () => document.body.classList.remove('link-hover')); }); document.body.style.cursor = 'none'; const restoreNativeCursor = () => { cursor.style.display = 'none'; cursorDot.style.display = 'none'; document.body.style.cursor = 'auto'; }; window.addEventListener('touchstart', () => { restoreNativeCursor(); }, { once: true }); } function initScrollAnimations() { if (!window.gsap || !window.ScrollTrigger) { return; } // Skip GSAP animations if user prefers reduced motion if (isReducedMotion()) { return; } const scrollSections = document.querySelectorAll(SELECTORS.scrollSection); if (!scrollSections.length) { return; } const floatingCode = document.querySelectorAll(SELECTORS.floatingCode); const authorImages = document.querySelectorAll(SELECTORS.authorImage); const techIcons = document.querySelectorAll(SELECTORS.techIcon); const perspectiveCards = document.querySelectorAll(SELECTORS.perspectiveCard); const instructorCards = document.querySelectorAll(SELECTORS.instructorCard); if (floatingCode.length) { floatingCode.forEach((element) => window.gsap.set(element, { opacity: 0, y: 20 })); } if (authorImages.length) { authorImages.forEach((element) => window.gsap.set(element, { opacity: 0, scale: 0.8 })); } if (techIcons.length) { techIcons.forEach((element) => window.gsap.set(element, { opacity: 0, scale: 0.8 })); } if (perspectiveCards.length) { perspectiveCards.forEach((element, index) => { window.gsap.set(element, { opacity: 0, x: index % 2 === 0 ? -50 : 50 }); }); } if (instructorCards.length) { instructorCards.forEach((element) => window.gsap.set(element, { opacity: 0, y: 50 })); } scrollSections.forEach((section) => { window.ScrollTrigger.create({ trigger: section, start: 'top 80%', onEnter: () => animateSection(section), once: true }); }); if (floatingCode.length) { window.gsap.to(floatingCode, { opacity: 0.5, y: 0, duration: 1, stagger: 0.2, ease: 'power2.out', delay: 0.5 }); } if (techIcons.length) { window.gsap.to(techIcons, { opacity: 0.7, scale: 1, duration: 1, stagger: 0.15, ease: 'back.out(1.5)', delay: 1 }); } if (authorImages.length) { window.gsap.to(authorImages, { opacity: 1, scale: 1, duration: 0.8, stagger: 0.3, ease: 'back.out(1.7)', delay: 1.5 }); } } function animateSection(section) { if (!window.gsap) { return; } if (section.classList.contains('perspectives')) { const leftCards = section.querySelectorAll('.perspective-card:nth-child(odd)'); const rightCards = section.querySelectorAll('.perspective-card:nth-child(even)'); window.gsap.to(leftCards, { opacity: 1, x: 0, duration: 0.8, stagger: 0.2, ease: 'power2.out' }); window.gsap.to(rightCards, { opacity: 1, x: 0, duration: 0.8, stagger: 0.2, ease: 'power2.out', delay: 0.2 }); return; } if (section.classList.contains('instructors')) { const cards = section.querySelectorAll('.instructor-card'); window.gsap.to(cards, { opacity: 1, y: 0, duration: 1, stagger: 0.3, ease: 'back.out(1.2)' }); return; } if (section.classList.contains('cta')) { const title = section.querySelector('.cta-title'); const description = section.querySelector('.cta-description'); const button = section.querySelector('.cta-button'); if (title) { window.gsap.from(title, { opacity: 0, y: 30, duration: 0.8, ease: 'power2.out' }); } if (description) { window.gsap.from(description, { opacity: 0, y: 30, duration: 0.8, ease: 'power2.out', delay: 0.2 }); } if (button) { window.gsap.from(button, { y: 30, duration: 0.8, ease: 'power2.out', delay: 0.4 }); } } } function initParallaxScrolling() { if (!window.gsap) { return; } // Skip parallax on mobile/older devices or when reduced motion is preferred if (isMobile() || isOlderDevice() || isReducedMotion()) { return; } const floatingElements = document.querySelectorAll(SELECTORS.floatingCode); const techIcons = document.querySelectorAll(SELECTORS.techIcon); if (!floatingElements.length && !techIcons.length) { return; } window.addEventListener('scroll', () => { const scrollPosition = window.scrollY; floatingElements.forEach((element, index) => { const speed = 0.05 + index * 0.01; const yPos = -scrollPosition * speed; window.gsap.to(element, { y: yPos, duration: 0.5, ease: 'power1.out', overwrite: 'auto' }); }); techIcons.forEach((element, index) => { const speed = 0.03 + index * 0.01; const yPos = -scrollPosition * speed; window.gsap.to(element, { y: yPos, duration: 0.5, ease: 'power1.out', overwrite: 'auto' }); }); }, { passive: true }); } function initParticles() { if (typeof window.particlesJS !== 'function') { return; } const container = document.querySelector(SELECTORS.particles); if (!container) { return; } // Reduce or disable particles based on device and user preferences const reducedMotion = isReducedMotion(); const mobile = isMobile(); const olderDevice = isOlderDevice(); // Disable particles completely if reduced motion is preferred if (reducedMotion) { container.style.display = 'none'; return; } // Calculate particle count based on device capabilities let particleCount = 70; // Desktop default if (mobile || olderDevice) { particleCount = 30; // Significantly reduced for mobile/older devices } const particleConfig = { particles: { number: { value: particleCount, density: { enable: true, value_area: mobile ? 1000 : 1200 } }, color: { value: ['#ffc107', '#4057f4', '#ffffff'] }, shape: { type: 'circle', stroke: { width: 0, color: '#000000' }, polygon: { nb_sides: 5 } }, opacity: { value: mobile ? 0.5 : 0.4, random: true, anim: { enable: !mobile, // Disable animations on mobile for performance speed: 1, opacity_min: 0.1, sync: false } }, size: { value: mobile ? 2 : 2.5, random: true, anim: { enable: !mobile, // Disable size animations on mobile speed: 2, size_min: 0.1, sync: false } }, line_linked: { enable: true, distance: mobile ? 120 : 150, color: '#ffc107', opacity: mobile ? 0.3 : 0.25, width: mobile ? 0.6 : 0.8 }, move: { enable: true, speed: mobile ? 0.5 : 0.8, // Slower movement on mobile direction: 'none', random: true, straight: false, out_mode: 'out', bounce: false, attract: { enable: !mobile, // Disable attraction on mobile for performance rotateX: 600, rotateY: 1200 } } }, interactivity: { detect_on: 'canvas', events: { onhover: { enable: !mobile && !olderDevice, // Disable hover interactions on mobile/older devices mode: 'grab' }, onclick: { enable: !mobile && !olderDevice, // Disable click interactions on mobile/older devices mode: 'push' }, resize: true }, modes: { grab: { distance: 140, line_linked: { opacity: 1 } }, bubble: { distance: 400, size: 40, duration: 2, opacity: 8, speed: 3 }, repulse: { distance: 200, duration: 0.4 }, push: { particles_nb: 4 }, remove: { particles_nb: 2 } } }, retina_detect: true }; window.particlesJS('particles-js', particleConfig); } function initCodeElements() { const hero = document.querySelector(SELECTORS.hero); const heroContent = document.querySelector(SELECTORS.heroContent); const codeElements = document.querySelectorAll(SELECTORS.floatingCode); if (!hero || !heroContent || !codeElements.length) { return; } codeElements.forEach((element) => { element.style.animationName = 'pulse'; element.style.animationDuration = '3s'; element.style.animationIterationCount = 'infinite'; }); const heroParticles = document.getElementById('particles-js'); const adjustForMobile = () => { const isMobile = window.innerWidth <= 480; if (isMobile) { const firstCodeEl = document.querySelector('.code-element-1'); if (firstCodeEl && firstCodeEl.parentElement !== heroContent) { heroContent.insertAdjacentElement('afterbegin', firstCodeEl); } const secondCodeEl = document.querySelector('.code-element-2'); const thirdCodeEl = document.querySelector('.code-element-3'); if (secondCodeEl && secondCodeEl.parentElement !== hero) { hero.appendChild(secondCodeEl); } if (thirdCodeEl && thirdCodeEl.parentElement !== hero) { hero.appendChild(thirdCodeEl); } return; } if (!heroParticles) { return; } document.querySelectorAll(SELECTORS.floatingCode).forEach((element) => { if (element.parentElement !== hero) { hero.insertBefore(element, heroParticles.nextSibling); } }); }; adjustForMobile(); window.addEventListener('resize', adjustForMobile); } function initPopup() { const popupOverlay = document.querySelector(SELECTORS.popupOverlay); if (!popupOverlay) { return; } const popupClose = document.querySelector(SELECTORS.popupClose); const popupContent = popupOverlay.querySelector('.new-popup-content'); const ctaButtons = [ document.getElementById('headerCta'), document.getElementById('heroCta'), document.getElementById('bottomCta'), document.getElementById('middleCta') ].filter(Boolean); const firstNameInput = document.getElementById('ck-first-name'); let focusableElements = []; let firstFocusable = null; let lastFocusable = null; const handleFocusTrap = (event) => { if (event.key !== 'Tab' || !popupOverlay.classList.contains('active')) { return; } if (!firstFocusable || !lastFocusable) { return; } if (event.shiftKey) { if (document.activeElement === firstFocusable) { event.preventDefault(); lastFocusable.focus(); } return; } if (document.activeElement === lastFocusable) { event.preventDefault(); firstFocusable.focus(); } }; const setupFocusTrap = () => { focusableElements = popupOverlay.querySelectorAll('input, button, textarea, select, a[href], [tabindex]:not([tabindex="-1"])'); if (!focusableElements.length) { return; } firstFocusable = focusableElements[0]; lastFocusable = focusableElements[focusableElements.length - 1]; document.addEventListener('keydown', handleFocusTrap); }; const removeFocusTrap = () => { document.removeEventListener('keydown', handleFocusTrap); focusableElements = []; firstFocusable = null; lastFocusable = null; }; const openPopup = () => { popupOverlay.classList.add('active'); if (popupContent) { popupContent.style.opacity = '1'; } document.body.style.overflow = 'hidden'; if (firstNameInput) { window.setTimeout(() => firstNameInput.focus(), 100); } setupFocusTrap(); }; const closePopup = () => { popupOverlay.classList.remove('active'); if (popupContent) { popupContent.style.opacity = '0'; } document.body.style.overflow = ''; removeFocusTrap(); }; ctaButtons.forEach((button) => button.addEventListener('click', openPopup)); if (popupClose) { popupClose.addEventListener('click', closePopup); } popupOverlay.addEventListener('click', (event) => { if (event.target === popupOverlay) { closePopup(); } }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && popupOverlay.classList.contains('active')) { closePopup(); } }); } function initScrollIndicator() { const scrollIndicator = document.getElementById('scrollIndicator'); const targetSection = document.getElementById('co-cie-czeka'); if (!scrollIndicator || !targetSection) { return; } scrollIndicator.addEventListener('click', () => { const targetPosition = targetSection.offsetTop - 100; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); }); // Hide scroll indicator after scrolling past hero const hideIndicatorOnScroll = () => { const heroHeight = window.innerHeight; if (window.scrollY > heroHeight * 0.5) { scrollIndicator.style.opacity = '0'; } else { scrollIndicator.style.opacity = '1'; } }; window.addEventListener('scroll', hideIndicatorOnScroll, { passive: true }); } function initHeroParallax() { // Skip hero parallax on mobile or when reduced motion is preferred if (isMobile() || isReducedMotion()) { return; } const heroSection = document.querySelector(SELECTORS.hero); const heroBackground = document.querySelector(SELECTORS.heroBackground); if (!heroSection || !heroBackground) { return; } const handleMouseMove = (event) => { const x = event.clientX / window.innerWidth; const y = event.clientY / window.innerHeight; heroBackground.style.transform = `translate(${x * -30}px, ${y * -30}px) scale(1.05)`; }; const resetBackground = () => { if (window.gsap) { window.gsap.to(heroBackground, { transform: 'translate(0, 0) scale(1)', duration: 1, ease: 'power2.out' }); return; } heroBackground.style.transform = 'translate(0, 0) scale(1)'; }; heroSection.addEventListener('mousemove', handleMouseMove); heroSection.addEventListener('mouseleave', resetBackground); } function initStickyHeader() { const header = document.querySelector('header'); if (!header) { return; } const scrollThreshold = 50; const handleScroll = () => { if (window.scrollY > scrollThreshold) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } }; // Listen to scroll event with throttling for performance let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { handleScroll(); ticking = false; }); ticking = true; } }, { passive: true }); } function initAnchorOffset() { const anchors = document.querySelectorAll('a[href^="#"]'); if (!anchors.length) { return; } const header = document.querySelector('header'); const announcement = document.querySelector('.sticky-announcement'); const getOffset = () => { const headerHeight = header ? header.getBoundingClientRect().height : 0; const announcementHeight = announcement ? announcement.getBoundingClientRect().height : 0; const safetyGap = window.innerWidth <= 900 ? 12 : 20; return headerHeight + announcementHeight + safetyGap; }; const scrollWithOffset = (hash) => { if (!hash || hash === '#') { return; } const target = document.querySelector(hash); if (!target) { return; } const targetPosition = target.getBoundingClientRect().top + window.scrollY; const offset = getOffset(); window.scrollTo({ top: targetPosition - offset, behavior: 'smooth' }); }; anchors.forEach((anchor) => { const href = anchor.getAttribute('href'); if (!href || href.length <= 1 || !href.startsWith('#')) { return; } anchor.addEventListener('click', (event) => { const hash = anchor.hash; if (!hash) { return; } const target = document.querySelector(hash); if (!target) { return; } event.preventDefault(); scrollWithOffset(hash); if (history.replaceState) { history.replaceState(null, '', hash); } else { window.location.hash = hash; } }); }); if (window.location.hash) { window.setTimeout(() => scrollWithOffset(window.location.hash), 100); } } document.addEventListener('DOMContentLoaded', () => { const modules = [ { name: 'particles', init: initParticles }, { name: 'codeElements', init: initCodeElements }, { name: 'customCursor', init: initCustomCursor }, { name: 'scrollAnimations', init: initScrollAnimations }, { name: 'parallaxScrolling', init: initParallaxScrolling }, { name: 'popup', init: initPopup }, { name: 'heroParallax', init: initHeroParallax }, { name: 'scrollIndicator', init: initScrollIndicator }, { name: 'stickyHeader', init: initStickyHeader }, { name: 'anchorOffset', init: initAnchorOffset } ]; modules.forEach((module) => safeRun(module.name, module.init)); }); })(window, document);