/* ── FILTER ── */ const btns = document.querySelectorAll('.filter-btn'); const cards = document.querySelectorAll('.card'); btns.forEach(btn => { btn.addEventListener('click', () => { btns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const filter = btn.dataset.filter; cards.forEach((card, i) => { const match = filter === 'all' || card.dataset.category === filter; if (match) { card.classList.remove('hidden'); card.style.animationDelay = (i * 0.06) + 's'; card.style.animation = 'none'; card.offsetHeight; /* reflow */ card.style.animation = ''; } else { card.classList.add('hidden'); } }); }); }); /* ── GOLD SPARK PARTICLES — global fixed canvas, always works ── */ (function(){ /* Create ONE fixed canvas over entire viewport */ const GC = document.createElement('canvas'); GC.id = 'global-spark-canvas'; document.body.appendChild(GC); const ctx = GC.getContext('2d'); function resize(){ GC.width = window.innerWidth; GC.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); const COLORS = ['#f0d080','#ffd84d','#c9a84c','#fff5c0','#e8b84b','#ffe066','#ffffff']; function rand(a,b){ return a + Math.random()*(b-a); } /* Particle class */ function Particle(x, y){ const angle = rand(0, Math.PI*2); const speed = rand(1.8, 5.5); this.x = x; this.y = y; this.vx = Math.cos(angle)*speed; this.vy = Math.sin(angle)*speed - rand(1, 3.5); this.alpha = 1; this.decay = rand(0.016, 0.034); this.size = rand(2, 6); this.color = COLORS[Math.floor(Math.random()*COLORS.length)]; this.type = Math.floor(Math.random()*4); /* 0=circle 1=star 2=diamond 3=line */ this.rot = rand(0, Math.PI*2); this.rotV = rand(-0.15, 0.15); this.grav = 0.12; } Particle.prototype.update = function(){ this.vy += this.grav; this.x += this.vx; this.y += this.vy; this.rot += this.rotV; this.vx *= 0.97; this.alpha -= this.decay; }; Particle.prototype.draw = function(c){ c.save(); c.globalAlpha = Math.max(0, this.alpha); c.fillStyle = this.color; c.strokeStyle = this.color; c.translate(this.x, this.y); c.rotate(this.rot); const s = this.size; if(this.type === 0){ c.beginPath(); c.arc(0,0,s*.65,0,Math.PI*2); c.fill(); } else if(this.type === 1){ /* 4-point star */ c.beginPath(); for(let i=0;i<4;i++){ const a = (i/4)*Math.PI*2; const a2 = a + Math.PI/4; i===0 ? c.moveTo(Math.cos(a)*s, Math.sin(a)*s) : c.lineTo(Math.cos(a)*s, Math.sin(a)*s); c.lineTo(Math.cos(a2)*s*.35, Math.sin(a2)*s*.35); } c.closePath(); c.fill(); } else if(this.type === 2){ c.beginPath(); c.moveTo(0,-s); c.lineTo(s*.55,0); c.lineTo(0,s); c.lineTo(-s*.55,0); c.closePath(); c.fill(); } else { c.lineWidth = Math.max(1, s*.3); c.lineCap = 'round'; c.beginPath(); c.moveTo(-s,0); c.lineTo(s,0); c.stroke(); } c.restore(); }; let particles = []; let rafId = null; function spawnBurst(x, y, count){ for(let i=0;i p.alpha > 0); particles.forEach(p => { p.update(); p.draw(ctx); }); if(particles.length > 0) rafId = requestAnimationFrame(loop); else rafId = null; } function startLoop(){ if(!rafId) rafId = requestAnimationFrame(loop); } /* Attach to every .back-link button */ document.querySelectorAll('.back-link').forEach(btn => { let iv = null; btn.addEventListener('mouseenter', function(){ const r = btn.getBoundingClientRect(); const cx = r.left + r.width/2; const cy = r.top + r.height/2; /* immediate big burst */ spawnBurst(cx, cy, 28); startLoop(); /* continuous small bursts while hovering */ iv = setInterval(function(){ const r2 = btn.getBoundingClientRect(); spawnBurst(r2.left + r2.width/2, r2.top + r2.height/2, 12); startLoop(); }, 350); }); btn.addEventListener('mouseleave', function(){ clearInterval(iv); iv = null; }); }); })(); /* ── TOUCH FLIP ── */ cards.forEach(card => { card.addEventListener('click', () => { if (window.matchMedia('(hover: none)').matches) { card.classList.toggle('flipped'); } }); }); /* ── COUNTER ANIMATION ── */ function animateCounter(el, target, suffix) { let start = 0; const isFloat = target.toString().includes('.'); const duration = 1800; const step = (timestamp) => { if (!start) start = timestamp; const progress = Math.min((timestamp - start) / duration, 1); const ease = 1 - Math.pow(1 - progress, 3); const val = isFloat ? (ease * parseFloat(target)).toFixed(0) : Math.floor(ease * parseInt(target)); el.textContent = val + suffix; if (progress < 1) requestAnimationFrame(step); else el.textContent = target + suffix; }; requestAnimationFrame(step); } const statNums = document.querySelectorAll('.branch-stat-num'); const statData = [ {val:'9', suf:'+'}, {val:'20', suf:'+'}, {val:'5000', suf:'+'}, {val:'5', suf:''}, {val:'100', suf:'%'}, ]; console.log("Counter JS Loaded"); console.log(statNums); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { statNums.forEach((el, i) => { if (statData[i]) { setTimeout(() => { animateCounter(el, statData[i].val, statData[i].suf); }, i * 120); } }); observer.disconnect(); } }); }, { threshold: 0.4 }); observer.observe(document.querySelector('.stats-bar'));