Initial commit: Novizun redesign — government-approved STEM toy vendor website
- Modern, conversion-focused redesign for novizun.com - Dual-audience positioning: schools (B2B) and parents (B2C) - Government vendor credentials section (GeBIZ/VCG, UEN, tax invoices) - Government claims reference (Edusave, PGS, ECDA, MOE schemes) - 4-step school order process with claim-ready documentation - Product catalog with category filtering - Dark mode, responsive design, scroll animations - Floating mobile CTA for school enquiries
This commit is contained in:
157
script.js
Normal file
157
script.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/* Novizun — Interactive Script */
|
||||
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Dark mode toggle
|
||||
(function () {
|
||||
const toggle = document.querySelector('[data-theme-toggle]');
|
||||
const root = document.documentElement;
|
||||
let dark = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
root.setAttribute('data-theme', dark);
|
||||
|
||||
if (toggle) {
|
||||
toggle.addEventListener('click', function () {
|
||||
dark = dark === 'dark' ? 'light' : 'dark';
|
||||
root.setAttribute('data-theme', dark);
|
||||
toggle.setAttribute('aria-label', 'Switch to ' + (dark === 'dark' ? 'light' : 'dark') + ' mode');
|
||||
toggle.innerHTML = dark === 'dark'
|
||||
? '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
|
||||
: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Mobile menu toggle
|
||||
(function () {
|
||||
const btn = document.getElementById('mobile-menu-btn');
|
||||
const nav = document.getElementById('nav');
|
||||
if (btn && nav) {
|
||||
btn.addEventListener('click', function () {
|
||||
const expanded = btn.getAttribute('aria-expanded') === 'true';
|
||||
btn.setAttribute('aria-expanded', String(!expanded));
|
||||
nav.classList.toggle('open');
|
||||
});
|
||||
// Close on link click
|
||||
nav.querySelectorAll('.nav-link').forEach(function (link) {
|
||||
link.addEventListener('click', function () {
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
nav.classList.remove('open');
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Header scroll behavior
|
||||
(function () {
|
||||
const header = document.getElementById('header');
|
||||
let lastScroll = 0;
|
||||
window.addEventListener('scroll', function () {
|
||||
const scrollY = window.scrollY;
|
||||
if (scrollY > 10) {
|
||||
header.classList.add('header--scrolled');
|
||||
} else {
|
||||
header.classList.remove('header--scrolled');
|
||||
}
|
||||
lastScroll = scrollY;
|
||||
}, { passive: true });
|
||||
})();
|
||||
|
||||
// Product category filtering
|
||||
(function () {
|
||||
const tabs = document.querySelectorAll('.product-tab');
|
||||
const cards = document.querySelectorAll('.product-card');
|
||||
|
||||
tabs.forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
// Update active tab
|
||||
tabs.forEach(function (t) {
|
||||
t.classList.remove('active');
|
||||
t.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
tab.classList.add('active');
|
||||
tab.setAttribute('aria-selected', 'true');
|
||||
|
||||
const filter = tab.getAttribute('data-filter');
|
||||
|
||||
cards.forEach(function (card) {
|
||||
if (filter === 'all') {
|
||||
card.classList.remove('hidden');
|
||||
} else {
|
||||
const category = card.getAttribute('data-category') || '';
|
||||
if (category.includes(filter)) {
|
||||
card.classList.remove('hidden');
|
||||
} else {
|
||||
card.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Intersection Observer for scroll reveal
|
||||
(function () {
|
||||
const sections = document.querySelectorAll('.section');
|
||||
if (!('IntersectionObserver' in window)) return;
|
||||
|
||||
const observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
|
||||
|
||||
sections.forEach(function (section) {
|
||||
section.style.opacity = '0';
|
||||
section.style.transform = 'translateY(20px)';
|
||||
section.style.transition = 'opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1)';
|
||||
observer.observe(section);
|
||||
});
|
||||
})();
|
||||
|
||||
// Floating CTA — show after scrolling past hero
|
||||
(function () {
|
||||
var floatingCta = document.getElementById('floating-cta');
|
||||
if (!floatingCta) return;
|
||||
|
||||
var hero = document.querySelector('.hero');
|
||||
if (!hero) return;
|
||||
|
||||
// Check if mobile (floating CTA hidden on desktop via CSS, but let's be safe)
|
||||
function checkMobile() {
|
||||
return window.innerWidth < 768;
|
||||
}
|
||||
|
||||
function toggleFloatingCta() {
|
||||
if (!checkMobile()) {
|
||||
floatingCta.classList.remove('visible');
|
||||
return;
|
||||
}
|
||||
var heroBottom = hero.getBoundingClientRect().bottom;
|
||||
if (heroBottom < 0) {
|
||||
floatingCta.classList.add('visible');
|
||||
} else {
|
||||
floatingCta.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', toggleFloatingCta, { passive: true });
|
||||
window.addEventListener('resize', toggleFloatingCta, { passive: true });
|
||||
// Initial check
|
||||
toggleFloatingCta();
|
||||
})();
|
||||
Reference in New Issue
Block a user