/**
* LANDING CONTROLLER (v5.0)
* Drives the dynamic homepage by loading project data and homepage-content.json,
* rendering each section, and initializing scroll-driven behaviors.
*/
const LandingController = (() => {
// Module-level: the random entry chosen for CTA text this page load
let selectedHeroEntry = null;
async function init() {
const [projects, config] = await Promise.all([
DataLoader.loadAllProjects(),
DataLoader.loadHomepageContent()
]);
if (!projects.length || !config) return;
loadHomepage(config, projects);
initScrollBehaviors();
}
function loadHomepage(config, projects) {
renderHero(config, projects);
renderShowcase(config, projects);
renderCredentials(config);
renderProcess(config, projects);
renderCreative(config, projects);
renderImpact(config, projects);
renderAchievements(config, projects);
renderCTA(config);
}
// ──────────────────────────────────────────────
// HELPERS
// ──────────────────────────────────────────────
function buildSectionURL(filter, mode) {
const tags = [];
if (filter && filter.all) tags.push(...filter.all);
if (filter && filter.any) tags.push(...filter.any);
if (!tags.length) return '/section.html';
const params = tags.map(t => DataLoader.normalizeForURL(t)).join('+');
let url = `/section.html?tags=${params}`;
if (mode) url += `&mode=${mode}`;
return url;
}
// ──────────────────────────────────────────────
// RENDER FUNCTIONS
// ──────────────────────────────────────────────
function renderHero(config, projects) {
const filtered = DataLoader.resolveFilter(projects, config.hero.filter);
if (!filtered.length) return;
// ── ALL matching entries, fully randomized order ──
const heroEntries = DataLoader.shuffleArray(
filtered.filter(p => p.img && p.img.length >= 2)
);
const heroCount = heroEntries.length || 1;
// Pick 1 random entry for CTA fields (persists until next refresh)
const ctaPool = filtered.filter(p => p.hero_btn_cta);
selectedHeroEntry = ctaPool.length
? ctaPool[Math.floor(Math.random() * ctaPool.length)]
: heroEntries[0] || filtered[0];
// ── Hero images: all 3 per entry, each entry's images shuffled ──
const imgScroll = document.querySelector('.hero-img-scroll');
if (imgScroll && heroEntries.length) {
const heroImages = [];
heroEntries.forEach(entry => {
const shuffledImgs = DataLoader.shuffleArray([...entry.img]);
shuffledImgs.forEach(src => heroImages.push(src));
});
const doubled = [...heroImages, ...heroImages];
imgScroll.innerHTML = doubled
.map(src => {
const resolved = src.startsWith('http') ? src : '/' + src;
return ``;
})
.join('');
// Force reflow after DOM mutation so iOS Safari starts the animation
imgScroll.style.animation = 'none';
void imgScroll.offsetWidth;
imgScroll.style.animation = '';
imgScroll.style.animationDuration = (heroCount * 5) + 's';
}
// ── Flip headlines: same entries, same order ──
const flipTrack = document.querySelector('.flip-track');
if (flipTrack && heroEntries.length) {
flipTrack.innerHTML = heroEntries
.map((entry, i) => {
const headline = entry.role_headline || entry.title;
return `