/* ── School System — GSAP Animations & UI Logic ── */ // Wait for GSAP CDN to be ready document.addEventListener('DOMContentLoaded', () => { /* ── Register GSAP Plugins ── */ if (window.gsap) { gsap.registerPlugin(ScrollTrigger); initAnimations(); } initSidebar(); initAlerts(); initSearch(); initTooltips(); }); /* ══════════════════════════════════════ GSAP ANIMATIONS ══════════════════════════════════════ */ function initAnimations() { /* Page entrance — topbar */ gsap.from('.topbar', { y: -64, opacity: 0, duration: .5, ease: 'power3.out' }); /* Sidebar nav items stagger */ gsap.from('.nav-item', { x: -24, opacity: 0, duration: .4, stagger: .04, ease: 'power2.out', delay: .15 }); /* Page header */ gsap.from('.page-header', { y: 20, opacity: 0, duration: .5, ease: 'power3.out', delay: .2 }); /* Stat cards stagger */ gsap.from('.stat-card', { y: 32, opacity: 0, duration: .5, stagger: .08, ease: 'power3.out', delay: .3, clearProps: 'all' }); /* Animate stat values counting up */ document.querySelectorAll('.stat-value[data-count]').forEach(el => { const target = parseInt(el.dataset.count, 10); gsap.from({ val: 0 }, { val: target, duration: 1.2, delay: .5, ease: 'power2.out', onUpdate: function () { el.textContent = Math.round(this.targets()[0].val).toLocaleString(); } }); }); /* Cards scroll reveal */ gsap.utils.toArray('.card').forEach((card, i) => { gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 88%', toggleActions: 'play none none none' }, y: 28, opacity: 0, duration: .5, delay: i * .04, ease: 'power3.out', clearProps: 'all' }); }); /* Table rows stagger on scroll */ gsap.utils.toArray('tbody tr').forEach((row, i) => { gsap.from(row, { scrollTrigger: { trigger: row, start: 'top 92%', toggleActions: 'play none none none' }, x: -16, opacity: 0, duration: .35, delay: i * .03, ease: 'power2.out', clearProps: 'all' }); }); /* Buttons hover micro-animation */ document.querySelectorAll('.btn').forEach(btn => { btn.addEventListener('mouseenter', () => { gsap.to(btn, { scale: 1.03, duration: .15, ease: 'power1.out' }); }); btn.addEventListener('mouseleave', () => { gsap.to(btn, { scale: 1, duration: .15, ease: 'power1.out' }); }); }); /* Progress bars animate in */ document.querySelectorAll('.progress-bar').forEach(bar => { const width = bar.style.width || bar.dataset.width || '0%'; bar.style.width = '0%'; gsap.to(bar, { scrollTrigger: { trigger: bar, start: 'top 90%' }, width: width, duration: .9, ease: 'power3.out', delay: .1 }); }); /* Notice cards */ gsap.utils.toArray('.notice-card').forEach((card, i) => { gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 90%' }, y: 20, opacity: 0, duration: .4, delay: i * .06, ease: 'power2.out', clearProps: 'all' }); }); /* Login card */ const loginCard = document.querySelector('.login-card'); if (loginCard) { gsap.from(loginCard, { y: 40, opacity: 0, scale: .96, duration: .6, ease: 'power3.out' }); gsap.from('.login-card .form-group', { y: 16, opacity: 0, duration: .4, stagger: .08, delay: .3, ease: 'power2.out' }); } /* Dashboard welcome banner */ const banner = document.querySelector('.dashboard-banner'); if (banner) { gsap.from(banner, { y: -20, opacity: 0, duration: .6, ease: 'power3.out', delay: .1 }); gsap.from(banner.querySelectorAll('h1, p, .btn'), { y: 16, opacity: 0, stagger: .1, duration: .5, delay: .3, ease: 'power2.out' }); } } /* ══════════════════════════════════════ SIDEBAR TOGGLE (mobile) ══════════════════════════════════════ */ function initSidebar() { const toggle = document.getElementById('sidebar-toggle'); const sidebar = document.querySelector('.sidebar'); const overlay = document.getElementById('sidebar-overlay'); if (!toggle || !sidebar) return; toggle.addEventListener('click', () => { const isOpen = sidebar.classList.toggle('open'); if (overlay) overlay.classList.toggle('active', isOpen); if (window.gsap) { gsap.from(sidebar, { x: -260, duration: .3, ease: 'power3.out' }); } }); if (overlay) { overlay.addEventListener('click', () => { sidebar.classList.remove('open'); overlay.classList.remove('active'); }); } } /* ══════════════════════════════════════ AUTO-DISMISS ALERTS ══════════════════════════════════════ */ function initAlerts() { document.querySelectorAll('.alert').forEach(alert => { const close = alert.querySelector('.alert-close'); if (close) { close.addEventListener('click', () => dismissAlert(alert)); } setTimeout(() => dismissAlert(alert), 5000); }); } function dismissAlert(alert) { if (!alert.parentNode) return; if (window.gsap) { gsap.to(alert, { opacity: 0, y: -8, height: 0, marginBottom: 0, padding: 0, duration: .3, ease: 'power2.in', onComplete: () => alert.remove() }); } else { alert.remove(); } } /* ══════════════════════════════════════ LIVE SEARCH FILTER (client-side) ══════════════════════════════════════ */ function initSearch() { const searchInput = document.getElementById('table-search'); if (!searchInput) return; searchInput.addEventListener('input', () => { const q = searchInput.value.toLowerCase(); document.querySelectorAll('tbody tr').forEach(row => { const text = row.textContent.toLowerCase(); row.style.display = text.includes(q) ? '' : 'none'; }); }); } /* ══════════════════════════════════════ SIMPLE TOOLTIPS ══════════════════════════════════════ */ function initTooltips() { document.querySelectorAll('[data-tooltip]').forEach(el => { el.addEventListener('mouseenter', e => { const tip = document.createElement('div'); tip.className = 'gsap-tooltip'; tip.textContent = el.dataset.tooltip; tip.style.cssText = ` position:fixed;background:#0f172a;color:#fff;font-size:12px; padding:5px 10px;border-radius:6px;pointer-events:none;z-index:9999; white-space:nowrap;box-shadow:0 4px 12px rgba(0,0,0,.2); `; document.body.appendChild(tip); const rect = el.getBoundingClientRect(); tip.style.left = rect.left + rect.width / 2 - tip.offsetWidth / 2 + 'px'; tip.style.top = rect.top - tip.offsetHeight - 8 + 'px'; if (window.gsap) gsap.from(tip, { opacity: 0, y: 4, duration: .15 }); el._tooltip = tip; }); el.addEventListener('mouseleave', () => { if (el._tooltip) { el._tooltip.remove(); el._tooltip = null; } }); }); }