File structure and key code explained
projects/nine-year-plan/
βββ index.html β Main app (all content embedded)
βββ css/
β βββ style.css β All styles, variables, animations, responsive
βββ js/
β βββ app.js β Scroll nav, fade-in observer, animated counters
βββ knowledge/
βββ the-plan.md
βββ core-activities.md
βββ framework.md
βββ institutions.md
βββ social-action.md
βββ messages.md
βββ youth.md
index.html. The knowledge/ folder contains Markdown source documents used to author the HTML β they are not loaded at runtime.
Statistics animate from 0 to their target value when they scroll into view. Uses requestAnimationFrame with ease-out cubic easing for a smooth deceleration effect. Each counter fires once and then unobserves itself.
function formatNum(n) {
if (n >= 1000000) return (n / 1000000).toFixed(1).replace('.0','') + 'M';
if (n >= 1000) return (n / 1000).toFixed(0) + ',000';
return n.toLocaleString();
}
const statNums = document.querySelectorAll('.stat-num[data-target]');
const counterIO = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const el = entry.target;
const target = parseInt(el.dataset.target, 10);
const dur = 1800;
const start = performance.now();
function tick(now) {
const p = Math.min((now - start) / dur, 1);
const ease = 1 - Math.pow(1 - p, 3); // ease-out cubic
el.textContent = formatNum(Math.round(ease * target));
if (p < 1) requestAnimationFrame(tick);
else el.textContent = formatNum(target);
}
requestAnimationFrame(tick);
counterIO.unobserve(el); // fire once only
});
}, { threshold: 0.5 });
statNums.forEach(el => counterIO.observe(el));
data-target="5700000" to any element with class stat-num and it will animate automatically on scroll.
Cards fade in with a staggered delay β each card waits 60ms longer than the previous one, creating a cascade effect as a grid of cards enters the viewport.
const fadeTargets = document.querySelectorAll(
'.activity-card, .framework-card, .institution-card, ' +
'.message-card, .social-card, .stat-card, .timeline-item, ' +
'.youth-item, .glossary-item, .variation-card'
);
fadeTargets.forEach(el => el.classList.add('fade-in'));
const io = new IntersectionObserver(entries => {
entries.forEach((e, i) => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('visible'), i * 60);
}
});
}, { threshold: 0.08, rootMargin: '0px 0px -30px 0px' });
fadeTargets.forEach(el => io.observe(el));
The site uses a deep navy/gold/ivory palette appropriate for the subject matter, defined as CSS custom properties.
:root {
--navy: #1B2A4A;
--navy-mid: #243556;
--navy-light: #2E4270;
--gold: #C8973E;
--gold-light: #E8B96A;
--ivory: #F8F4EE;
--ivory-mid: #EDE7DC;
--text-dark: #1B2A4A;
--text-mid: #3D4F70;
--text-light: #6B7A9A;
--section-light: #F8F4EE;
--section-mid: #EDE7DC;
--section-dark: #1B2A4A;
--radius: 8px;
--shadow-sm: 0 2px 8px rgba(0,0,0,0.08);
--shadow-md: 0 4px 16px rgba(0,0,0,0.12);
--transition: 0.3s ease;
}
Each major section of the site was authored from a corresponding Markdown file in knowledge/, containing verified facts and quotes from official BahΓ‘'Γ documents.
--- title: "The Nine Year Plan" description: "Overview of the Nine Year Plan 2022β2031" tags: ["plan", "UHJ", "2022", "2031", "society-building"] category: "Overview" --- ## What Is the Nine Year Plan? The Nine Year Plan (2022β2031) is the first major undertaking in a new twenty-five-year series of global Plans launched by the Universal House of Justice at RiαΈvΓ‘n 2022. ## Core Aim > "The release of the society-building power of the Faith of > BahΓ‘'u'llΓ‘h in ever-greater measures." ## Duration 2022 β 2031 (first of a series ending RiαΈvΓ‘n 2046)