Spaces:
Running
Running
| /* ============================================ | |
| AW Interiors — Premium Script v3 | |
| Elite microinteractions & animations | |
| ============================================ */ | |
| // ─── PRELOADER ────────────────────────────── | |
| document.body.style.overflow = 'hidden'; | |
| window.addEventListener('load', () => { | |
| const fill = document.getElementById('preloaderFill'); | |
| const preloader = document.getElementById('preloader'); | |
| if (!fill || !preloader) return; | |
| requestAnimationFrame(() => { fill.style.width = '100%'; }); | |
| setTimeout(() => { | |
| preloader.classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; | |
| initCounters(); | |
| initReveal(); | |
| }, 2100); | |
| }); | |
| // ─── CUSTOM CURSOR ─────────────────────────── | |
| const cursor = document.getElementById('cursor'); | |
| const follower = document.getElementById('cursor-follower'); | |
| if (cursor && follower) { | |
| let mX = window.innerWidth / 2; | |
| let mY = window.innerHeight / 2; | |
| let fX = mX, fY = mY; | |
| let raf; | |
| document.addEventListener('mousemove', (e) => { | |
| mX = e.clientX; | |
| mY = e.clientY; | |
| cursor.style.left = mX + 'px'; | |
| cursor.style.top = mY + 'px'; | |
| }); | |
| document.addEventListener('mousedown', () => document.body.classList.add('cursor-click')); | |
| document.addEventListener('mouseup', () => document.body.classList.remove('cursor-click')); | |
| document.addEventListener('mouseleave', () => { cursor.style.opacity = '0'; follower.style.opacity = '0'; }); | |
| document.addEventListener('mouseenter', () => { cursor.style.opacity = ''; follower.style.opacity = ''; }); | |
| (function loopFollower() { | |
| fX += (mX - fX) * 0.095; | |
| fY += (mY - fY) * 0.095; | |
| follower.style.left = fX + 'px'; | |
| follower.style.top = fY + 'px'; | |
| requestAnimationFrame(loopFollower); | |
| })(); | |
| const hoverTargets = 'a, button, .portfolio-item, .tag, .filter-btn, .process-step, .testimonial-card, .award-item, .contact-detail'; | |
| document.querySelectorAll(hoverTargets).forEach(el => { | |
| el.addEventListener('mouseenter', () => document.body.classList.add('cursor-hover')); | |
| el.addEventListener('mouseleave', () => document.body.classList.remove('cursor-hover')); | |
| }); | |
| } | |
| // ─── NAV SCROLL ────────────────────────────── | |
| const nav = document.getElementById('nav'); | |
| let lastScrollY = 0; | |
| window.addEventListener('scroll', () => { | |
| const y = window.scrollY; | |
| if (nav) nav.classList.toggle('scrolled', y > 60); | |
| lastScrollY = y; | |
| }, { passive: true }); | |
| // ─── MOBILE NAV ─────────────────────────────── | |
| const navToggle = document.getElementById('navToggle'); | |
| const navMobile = document.getElementById('navMobile'); | |
| if (navToggle && navMobile) { | |
| navToggle.addEventListener('click', () => { | |
| const open = navMobile.classList.toggle('open'); | |
| navToggle.classList.toggle('open', open); | |
| navToggle.setAttribute('aria-expanded', String(open)); | |
| document.body.style.overflow = open ? 'hidden' : ''; | |
| }); | |
| navMobile.querySelectorAll('a').forEach(a => { | |
| a.addEventListener('click', () => { | |
| navMobile.classList.remove('open'); | |
| navToggle.classList.remove('open'); | |
| document.body.style.overflow = ''; | |
| }); | |
| }); | |
| } | |
| // ─── PARTICLES ──────────────────────────────── | |
| (function createParticles() { | |
| const container = document.getElementById('heroParticles'); | |
| if (!container) return; | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| @keyframes pFloat { | |
| 0%,100%{transform:translateY(0) translateX(0) scale(1);opacity:var(--op)} | |
| 30%{transform:translateY(-24px) translateX(14px) scale(1.12);opacity:calc(var(--op)*1.6)} | |
| 65%{transform:translateY(16px) translateX(-10px) scale(0.88);opacity:calc(var(--op)*0.5)} | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| for (let i = 0; i < 28; i++) { | |
| const p = document.createElement('div'); | |
| const size = (Math.random() * 2.8 + 0.5).toFixed(1); | |
| const op = (Math.random() * 0.22 + 0.04).toFixed(2); | |
| p.style.cssText = [ | |
| 'position:absolute', | |
| `width:${size}px`, `height:${size}px`, | |
| 'background:rgba(200,169,106,1)', | |
| 'border-radius:50%', | |
| `left:${(Math.random() * 92 + 4).toFixed(1)}%`, | |
| `top:${(Math.random() * 92 + 4).toFixed(1)}%`, | |
| `--op:${op}`, | |
| `opacity:${op}`, | |
| `animation:pFloat ${(Math.random() * 12 + 9).toFixed(1)}s ease-in-out infinite`, | |
| `animation-delay:-${(Math.random() * 8).toFixed(1)}s`, | |
| 'pointer-events:none', | |
| 'will-change:transform' | |
| ].join(';'); | |
| container.appendChild(p); | |
| } | |
| })(); | |
| // ─── REVEAL ON SCROLL ───────────────────────── | |
| let revealObserver; | |
| function initReveal() { | |
| revealObserver = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (!entry.isIntersecting) return; | |
| const el = entry.target; | |
| el.classList.add('visible'); | |
| revealObserver.unobserve(el); | |
| }); | |
| }, { threshold: 0.07, rootMargin: '0px 0px -48px 0px' }); | |
| document.querySelectorAll('.reveal').forEach((el) => { | |
| const siblings = Array.from(el.parentElement.querySelectorAll('.reveal')); | |
| const idx = siblings.indexOf(el); | |
| if (idx > 0 && idx < 6) { | |
| const existing = parseFloat(el.style.transitionDelay) || 0; | |
| el.style.transitionDelay = (existing + idx * 0.09) + 's'; | |
| } | |
| revealObserver.observe(el); | |
| }); | |
| } | |
| // ─── COUNTER ANIMATION ──────────────────────── | |
| function initCounters() { | |
| const easeOutExpo = t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t); | |
| document.querySelectorAll('.hero-stat-num').forEach(el => { | |
| const target = parseInt(el.dataset.count, 10); | |
| if (!target) return; | |
| const dur = 2000; | |
| const start = performance.now(); | |
| function tick(now) { | |
| const p = Math.min((now - start) / dur, 1); | |
| el.textContent = Math.round(easeOutExpo(p) * target); | |
| if (p < 1) requestAnimationFrame(tick); | |
| else el.textContent = target; | |
| } | |
| requestAnimationFrame(tick); | |
| }); | |
| } | |
| // ─── PORTFOLIO FILTER ───────────────────────── | |
| const filterBtns = document.querySelectorAll('.filter-btn'); | |
| const portfolioItems = document.querySelectorAll('.portfolio-item'); | |
| filterBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| if (btn.classList.contains('active')) return; | |
| filterBtns.forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| const filter = btn.dataset.filter; | |
| let delay = 0; | |
| portfolioItems.forEach(item => { | |
| const match = filter === 'all' || item.dataset.category === filter; | |
| if (match) { | |
| item.classList.remove('filtered-out'); | |
| item.style.opacity = '0'; | |
| item.style.transform = 'translateY(12px) scale(0.98)'; | |
| item.style.transition = 'none'; | |
| requestAnimationFrame(() => { | |
| setTimeout(() => { | |
| item.style.transition = `opacity 0.55s ease ${delay}s, transform 0.55s cubic-bezier(0.16,1,0.3,1) ${delay}s`; | |
| item.style.opacity = '1'; | |
| item.style.transform = 'translateY(0) scale(1)'; | |
| }, 16); | |
| }); | |
| delay += 0.06; | |
| } else { | |
| item.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; | |
| item.style.opacity = '0'; | |
| item.style.transform = 'scale(0.97)'; | |
| setTimeout(() => { | |
| item.classList.add('filtered-out'); | |
| item.style.opacity = ''; | |
| item.style.transform = ''; | |
| item.style.transition = ''; | |
| }, 310); | |
| } | |
| }); | |
| }); | |
| }); | |
| // ─── MAGNETIC BUTTONS ───────────────────────── | |
| document.querySelectorAll('.btn-primary, .nav-cta').forEach(btn => { | |
| btn.addEventListener('mousemove', (e) => { | |
| const rect = btn.getBoundingClientRect(); | |
| const dx = (e.clientX - rect.left - rect.width / 2) * 0.22; | |
| const dy = (e.clientY - rect.top - rect.height / 2) * 0.22; | |
| btn.style.transform = `translate(${dx}px, ${dy}px)`; | |
| }); | |
| btn.addEventListener('mouseleave', () => { | |
| btn.style.transform = ''; | |
| }); | |
| }); | |
| // ─── CONTACT FORM ───────────────────────────── | |
| const form = document.getElementById('contactForm'); | |
| const formSuccess = document.getElementById('formSuccess'); | |
| if (form) { | |
| form.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const btn = form.querySelector('button[type="submit"]'); | |
| if (btn.disabled) return; | |
| const originalHTML = btn.innerHTML; | |
| btn.innerHTML = `<span>Wysyłanie</span><svg width="18" height="18" viewBox="0 0 24 24" fill="none" style="animation:spinBtn 0.85s linear infinite"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.5" stroke-dasharray="40 22" stroke-linecap="round"/></svg>`; | |
| btn.disabled = true; | |
| if (!document.getElementById('spinBtnStyle')) { | |
| const s = document.createElement('style'); | |
| s.id = 'spinBtnStyle'; | |
| s.textContent = '@keyframes spinBtn{to{rotate:360deg}}'; | |
| document.head.appendChild(s); | |
| } | |
| setTimeout(() => { | |
| form.reset(); | |
| if (formSuccess) { | |
| formSuccess.classList.add('visible'); | |
| setTimeout(() => formSuccess.classList.remove('visible'), 5500); | |
| } | |
| btn.innerHTML = originalHTML; | |
| btn.disabled = false; | |
| }, 1700); | |
| }); | |
| // Live label animation for select | |
| const select = form.querySelector('select'); | |
| if (select) { | |
| select.style.color = 'rgba(244,239,232,0.3)'; | |
| select.addEventListener('change', () => { | |
| select.style.color = select.value ? 'var(--white)' : 'rgba(244,239,232,0.3)'; | |
| }); | |
| } | |
| } | |
| // ─── SMOOTH ANCHOR SCROLL ───────────────────── | |
| document.querySelectorAll('a[href^="#"]').forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| const href = link.getAttribute('href'); | |
| if (href === '#') return; | |
| const target = document.querySelector(href); | |
| if (target) { | |
| e.preventDefault(); | |
| const navH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--nav-h'), 10) || 78; | |
| const offset = target.getBoundingClientRect().top + window.scrollY - navH; | |
| window.scrollTo({ top: offset, behavior: 'smooth' }); | |
| } | |
| }); | |
| }); | |
| // ─── PARALLAX ───────────────────────────────── | |
| function onScroll() { | |
| const y = window.scrollY; | |
| const heroGrid = document.querySelector('.hero-grid'); | |
| if (heroGrid && y < window.innerHeight * 1.6) { | |
| heroGrid.style.transform = `translateY(${y * 0.22}px)`; | |
| } | |
| const philoBg = document.querySelector('.philosophy-bg'); | |
| if (philoBg) { | |
| const rect = philoBg.parentElement.getBoundingClientRect(); | |
| if (rect.top < window.innerHeight && rect.bottom > 0) { | |
| const progress = (window.innerHeight - rect.top) / (window.innerHeight + rect.height); | |
| philoBg.style.transform = `scale(1.06) translateY(${(progress - 0.5) * -28}px)`; | |
| } | |
| } | |
| } | |
| window.addEventListener('scroll', onScroll, { passive: true }); | |
| // ─── SECTION SEPARATOR LINES ────────────────── | |
| const sepStyle = document.createElement('style'); | |
| sepStyle.textContent = ` | |
| .section-sep{height:1px;background:linear-gradient(to right,transparent,rgba(200,169,106,0.28),transparent);transform:scaleX(0);transform-origin:left;transition:transform 1.3s cubic-bezier(0.16,1,0.3,1)} | |
| .section-sep.line-visible{transform:scaleX(1)} | |
| `; | |
| document.head.appendChild(sepStyle); | |
| const lineObs = new IntersectionObserver((entries) => { | |
| entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('line-visible'); }); | |
| }, { threshold: 0.3 }); | |
| document.querySelectorAll('.section-sep').forEach(el => lineObs.observe(el)); | |
| // ─── TILT ON HOVER — PORTFOLIO CARDS ────────── | |
| document.querySelectorAll('.portfolio-item').forEach(card => { | |
| 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; | |
| card.style.transform = `translateY(-3px) rotateX(${-y * 4}deg) rotateY(${x * 4}deg)`; | |
| }); | |
| card.addEventListener('mouseleave', () => { | |
| card.style.transform = ''; | |
| }); | |
| }); | |