1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Africa Coalition For Academic Freedom (ACAF) - Defending Intellectual Freedom</title> 8 <meta name="description" 9 content="Promoting, defending, and advancing academic freedom, institutional autonomy, and freedom of expression in higher education across Africa."> 10 <link rel="stylesheet" href="assets/css/style.css"> 11 <link rel="icon" type="image/png" href="assets/images/ACAF_logo.png"> 12 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> 13 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/css/splide.min.css"> 14
14<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
14 15 <style> 16 /* ââ Latest Insights popup âââââââââââââââââââââââââââââââ */ 17 .hi-overlay { 18 display: none; 19 position: fixed; 20 inset: 0; 21 background: rgba(0,0,0,.55); 22 z-index: 9000; 23 align-items: flex-start; 24 justify-content: center; 25 padding: 2rem 1rem; 26 overflow-y: auto; 27 } 28 .hi-overlay.open { display: flex; } 29 .hi-box { 30 background: #fff; 31 border-radius: 12px; 32 width: 100%; 33 max-width: 680px; 34 margin: auto; 35 box-shadow: 0 20px 60px rgba(0,0,0,.25); 36 position: relative; 37 display: flex; 38 flex-direction: column; 39 overflow: hidden; 40 } 41 .hi-cover { width: 100%; max-height: 320px; overflow: hidden; } 42 .hi-cover img { width: 100%; height: 100%; object-fit: cover; display: block; } 43 .hi-close { 44 position: absolute; 45 top: .75rem; right: .75rem; 46 width: 34px; height: 34px; 47 border-radius: 50%; 48 background: rgba(15,23,42,.7); 49 color: #fff; 50 border: none; 51 cursor: pointer; 52 display: flex; align-items: center; justify-content: center; 53 z-index: 2; 54 } 55 .hi-close:hover { background: rgba(15,23,42,.9); } 56 .hi-body { padding: 1.5rem 1.75rem; } 57 .hi-meta-line { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: .75rem; } 58 .hi-badge { 59 display: inline-block; 60 padding: .25rem .75rem; 61 border-radius: 999px; 62 font-size: .72rem; 63 font-weight: 700; 64 letter-spacing: .04em; 65 text-transform: uppercase; 66 background: var(--color-accent); 67 color: var(--color-primary); 68 } 69 .hi-date { font-size: .85rem; color: var(--color-text-light); } 70 .hi-title { font-size: 1.45rem; color: var(--color-primary); margin: 0 0 1rem; line-height: 1.3; } 71 .hi-desc { font-size: .95rem; line-height: 1.75; color: var(--color-text-main); white-space: pre-line; } 72 .hi-desc p { margin-bottom: 1rem; white-space: normal; } 73 .hi-desc p:last-child { margin-bottom: 0; } 74 .hi-section-title { 75 font-size: .72rem; 76 font-weight: 700; 77 text-transform: uppercase; 78 letter-spacing: .07em; 79 color: var(--color-text-light); 80 margin: 1.25rem 0 .5rem; 81 } 82 .hi-links, .hi-tags { display: flex; flex-wrap: wrap; gap: .5rem; } 83 .hi-link-btn { 84 display: inline-flex; 85 align-items: center; 86 gap: .45rem; 87 padding: .55rem 1rem; 88 border: 1px solid var(--color-border); 89 border-radius: 6px; 90 background: #fff; 91 color: var(--color-primary); 92 font-size: .85rem; 93 font-weight: 500; 94 text-decoration: none; 95 } 96 .hi-link-btn:hover { background: var(--color-light); border-color: var(--color-primary); } 97 .hi-tag { padding: .3rem .7rem; border-radius: 999px; background: var(--color-light); font-size: .8rem; color: var(--color-text-main); } 98 .hi-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .85rem; } 99 .hi-meta-item label { display: block; font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--color-text-light); margin-bottom: .15rem; } 100 .hi-meta-item span { font-size: .92rem; color: var(--color-text-main); } 101 .hi-sources { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .4rem; } 102 .hi-video { position: relative; padding-top: 56.25%; background: #000; } 103 .hi-video iframe, 104 .hi-video video { position: absolute; inset: 0; width: 100%; height: 100%; border: none; } 105 /* Reels and shorts are 9:16 â a fixed height with the width derived from 106 it, so the video is never cropped to a strip. */ 107 .hi-video.is-portrait { 108 padding-top: 0; 109 height: min(70vh, 640px); 110 max-width: calc(min(70vh, 640px) * 9 / 16); 111 margin: 0 auto; 112 } 113 /* Instagram and X serve a self-sizing card, not a bare player. */ 114 .hi-video.is-social { padding-top: 0; height: min(70vh, 640px); background: #fff; } 115 .hi-video.is-social iframe { position: static; height: 100%; background: #fff; } 116 .hi-footer { 117 padding: 1rem 1.75rem 1.25rem; 118 border-top: 1px solid var(--color-border); 119 display: flex; 120 justify-content: flex-end; 121 gap: .5rem; 122 flex-wrap: wrap; 123 } 124 @media (max-width: 560px) { 125 .hi-meta-grid { grid-template-columns: 1fr; } 126 } 127 </style> 128 <style> 129 /* Admin-controlled homepage popup */ 130 .home-popup-overlay { 131 position: fixed; inset: 0; z-index: 9500; 132 background: rgba(15, 23, 42, 0.6); 133 display: none; align-items: center; justify-content: center; 134 padding: 1rem; opacity: 0; transition: opacity .25s ease; 135 } 136 .home-popup-overlay.open { display: flex; opacity: 1; } 137 .home-popup-card { 138 background: #fff; border-radius: 14px; overflow: hidden; 139 width: 100%; max-width: 440px; position: relative; 140 box-shadow: 0 24px 70px rgba(0,0,0,.35); 141 transform: translateY(12px) scale(.98); transition: transform .25s ease; 142 max-height: calc(100vh - 2rem); display: flex; flex-direction: column; 143 } 144 .home-popup-overlay.open .home-popup-card { transform: translateY(0) scale(1); } 145 .home-popup-close { 146 position: absolute; top: .6rem; right: .6rem; z-index: 2; 147 width: 34px; height: 34px; border-radius: 50%; border: none; cursor: pointer; 148 background: rgba(15,23,42,.55); color: #fff; font-size: 1rem; 149 display: inline-flex; align-items: center; justify-content: center; transition: background .18s; 150 } 151 .home-popup-close:hover { background: rgba(15,23,42,.8); } 152 .home-popup-media { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; background: #E2E8F0; } 153 .home-popup-media-fallback { 154 width: 100%; aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center; 155 background: linear-gradient(135deg, var(--color-primary) 0%, #1e3a5f 100%); color: #fff; font-size: 2.4rem; 156 } 157 .home-popup-body { padding: 1.25rem 1.4rem 1.5rem; overflow-y: auto; } 158 .home-popup-badge { 159 display: inline-block; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; 160 font-weight: 700; color: var(--color-primary); background: #EFF3FF; 161 padding: .2rem .6rem; border-radius: 999px; margin-bottom: .6rem; 162 } 163 .home-popup-title { font-size: 1.25rem; line-height: 1.3; margin: 0 0 .6rem; color: #0F172A; } 164 .home-popup-desc { 165 font-size: .9rem; line-height: 1.6; color: var(--color-text-light); margin: 0 0 1.1rem; 166 display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; 167 } 168 .home-popup-cta { 169 display: inline-flex; align-items: center; background: var(--color-primary); color: #fff; 170 padding: .65rem 1.25rem; border-radius: 8px; font-weight: 600; font-size: .9rem; 171 text-decoration: none; transition: background .18s; 172 } 173 .home-popup-cta:hover { background: #1e3a5f; } 174 @media (max-width: 480px) { .home-popup-card { max-width: 100%; } .home-popup-title { font-size: 1.1rem; } } 175 </style> 176</head> 177 178<body> 179 180 <!-- Header / Navigation --> 181 <header> 182 <div class="container nav-container"> 183 <a href="index.html" class="logo"> 184 <img src="assets/images/ACAF_logo.png" alt="ACAF Logo">
185 <span class="logo-text">ACAF</span> 186 </a> 187 <!-- Socials removed --> 188 <button class="mobile-menu-btn">â°</button> 189 <nav class="nav-links"> 190 <a href="index.html" class="nav-link active" data-i18n="nav.home">Home</a> 191 <div class="nav-dropdown"> 192 <a href="about.html" class="nav-link" data-i18n="nav.about">About Us <span class="dropdown-arrow">▼</span></a> 193 <div class="dropdown-menu"> 194 <a href="about.html" class="dropdown-item">About ACAF</a> 195 <a href="work.html" class="dropdown-item">Our Work</a> 196 <a href="teams.html" class="dropdown-item">Our Team</a> 197 </div> 198 </div> 199 <a href="monitoring.html" class="nav-link" data-i18n="nav.monitoring">Monitoring</a> 200 <a href="news.html" class="nav-link" data-i18n="nav.news">News & Events</a> 201 <a href="publications.html" class="nav-link">Publications & Resources</a> 202 <a href="newsletter.html" class="nav-link">Newsletter</a> 203 <a href="gallery.html" class="nav-link">Gallery</a> 204 <a href="contact.html" class="nav-link" data-i18n="nav.contact">Contact</a> 205 <a href="get-involved.html" class="nav-link" data-i18n="nav.get-involved">Get Involved</a> 206 </nav> 207 </div> 208 </header> 209 210 <!-- Hero Section --> 211 <section class="hero"> 212 <div class="container hero-inner"> 213 214 <!-- Left: text --> 215 <div class="hero-text hero-full fade-in" id="heroText"> 216 <h1 data-i18n="home.hero.title">Defending Academic Freedom Across Africa</h1> 217 <p data-i18n="home.hero.text">We are dedicated to promoting critical enquiry and the pursuit of truth in Africa's higher education and other research institutions to ensure that knowledge can flourish to promote learning and societal well-being without fear or restriction.</p> 218 <div class="hero-btns"> 219 <a href="get-involved.html" class="btn btn-primary" 220 style="background-color:var(--color-accent);border:none;color:var(--color-primary);" data-i18n="home.hero.member">Become a Member</a> 221 </div> 222 </div> 223 224 <!-- Right: carousel (revealed once posts load) --> 225 <div class="hero-carousel-wrap" id="heroCarouselWrap" style="display:none;" aria-hidden="true"> 226 <div class="hero-carousel-label"> 227 <span>Latest News & Events</span> 228 <a href="news.html">View All <i class="fas fa-arrow-right" style="font-size:0.65rem;"></i></a> 229 </div> 230 <!-- loading skeleton shown while fetching --> 231 <div class="hero-carousel-skeleton" id="heroSkeleton" aria-hidden="true"> 232 <i class="fas fa-spinner fa-spin"></i> 233 </div> 234 <div id="heroSplide" class="splide" aria-label="Latest news and events" style="display:none;"> 235 <div class="splide__track"> 236 <ul class="splide__list" id="heroSlidesList" data-i18n-dynamic></ul> 237 </div> 238 </div> 239 </div> 240 241 </div> 242 </section> 243 244 <!-- Introduction / Mission --> 245 <section class="section bg-white"> 246 <div class="container grid grid-2 fade-in" style="align-items: center;"> 247 <div> 248 <h4 class="text-accent" style="text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.9rem;" data-i18n="home.who.label">Who We Are</h4> 249 <h2 data-i18n="home.who.title">Safeguarding the Future of African Education and Democracy</h2> 250 <p data-i18n="home.who.text1">Academic freedom is the cornerstone of a vibrant, innovative, and democratic society. Yet, across the continent, institutions, scholars and students face increasing pressures that threaten their ability to teach, research, produce/disseminate knowledge, speak truth to power and challenge orthodoxy.</p> 251 <p data-i18n="home.who.text2">ACAF stands as a guardian of these fundamental principles, uniting educators, researchers, students, policymakers and civil society to build resilient academic ecosystems.</p> 252 <a href="about.html" class="read-more" data-i18n="home.who.link">Read Our Full Mission â</a> 253 </div> 254 <div 255 style="height: 400px; border-radius: 8px; display: flex; align-items: center; justify-content: center;"> 256 <!-- Placeholder for an image --> 257 <img src="assets/images/web_image_1.jpg" loading="lazy" class="img-subtle-border" 258 style="width: 100%; height: 100%; object-fit: cover; border-radius: 8px;" 259 alt="Diverse group of African scholars in discussion"> 260 </div> 261 </div> 262 </section> 263 264 <!-- Key Focus Areas --> 265 <section class="section bg-light"> 266 <div class="container"> 267 <div class="text-center fade-in" style="max-width: 800px; margin: 0 auto 4rem;"> 268 <h2 data-i18n="home.pillars.title">Our Strategic Pillars</h2> 269 <p data-i18n="home.pillars.text">We approach the defense of academic freedom through four interconnected pillars of action.</p> 270 </div> 271 272 <div class="grid grid-4 fade-in"> 273 <div class="card"> 274 <div class="card-icon">ðï¸</div> 275 <h3 data-i18n="home.pillars.monitoring">Monitoring</h3> 276 <p class="text-sm" data-i18n="home.pillars.monitoring.text">Tracking and documenting violations of academic freedom to ensure accountability 277 and transparency.</p> 278 </div> 279 <div class="card"> 280 <div class="card-icon">ð¢</div> 281 <h3 data-i18n="home.pillars.advocacy">Advocacy</h3> 282 <p class="text-sm" data-i18n="home.pillars.advocacy.text">
282Engaging with governments and international bodies to shape policies that protect 283 scholars.</p> 284 </div> 285 <div class="card"> 286 <div class="card-icon">ð</div> 287 <h3 data-i18n="home.pillars.research">Research</h3> 288 <p class="text-sm" data-i18n="home.pillars.research.text">Producing data-driven reports and analyses on the state of higher education in 289 Africa.</p> 290 </div> 291 <div class="card"> 292 <div class="card-icon">ð¤</div> 293 <h3 data-i18n="home.pillars.capacity">Capacity Building</h3> 294 <p class="text-sm" data-i18n="home.pillars.capacity.text">Empowering institutions and individuals with the tools to defend their rights. 295 </p> 296 </div> 297 </div> 298 </div> 299 </section> 300 301 <!-- Latest News / Publications --> 302 <section class="section bg-white"> 303 <div class="container"> 304 <div class="flex justify-between items-center fade-in" style="margin-bottom: 3rem;"> 305 <h2 data-i18n="home.insights.title">Latest Insights</h2> 306 <a href="publications.html" class="btn btn-outline btn-sm" data-i18n="home.insights.view-all">View All Publications</a> 307 </div> 308 309 <div class="grid grid-3 fade-in" id="insightsGrid" data-i18n-dynamic> 310 <!-- loading skeleton shown while fetching --> 311 <div class="text-center" id="insightsLoading" style="grid-column:1/-1;padding:2rem;color:#94A3B8;"> 312 <i class="fas fa-spinner fa-spin"></i> 313 </div> 314 </div> 315 <p class="text-center" id="insightsEmpty" style="display:none;color:#94A3B8;">No publications to show yet. Check back soon.</p> 316 </div> 317 </section> 318 319 <!-- CTA Section --> 320 <section class="section bg-dark text-center"> 321 <div class="container fade-in"> 322 <h2 style="color: white; margin-bottom: 1.5rem;" data-i18n="home.cta.title">Stand With Us</h2> 323 <p style="color: #cbd5e1; max-width: 600px; margin: 0 auto 2.5rem;" data-i18n="home.cta.text">Your support enables us to provide legal 324 assistance, publish critical research, and advocate for the rights of scholars everywhere.</p> 325 <div class="flex gap-1 justify-center"> 326 <a href="get-involved.html" class="btn btn-primary" 327 style="background-color: var(--color-accent); color: var(--color-primary);" data-i18n="home.cta.join">Join ACAF</a> 328 <a href="donate.html" class="btn btn-light" data-i18n="home.cta.donate">Make a Donation</a> 329 </div> 330 </div> 331 </section> 332 333 <!-- Latest Insights popup --> 334 <div class="hi-overlay" id="hiModal" onclick="closeInsight(event)" data-i18n-dynamic> 335 <div class="hi-box" id="hiModalContent"></div> 336 </div> 337 338 <!-- Admin-controlled featured popup (assets/js/acaf-data.js -> home_popup) --> 339 <div class="home-popup-overlay" id="homePopup" aria-hidden="true"> 340 <div class="home-popup-card" role="dialog" aria-modal="true" aria-labelledby="homePopupTitle"> 341 <button type="button" class="home-popup-close" id="homePopupClose" aria-label="Close"> 342 <i class="fas fa-times"></i> 343 </button> 344 <div id="homePopupMedia"></div> 345 <div class="home-popup-body"> 346 <span class="home-popup-badge" id="homePopupBadge"></span> 347 <h3 class="home-popup-title" id="homePopupTitle"></h3> 348 <p class="home-popup-desc" id="homePopupDesc"></p> 349 <a class="home-popup-cta" id="homePopupCta" href="#"> 350 <span id="homePopupCtaText">View</span> <i class="fas fa-arrow-right" style="margin-left:.4rem;"></i> 351 </a> 352 </div> 353 </div> 354 </div> 355 356 <!-- Footer --> 357 <footer> 358 <div class="container"> 359 <div class="footer-grid"> 360 <div class="footer-brand"> 361 <div class="footer-brand-header"> 362 <img src="assets/images/ACAF_logo.png" alt="ACAF Logo">
363 <span>ACAF</span> 364 </div> 365 <p class="footer-full-name" data-i18n="footer.org-name">Africa Coalition for Academic Freedom</p> 366 <p data-i18n="footer.tagline">Defending academic freedom to advance democracy, science and sustainable development in Africa</p> 367 </div> 368 <div class="footer-col"> 369 <h4 data-i18n="footer.quick-links">Quick Links</h4> 370 <ul class="footer-links"> 371 <li><a href="about.html" data-i18n="nav.about">About Us</a></li> 372 <li><a href="contact.html" data-i18n="nav.contact">Contact</a></li> 373 </ul> 374 </div> 375 <div class="footer-col"> 376 <h4 data-i18n="footer.contact">Contact</h4> 377 <ul class="footer-links"> 378 <li><a href="mailto:[email protected]">[email protected]</a></li> 379 <li>CDD â Ghana<br>No. 95 Nortei Ababio Loop<br>North Airport Residential Area</li> 380 <li>+233(0) 25 612 1111</li> 381 </ul> 382 </div> 383 <div class="footer-col"> 384 <h4 data-i18n="footer.stay-informed">Stay Informed</h4> 385 <p style="font-size: 0.875rem; color: #94A3B8; margin-bottom: 1rem;" data-i18n="footer.newsletter-text">Subscribe to our periodic 386 newsletter.</p> 387 <form class="newsletter-form"> 388 <input type="email" placeholder="Your email" data-i18n-placeholder="footer.email-placeholder"> 389 <button type="submit" class="btn btn-primary btn-sm" 390 style="background-color: var(--color-accent); border:none; color: var(--color-primary);" data-i18n="footer.go">Go</button> 391 </form> 392 <div style="margin-top: 1.5rem;"> 393 <div class="flex gap-1" style="font-size: 1.2rem;"> 394 <a href="https://web.facebook.com/people/Africa-Coalition-for-Academic-Freedom-ACAF/61567221721200/" 395 target="_blank" style="color: #94A3B8;"><i class="fab fa-facebook"></i></a> 396 <a href="https://x.com/acafafrica" target="_blank" style="color: #94A3B8;"><i 397 class="fab fa-x-twitter"></i></a> 398 <a href="https://www.linkedin.com/company/africa-coalition-for-academic-freedom/" 399 target="_blank" style="color: #94A3B8;"><i class="fab fa-linkedin"></i></a> 400 <a href="https://www.youtube.com/@acafafrica" target="_blank" style="color: #94A3B8;"><i 401 class="fab fa-youtube"></i></a> 402 </div> 403 </div> 404 </div> 405 </div> 406 <div class="footer-bottom"> 407 <p data-i18n="footer.copyright">© 2026 Africa Coalition For Academic Freedom. All rights reserved.</p> 408 </div> 409 </div> 410 </footer> 411 412
412<script src="assets/lang/translations.js"></script>
412 413
413<script src="assets/js/i18n-dynamic.js?v=1"></script>
413 414
414<script src="assets/js/i18n.js?v=2"></script>
414 415
415<script src="assets/js/main.js"></script>
415 416
416<script src="assets/js/analytics.js?v=1" defer></script>
416 417
417<script src="assets/js/acaf-data.js?v=13"></script>
417 418
418<script src="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/js/splide.min.js"></script>
418 419
419<script> 420 (function () { 421 'use strict'; 422 423 function badgeClass(category) { 424 if (!category) return 'hero-badge-default'; 425 const k = category.toLowerCase().replace(/\s+/g, '-'); 426 if (k === 'news') return 'hero-badge-news'; 427 if (k === 'event') return 'hero-badge-event'; 428 if (k === 'press-release') return 'hero-badge-press-release'; 429 if (k === 'blog') return 'hero-badge-blog'; 430 return 'hero-badge-default'; 431 } 432 433 function formatDate(str) { 434 if (!str) return ''; 435 try { 436 return new Date(str).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); 437 } catch (e) { return ''; } 438 } 439 440 function truncate(str, max) { 441 if (!str) return ''; 442 return str.length > max ? str.slice(0, max).trimEnd() + 'â¦' : str; 443 } 444 445 function escHtml(str) { 446 if (!str) return ''; 447 return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); 448 } 449 450 function buildSlide(post) { 451 const url = 'news-post?id=' + encodeURIComponent(post.id); 452 const date = formatDate(post.publishedAt); 453 const title = truncate(post.title || '', 80); 454 const badge = badgeClass(post.category); 455 456 const imgHtml = post.imageUrl 457 ? `<img src="${escHtml(post.imageUrl)}" alt="${escHtml(post.title)}" loading="lazy">` 458 : `<i class="fas fa-newspaper hero-slide-img-icon" aria-hidden="true"></i>`; 459 460 return ` 461 <li class="splide__slide"> 462 <a class="hero-slide-card" href="${escHtml(url)}" aria-label="${escHtml(post.title)}"> 463 <div class="hero-slide-img">${imgHtml}</div> 464 <div class="hero-slide-body"> 465 <div class="hero-slide-meta"> 466 <span class="hero-slide-badge ${badge}">${escHtml(post.category || 'Post')}</span> 467 ${date ? `<span class="hero-slide-date">${escHtml(date)}</span>` : ''} 468 </div> 469 <h3 class="hero-slide-title">${escHtml(title)}</h3> 470 <span class="hero-slide-readmore" tabindex="-1" aria-hidden="true"> 471 Read More <i class="fas fa-arrow-right"></i> 472 </span> 473 </div> 474 </a> 475 </li>`; 476 } 477 478 // "Latest Insights" cards are mixed content chosen by the admin 479 // (assets/js/acaf-data.js â getHomeInsights). Each card links directly to 480 // its own detail view (full post, publication modal, incident, gallery 481 // popup, or newsletter download). 482 function buildInsightCard(item) { 483 const url = item.href || '#'; 484 const target = item.external ? ' target="_blank" rel="noopener noreferrer"' : ''; 485 const date = formatDate(item.date); 486 const meta = [item.label, date].filter(Boolean).join(' ⢠'); 487 const media = item.imageUrl 488 ? `<a href="${escHtml(url)}"${target} style="display:block;height:200px;margin:-2rem -2rem 1.5rem -2rem;border-radius:8px 8px 0 0;overflow:hidden;"><img src="${escHtml(item.imageUrl)}" alt="" loading="lazy" style="width:100%;height:100%;object-fit:cover;"></a>` 489 : `<a href="${escHtml(url)}"${target} style="height:200px;background:linear-gradient(135deg,#f0f4ff 0%,#e8f0fe 100%);margin:-2rem -2rem 1.5rem -2rem;border-radius:8px 8px 0 0;display:flex;align-items:center;justify-content:center;"><i class="fas ${item.icon || 'fa-file-alt'}" style="font-size:2.5rem;color:var(--color-primary);"></i></a>`; 490 return ` 491 <article class="card post-preview" data-insight data-type="${escHtml(item.type)}" data-id="${escHtml(item.id)}" style="cursor:pointer;"> 492 ${media} 493 <div class="post-meta">${escHtml(meta)}</div> 494 <h3 class="post-title"><a href="${escHtml(url)}"${target}>${escHtml(item.title || '')}</a></h3> 495 <p>
495${escHtml(truncate(item.description || '', 140))}</p> 496 <a href="${escHtml(url)}"${target} class="read-more">Read More <i class="fas fa-arrow-right"></i></a> 497 </article>`; 498 } 499 500 function renderInsights(items) { 501 const gridEl = document.getElementById('insightsGrid'); 502 const loadingEl = document.getElementById('insightsLoading'); 503 const emptyEl = document.getElementById('insightsEmpty'); 504 if (!gridEl) return; 505 if (loadingEl) loadingEl.remove(); 506 if (!items.length) { 507 gridEl.style.display = 'none'; 508 if (emptyEl) emptyEl.style.display = ''; 509 return; 510 } 511 gridEl.innerHTML = items.slice(0, 3).map(buildInsightCard).join(''); 512 513 // Open the clicked card in a popup ON the homepage instead of 514 // navigating away. The card's links remain as a no-JS fallback, and 515 // modifier-clicks still open the detail page in a new tab. 516 if (!gridEl.dataset.insightBound) { 517 gridEl.dataset.insightBound = '1'; 518 gridEl.addEventListener('click', (e) => { 519 if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return; 520 const card = e.target.closest('[data-insight]'); 521 if (!card) return; 522 e.preventDefault(); 523 openInsight(card.dataset.type, card.dataset.id); 524 }); 525 } 526 } 527 528 // ââ Homepage "Latest Insights" popup ââââââââââââââââââââââââââââââââââ 529 function paragraphs(text) { 530 const parts = String(text || '').split(/\n\n+/).map(p => p.trim()).filter(Boolean); 531 if (!parts.length) return escHtml(text || ''); 532 return parts.map(p => `<p>${escHtml(p).replace(/\n/g, '<br>')}</p>`).join(''); 533 } 534 535 function linkBtns(links) { 536 if (!links || !links.length) return ''; 537 return `<div class="hi-section-title">Related Links</div> 538 <div class="hi-links">${links.map(l => { 539 const u = ACAF.sanitizeUrl(l.url); 540 return `<a class="hi-link-btn" href="${escHtml(u)}" target="_blank" rel="noopener noreferrer"><i class="fas fa-external-link-alt"></i> ${escHtml(l.title || u)}</a>`; 541 }).join('')}</div>`; 542 } 543 544 // Returns { cover, badge, date, title, body, footer } for the open modal. 545 function renderInsightDetail(type, rec) { 546 const close = `<button class="btn btn-outline btn-sm" onclick="closeInsightDirect()">Close</button>`; 547 const coverImg = (src) => src 548 ? `<div class="hi-cover"><img src="${escHtml(src)}" alt=""></div>` : ''; 549 550 if (type === 'news') { 551 const links = (Array.isArray(rec.links) && rec.links.length) 552 ? rec.links.filter(l => l && l.url) 553 : (rec.linkUrl ? [{ title: 'View Full Article', url: rec.linkUrl }] : []); 554 return { 555 cover: coverImg(rec.imageUrl), badge: rec.category || 'News & Events', 556 date: formatDate(rec.publishedAt), title: rec.title, 557 body: `<div class="hi-desc">${paragraphs(rec.content || rec.summary)}</div>${linkBtns(links)}`, 558 footer: `<a class="btn btn-outline btn-sm" href="news-post?id=${encodeURIComponent(rec.id)}"><i class="fas fa-external-link-alt" style="margin-right:.35rem;"></i>Open full page</a>${close}`, 559 }; 560 } 561 562 if (type === 'publications') { 563 if (rec.type === 'video') { 564 const v = ACAF.resolveVideoEmbed(rec); 565 let embed = ''; 566 let watch = ''; 567 if (v.kind === 'iframe') { 568 embed = `<div class="hi-video is-${escHtml(v.layout)}"><iframe src="${escHtml(v.src)}" allowfullscreen allow="autoplay; encrypted-media;
568 picture-in-picture" title="${escHtml(rec.title || 'Video')}"></iframe></div>`; 569 } else if (v.kind === 'file') { 570 embed = `<div class="hi-video is-${escHtml(v.layout)}"><video src="${escHtml(v.src)}" controls preload="metadata"${v.thumbnail ? ` poster="${escHtml(v.thumbnail)}"` : ''}></video></div>`; 571 } else if (v.kind === 'link') { 572 // Platform blocks embedding â send viewers to the source. 573 embed = v.thumbnail ? coverImg(v.thumbnail) : ''; 574 watch = `<a class="btn btn-primary btn-sm" style="background:var(--color-primary);border:none;color:#fff;" href="${escHtml(v.url)}" target="_blank" rel="noopener noreferrer"><i class="${escHtml(v.icon)}" style="margin-right:.35rem;"></i>${escHtml(v.cta)}</a>`; 575 } 576 return { 577 cover: '', badge: 'Video', date: formatDate(rec.publishedAt), title: rec.title, 578 body: `${embed}${rec.description ? `<div class="hi-desc" style="margin-top:1rem;">${escHtml(rec.description)}</div>` : ''}`, 579 footer: `${watch}${close}`, 580 }; 581 } 582 const docs = ACAF.getDocsList(rec.documents); 583 let dl = ''; 584 if (docs.length === 1) { 585 dl = `<a class="btn btn-primary btn-sm" style="background:var(--color-primary);border:none;color:#fff;" href="${escHtml(docs[0].url)}" target="_blank" rel="noopener"><i class="fas fa-download" style="margin-right:.35rem;"></i>Download</a>`; 586 } else if (docs.length > 1) { 587 dl = docs.map(d => `<a class="btn btn-outline btn-sm" href="${escHtml(d.url)}" target="_blank" rel="noopener"><i class="fas fa-file-alt" style="margin-right:.35rem;"></i>${escHtml(d.label)}</a>`).join(''); 588 } 589 const author = rec.author ? `<p class="notranslate" style="font-size:.85rem;color:var(--color-text-light);margin:0 0 .75rem;"><i class="fas fa-user" style="margin-right:.35rem;"></i>${escHtml(rec.author)}</p>` : ''; 590 return { 591 cover: coverImg(rec.thumbnailUrl), badge: 'Publication', date: formatDate(rec.publishedAt), title: rec.title, 592 body: `${author}<div class="hi-desc">${escHtml(rec.description || 'No description provided.')}</div>`, 593 footer: `${dl}${close}`, 594 }; 595 } 596 597 if (type === 'incidents') { 598 const meta = [ 599 ['Date', formatDate(rec.date)], ['Country', rec.country], 600 ['Institution', rec.institution], ['City / Location', rec.city], 601 ].filter(m => m[1]).map(m =>
601 `<div class="hi-meta-item"><label>${m[0]}</label><span>${escHtml(m[1])}</span></div>`).join(''); 602 const types = (rec.incidentTypes || []).map(t => `<span class="hi-tag">${escHtml(t)}</span>`).join(''); 603 const sources = (rec.sources || []).filter(s => s && s.url).map(s => 604 `<li><a class="hi-link-btn" href="${escHtml(ACAF.sanitizeUrl(s.url))}" target="_blank" rel="noopener"><i class="fas fa-external-link-alt"></i> ${escHtml(s.title || s.url)}</a></li>`).join(''); 605 return { 606 cover: '', badge: 'Incident', date: formatDate(rec.date), title: rec.title, 607 body: `${meta ? `<div class="hi-meta-grid">${meta}</div>` : ''} 608 ${types ? `<div class="hi-section-title">Type of Incident</div><div class="hi-tags">${types}</div>` : ''} 609 <div class="hi-section-title">Description</div> 610 <div class="hi-desc">${rec.description || 'No description provided.'}</div> 611 ${sources ? `<div class="hi-section-title">Sources</div><ul class="hi-sources">${sources}</ul>` : ''}`, 612 footer: `<a class="btn btn-outline btn-sm" href="monitoring.html#incident=${encodeURIComponent(rec.id)}"><i class="fas fa-external-link-alt" style="margin-right:.35rem;"></i>Open in Monitoring</a>${close}`, 613 }; 614 } 615 616 if (type === 'newsletter') { 617 const docs = ACAF.getDocsList(rec.documents); 618 let pdf = ''; 619 if (docs.length === 1) { 620 pdf = `<a class="btn btn-primary btn-sm" style="background:var(--color-primary);border:none;color:#fff;" href="${escHtml(docs[0].url)}" target="_blank" rel="noopener"><i class="fas fa-file-pdf" style="margin-right:.35rem;"></i>View PDF</a>`; 621 } else if (docs.length > 1) { 622 pdf = docs.map(d => `<a class="btn btn-outline btn-sm" href="${escHtml(d.url)}" target="_blank" rel="noopener"><i class="fas fa-file-pdf" style="margin-right:.35rem;"></i>${escHtml(d.label)}</a>`).join(''); 623 } 624 const readMore = rec.link_url ? `<a class="btn btn-outline btn-sm" href="${escHtml(ACAF.sanitizeUrl(rec.link_url))}" target="_blank" rel="noopener">Read More</a>` : ''; 625 return { 626 cover: coverImg(rec.image_url), badge: 'Newsletter', date: formatDate(rec.created_at), title: rec.title, 627 body: `<div class="hi-desc">${escHtml(rec.description || 'No description provided.')}</div>`, 628 footer: `${readMore}${pdf}${close}`, 629 }; 630 } 631 632 if (type === 'gallery') { 633 const album = rec.albumUrl ? ACAF.sanitizeUrl(rec.albumUrl) : ''; 634 const view = album 635 ? `<a class="btn btn-primary btn-sm" style="background:var(--color-primary);border:none;color:#fff;" href="${escHtml(album)}" target="_blank" rel="noopener noreferrer"><i class="fas fa-images" style="margin-right:.35rem;"></i>View Photos</a>` : ''; 636 return { 637 cover: coverImg(rec.coverImageUrl), badge: 'Gallery', date: formatDate(rec.eventDate), title: rec.title, 638 body: `<div class="hi-desc">${escHtml(rec.description || 'No description provided.')}</div>`, 639 footer: `${view}${close}`, 640 }; 641 } 642 return null; 643 } 644 645 async function openInsight(type, id) { 646 const box = document.getElementById('hiModalContent'); 647 const overlay = document.getElementById('hiModal'); 648 box.innerHTML = `<div style="padding:3rem;text-align:center;color:var(--color-text-light);"><i class="fas fa-spinner fa-spin" style="font-size:1.5rem;"></i></div>`; 649 overlay.classList.add('open'); 650 document.body.style.overflow = 'hidden'; 651 652 let rec = null; 653 try { 654 if (type === 'news') rec = await ACAF.getNewsEventById(id); 655 else if (type === 'publications') rec = await ACAF.getPublicationById(id); 656 else if (type === 'incidents') rec = await ACAF.getIncidentById(id); 657 else if (type === 'newsletter') rec = await ACAF.getNewsletterIssueById(id); 658 else if (type === 'gallery') rec = await ACAF.getGalleryItemById(id); 659 } catch (e) { rec = null; } 660 661 const d = rec ? renderInsightDetail(type, rec) : null; 662 if (!d) { 663 box.innerHTML = `<div class="hi-body" style="text-align:center;"><p style="color:var(--color-text-light);">Sorry, this item could not be loaded.</p><div class="hi-footer" style="justify-content:center;"><button class="btn btn-outline btn-sm" onclick="closeInsightDirect()">Close</button></div></div>`; 664 return; 665 } 666 const dateLine = d.date ? `<span class="hi-date">${escHtml(d.date)}</span>` : ''; 667 box.innerHTML = ` 668 ${d.cover} 669 <button class="hi-close" onclick="closeInsightDirect()" aria-label="Close"><i class="fas fa-times"></i></button> 670 <div class="hi-body"> 671 <div class="hi-meta-line"><span class="hi-badge">${escHtml(d.badge)}</span>${dateLine}</div> 672 <h2 class="hi-title">${escHtml(d.title || '')}</h2> 673 ${d.body}
673 674 </div> 675 <div class="hi-footer">${d.footer}</div>`; 676 // Keep dynamic translation in sync with the chosen language. 677 if (window.ACAF_DYN && typeof window.ACAF_DYN.translateRoot === 'function') { 678 window.ACAF_DYN.translateRoot(overlay); 679 } 680 } 681 682 function closeInsight(e) { 683 if (e.target === document.getElementById('hiModal')) closeInsightDirect(); 684 } 685 function closeInsightDirect() { 686 document.getElementById('hiModal').classList.remove('open'); 687 document.body.style.overflow = ''; 688 } 689 document.addEventListener('keydown', e => { if (e.key === 'Escape') closeInsightDirect(); }); 690 window.openInsight = openInsight; 691 window.closeInsight = closeInsight; 692 window.closeInsightDirect = closeInsightDirect; 693 694 async function initHomeContent() { 695 // Latest Insights â admin-configured mix of content types (or the 696 // latest publications by default). 697 let insights = []; 698 try { 699 insights = await ACAF.getHomeInsights(3); 700 } catch (e) { insights = []; } 701 renderInsights(insights); 702 703 // Hero carousel â latest news & events. 704 let posts = []; 705 try { 706 const result = await ACAF.getNewsEvents({ publishedOnly: true, perPage: 10 }); 707 posts = result && result.items ? result.items : []; 708 } catch (e) { posts = []; } 709 710 if (!posts.length) return; // hero stays in full-width centered mode 711 712 const listEl = document.getElementById('heroSlidesList'); 713 const splideEl = document.getElementById('heroSplide'); 714 const skeletonEl = document.getElementById('heroSkeleton'); 715 const wrapEl = document.getElementById('heroCarouselWrap'); 716 const textEl = document.getElementById('heroText'); 717 718 listEl.innerHTML = posts.map(buildSlide).join(''); 719 720 // Reveal: switch to two-column layout 721 textEl.classList.remove('hero-full'); 722 wrapEl.style.display = ''; 723 wrapEl.removeAttribute('aria-hidden'); 724 skeletonEl.style.display = 'none'; 725 splideEl.style.display = ''; 726 727 const splide = new Splide('#heroSplide', { 728 type: 'loop', 729 perPage: 1, 730 gap: 0, 731 autoplay: true, 732 interval: 6000, 733 pauseOnHover: true, 734 arrows: true, 735 pagination: true, 736 speed: 550, 737 easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', 738 i18n: { 739 prev: 'Previous post', 740 next: 'Next post', 741 first: 'First post', 742 last: 'Last post', 743 slideX: 'Post %s', 744 pageX: 'Go to page %s', 745 play: 'Start autoplay', 746 pause: 'Pause autoplay', 747 }, 748 }); 749 splide.mount(); 750 } 751 752 // ââ Admin-controlled featured popup âââââââââââââââââââââââââââââââââââ 753 const POPUP_DELAY_MS = 2000; 754 const POPUP_SESSION_KEY = 'acaf_home_popup_dismissed'; 755 756 function dismissKey(cfg) { return (cfg.type || '') + ':' + (cfg.id || ''); } 757 function alreadyDismissed(cfg) { 758 try { return sessionStorage.getItem(POPUP_SESSION_KEY) === dismissKey(cfg); } 759 catch (e) { return false; } // private mode: just show it 760 } 761 function markDismissed(cfg) { 762 try { sessionStorage.setItem(POPUP_SESSION_KEY, dismissKey(cfg)); } catch (e) {} 763 } 764 765 function closeHomePopup(cfg) { 766 const ov = document.getElementById('homePopup'); 767 ov.classList.remove('open'); 768 ov.setAttribute('aria-hidden', 'true'); 769 document.body.style.overflow = ''; 770 if (cfg) markDismissed(cfg); 771 } 772 773 async function initHomePopup() { 774 let cfg;
775 try { cfg = await ACAF.getHomePopupConfig(); } catch (e) { return; } 776 if (!ACAF.isPopupActive(cfg)) return; // disabled or out of window 777 if (alreadyDismissed(cfg)) return; // seen this session 778 779 const item = await ACAF.getPopupItem(cfg.type, cfg.id); 780 if (!item) return; // content unpublished/removed 781 782 document.getElementById('homePopupBadge').textContent = item.label || ''; 783 document.getElementById('homePopupTitle').textContent = item.title || ''; 784 document.getElementById('homePopupDesc').textContent = item.description || ''; 785 786 const media = document.getElementById('homePopupMedia'); 787 media.innerHTML = item.imageUrl 788 ? `<img class="home-popup-media" src="${escHtml(item.imageUrl)}" alt="">` 789 : `<div class="home-popup-media-fallback"><i class="fas ${escHtml(item.icon || 'fa-star')}"></i></div>`; 790 791 const cta = document.getElementById('homePopupCta'); 792 cta.setAttribute('href', item.href); 793 if (item.external) { cta.setAttribute('target', '_blank'); cta.setAttribute('rel', 'noopener noreferrer'); } 794 else { cta.removeAttribute('target'); cta.removeAttribute('rel'); } 795 document.getElementById('homePopupCtaText').textContent = 796 item.type === 'publications' ? 'View publication' 797 : item.type === 'news' ? 'Read more' 798 : item.type === 'gallery' ? 'View gallery' 799 : item.type === 'newsletter' ? 'Open newsletter' 800 : item.type === 'incidents' ? 'View details' : 'View'; 801 // Following the CTA counts as handled â do not nag again this session. 802 cta.addEventListener('click', () => markDismissed(cfg), { once: true }); 803 804 const ov = document.getElementById('homePopup'); 805 const card = ov.querySelector('.home-popup-card'); 806 ov.addEventListener('click', e => { if (e.target === ov) closeHomePopup(cfg); }); 807 document.getElementById('homePopupClose').addEventListener('click', () => closeHomePopup(cfg)); 808 document.addEventListener('keydown', e => { 809 if (e.key === 'Escape' && ov.classList.contains('open')) closeHomePopup(cfg); 810 }); 811 812 setTimeout(() => { 813 ov.classList.add('open'); 814 ov.setAttribute('aria-hidden', 'false'); 815 }, POPUP_DELAY_MS); 816 } 817 818 document.addEventListener('DOMContentLoaded', initHomeContent); 819 document.addEventListener('DOMContentLoaded', initHomePopup); 820 })(); 821 </script>
821 822</body> 823 824</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.