1/* ================================================================ 2 LARRY WHITESIDE JR. â script.js 3 ================================================================ 4 RENDERING ENGINE ONLY. Do not edit this file for content updates. 5 All site content lives in content.js. 6 ================================================================ */ 7 8'use strict'; 9 10/* ---------------------------------------------------------------- 11 GOOGLE CALENDAR EMBED 12 ---------------------------------------------------------------- */ 13function renderCalendar() { 14 const wrap = document.getElementById('calendarEmbed'); 15 if (!wrap) return; 16 17 if (!publicCalendarUrl || !publicCalendarUrl.trim()) { 18 wrap.innerHTML = ` 19 <div class="calendar-placeholder glass-panel"> 20 <div class="calendar-placeholder__icon" aria-hidden="true">ð </div> 21 <p class="calendar-placeholder__text"> 22 Calendar coming soon â add your Google Calendar embed URL to <code>content.js</code>. 23 </p> 24 <a href="https://www.thecisosociety.com/meetups" class="btn btn--outline" target="_blank" rel="noopener noreferrer"> 25 View CISO Society Events â 26 </a> 27 </div> 28 `; 29 return; 30 } 31 32 wrap.innerHTML = ` 33 <div class="calendar-embed__frame-wrap reveal"> 34 <iframe 35 src="${publicCalendarUrl}" 36 style="border:0" 37 frameborder="0" 38 scrolling="no" 39 title="Larry Whiteside Jr. â Upcoming Events" 40 loading="lazy" 41 ></iframe> 42 </div> 43 <div class="calendar-embed__cta reveal"> 44 <a href="${publicCalendarUrl.replace('embed?', 'r/').replace('%40', '@')}" class="btn btn--outline" target="_blank" rel="noopener noreferrer"> 45 View Full Calendar â 46 </a> 47 </div> 48 `; 49 observeNew(); 50} 51 52 53/* ---------------------------------------------------------------- 54 CAREER TIMELINE 55 ---------------------------------------------------------------- */ 56function renderTimeline() { 57 const list = document.getElementById('timelineList'); 58 if (!list) return; 59 60 timeline.forEach(item => { 61 const el = document.createElement('div'); 62 el.className = 'timeline-item'; 63 el.innerHTML = ` 64 <span class="timeline-item__year">${item.year}</span> 65 <div class="timeline-item__dot" aria-hidden="true"></div> 66 <div class="timeline-item__body"> 67 <p class="timeline-item__title">${item.title}</p> 68 <p class="timeline-item__org">${item.industry}</p> 69 </div> 70 `; 71 list.appendChild(el); 72 }); 73} 74 75 76/* ---------------------------------------------------------------- 77 SPEAKING TOPICS 78 ---------------------------------------------------------------- */
79function renderSpeakingTopics() { 80 const grid = document.getElementById('speakingTopicsGrid'); 81 if (!grid) return; 82 83 speakingTopics.forEach((topic, i) => { 84 const card = document.createElement('div'); 85 card.className = 'speaking__topic-card reveal'; 86 card.style.transitionDelay = `${(i % 4) * 0.07}s`; 87 88 const takeawaysHTML = topic.takeaways 89 ? `<ul class="speaking__topic-takeaways"> 90 ${topic.takeaways.map(t => `<li>${t}</li>`).join('')} 91 </ul>` 92 : ''; 93 94 card.innerHTML = ` 95 <div class="speaking__topic-header"> 96 <div class="speaking__topic-icon" aria-hidden="true">${topic.icon}</div> 97 <h3>${topic.title}</h3> 98 </div> 99 <p>${topic.desc}</p> 100 ${takeawaysHTML} 101 `; 102 grid.appendChild(card); 103 }); 104} 105 106 107/* ---------------------------------------------------------------- 108 SPEAKING DIFFERENTIATORS 109 ---------------------------------------------------------------- */ 110function renderSpeakingDifferentiators() { 111 const grid = document.getElementById('speakingDifferentiators'); 112 if (!grid) return; 113 114 speakingDifferentiators.forEach((item, i) => { 115 const card = document.createElement('div'); 116 card.className = 'speaking__diff-card reveal'; 117 card.style.transitionDelay = `${(i % 4) * 0.07}s`; 118 card.innerHTML = ` 119 <div class="speaking__diff-icon" aria-hidden="true">${item.icon}</div> 120 <h4>${item.title}</h4> 121 <p>${item.desc}</p> 122 `; 123 grid.appendChild(card); 124 }); 125} 126 127 128/* ---------------------------------------------------------------- 129 IDEAL AUDIENCES 130 ---------------------------------------------------------------- */
131function renderSpeakingAudiences() { 132 const grid = document.getElementById('speakingAudiences'); 133 if (!grid) return; 134 135 speakingAudiences.forEach(a => { 136 const chip = document.createElement('div'); 137 chip.className = 'speaking__audience-chip'; 138 chip.innerHTML = `<span aria-hidden="true">${a.icon}</span> ${a.label}`; 139 grid.appendChild(chip); 140 }); 141} 142 143 144/* ---------------------------------------------------------------- 145 PAST VENUES 146 ---------------------------------------------------------------- */ 147function renderSpeakingVenues() { 148 const grid = document.getElementById('speakingVenues'); 149 if (!grid) return; 150 151 speakingVenues.forEach((v, i) => { 152 const card = document.createElement('div'); 153 card.className = 'speaking__venue-card reveal'; 154 card.style.transitionDelay = `${(i % 4) * 0.06}s`; 155 card.innerHTML = ` 156 <p class="speaking__venue-name">${v.name}</p> 157 <p class="speaking__venue-type">${v.type}</p> 158 `; 159 grid.appendChild(card); 160 }); 161} 162 163 164/* ---------------------------------------------------------------- 165 SPEAKING FORMATS 166 ---------------------------------------------------------------- */
167function renderSpeakingFormats() { 168 const grid = document.getElementById('speakingFormats'); 169 if (!grid) return; 170 171 speakingFormats.forEach((f, i) => { 172 const card = document.createElement('div'); 173 card.className = 'speaking__format-card reveal'; 174 card.style.transitionDelay = `${(i % 3) * 0.07}s`; 175 card.innerHTML = ` 176 <div class="speaking__format-icon" aria-hidden="true">${f.icon}</div> 177 <div> 178 <p class="speaking__format-label">${f.label}</p> 179 <p class="speaking__format-desc">${f.desc}</p> 180 </div> 181 `; 182 grid.appendChild(card); 183 }); 184} 185 186 187/* ---------------------------------------------------------------- 188 FEATURED ORGANIZATIONS 189 ---------------------------------------------------------------- */ 190function renderFeaturedOrgs() { 191 const grid = document.getElementById('orgsFeatured'); 192 if (!grid) return; 193 194 featuredOrgs.forEach((org, i) => { 195 const card = document.createElement(org.url ? 'a' : 'div'); 196 card.className = 'org-card reveal' + (org.url ? ' org-card--link' : ''); 197 card.style.transitionDelay = `${i * 0.07}s`; 198 if (org.url) { card.href = org.url; card.target = '_blank'; card.rel = 'noopener noreferrer'; } 199 200 const logoContent = org.logoImg 201 ? `<img src="${org.logoImg}" alt="${org.name} logo" />` 202 : org.logo; 203 204 card.innerHTML = ` 205 <div class="org-card__header"> 206 <div class="org-card__logo" aria-hidden="${!!org.logoImg}">${logoContent}</div> 207 <div> 208 <p class="org-card__role">${org.role}</p> 209 <p class="org-card__name">${org.name}</p> 210 </div> 211 </div> 212 <p class="org-card__desc">${org.desc}</p> 213 ${org.url ? `<span class="org-card__link">Visit ${org.name} â</span>` : ''} 214 `; 215 grid.appendChild(card); 216 }); 217} 218 219 220/* ---------------------------------------------------------------- 221 PARTNER MARQUEE 222 ---------------------------------------------------------------- */ 223function renderPartners() { 224 const track = document.getElementById('partnersTrack'); 225 if (!track) return; 226 227 [...partners, ...partners].forEach(p => { 228 const el = document.createElement('div'); 229 el.className = 'partner-item'; 230 el.innerHTML = `<div class="partner-item__logo" aria-hidden="true">${p.logo}</div>${p.name}`; 231 track.appendChild(el); 232 }); 233} 234 235 236/* ----------------------------------------------------------------
237 SOCIAL MEDIA GRID 238 ---------------------------------------------------------------- */ 239function renderSocialLinks() { 240 const grid = document.getElementById('socialGrid'); 241 const footerEl = document.getElementById('footerSocial'); 242 243 if (grid) { 244 socialLinks.filter(s => s.url).forEach(social => { 245 const card = document.createElement('a'); 246 card.className = 'social-card reveal'; 247 card.href = social.url; 248 card.target = '_blank'; 249 card.rel = 'noopener noreferrer'; 250 card.setAttribute('aria-label', `Follow on ${social.platform}`); 251 252 const isGradient = social.color.includes('gradient'); 253 card.innerHTML = ` 254 <div class="social-card__icon" style="background:${isGradient ? social.color : social.color + '22'}" aria-hidden="true">${social.icon}</div> 255 <span class="social-card__platform">${social.platform}</span> 256 <span class="social-card__handle">${social.handle}</span> 257 <span class="social-card__follow" style="color:${isGradient ? 'var(--color-gold)' : social.color}">Follow</span> 258 `; 259 grid.appendChild(card); 260 }); 261 } 262 263 if (footerEl) { 264 socialLinks.filter(s => s.url).forEach(social => { 265 const a = document.createElement('a'); 266 a.className = 'footer-social-link'; 267 a.href = social.url; 268 a.target = '_blank'; 269 a.rel = 'noopener noreferrer'; 270 a.setAttribute('aria-label', social.platform); 271 a.textContent = social.icon; 272 footerEl.appendChild(a); 273 }); 274 } 275} 276 277 278/* ---------------------------------------------------------------- 279 LINKEDIN FEATURED POST 280 ---------------------------------------------------------------- */ 281function renderLinkedInPost() { 282 const wrap = document.getElementById('linkedInPostWrap'); 283 if (!wrap) return; 284 285 if (!linkedInPost.embedUrl || !linkedInPost.embedUrl.trim()) { 286 wrap.style.display = 'none'; 287 return; 288 } 289 290 wrap.style.display = ''; 291 wrap.innerHTML = ` 292 <div class="glass-panel linkedin-embed-panel reveal"> 293 <div class="linkedin-embed-panel__header"> 294 <div class="linkedin-embed-panel__logo" aria-hidden="true">in</div> 295 <div> 296 <p class="linkedin-embed-panel__label">Latest on LinkedIn</p> 297 <p class="linkedin-embed-panel__name">Larry Whiteside Jr.</p> 298 </div> 299 </div> 300 <div class="linkedin-embed-panel__frame-wrap"> 301 <iframe 302 src="${linkedInPost.embedUrl}" 303 class="linkedin-embed-panel__iframe" 304 frameborder="0" 305 allowfullscreen="" 306 title="Latest LinkedIn post by Larry Whiteside Jr." 307 loading="lazy" 308 ></iframe> 309 </div> 310 ${linkedInPost.caption ? `<p class="linkedin-embed-panel__caption">${linkedInPost.caption}</p>` : ''} 311 <a 312 href="https://www.linkedin.com/in/larrywhitesidejr" 313 class="btn btn--outline btn--sm linkedin-embed-panel__follow" 314 target="_blank" 315 rel="noopener noreferrer" 316 > 317 Follow on LinkedIn â 318 </a> 319 </div> 320 `; 321 observeNew(); 322} 323 324 325/* ---------------------------------------------------------------- 326 FEATURED CONTENT 327 ---------------------------------------------------------------- 328 Auto-detects YouTube links and opens them in an inline player. 329 All other links open in a new tab. 330 ---------------------------------------------------------------- */ 331function extractYouTubeId(url) { 332 if (!url) return null; 333 const patterns = [ 334 /youtube\.com\/watch\?v=([^&]+)/, 335 /youtu\.be\/([^?]+)/, 336 /youtube\.com\/embed\/([^?]+)/ 337 ]; 338 for (const re of patterns) { 339 const m = url.match(re); 340 if (m) return m[1]; 341 } 342 return null; 343} 344 345function renderFeaturedContent() { 346 const grid = document.getElementById('contentGrid'); 347 if (!grid) return; 348 349 grid.innerHTML = ''; 350 351 featuredContent.forEach((item, i) => { 352 const card = document.createElement('article'); 353 card.className = 'content-card reveal'; 354 card.style.transitionDelay = `${i * 0.06}s`; 355 356 const youtubeId = extractYouTubeId(item.link); 357 const hasThumb = item.image && item.image.trim(); 358 359 const thumbHTML = youtubeId 360 ? `<img src="https://img.youtube.com/vi/${youtubeId}/hqdefault.jpg" alt="${item.title}" loading="lazy" />` 361 : hasThumb 362 ? `<img src="${item.image}" alt="${item.title}" loading="lazy" />` 363 : `<div class="content-card__thumb-placeholder">ð</div>`; 364 365 const showPlay = !!youtubeId; 366 367 card.innerHTML = ` 368 <div class="content-card__thumb"> 369 ${thumbHTML} 370 ${showPlay ? `<div class="content-card__play" aria-hidden="true">â¶</div>` : ''} 371 </div> 372 <div class="content-card__body"> 373 <p class="content-card__title">${item.title}</p> 374 ${item.description ? `<p class="content-card__meta">${item.description}</p>` : ''} 375 </div> 376 `; 377 378 card.style.cursor = item.link ? 'pointer' : 'default'; 379 380 if (youtubeId) { 381 card.addEventListener('click', () => openVideoLightbox(youtubeId, item.title)); 382 } else if (item.link) { 383 card.addEventListener('click', () => window.open(item.link, '_blank', 'noopener')); 384 } 385 386 grid.appendChild(card); 387 }); 388 389 observeNew(); 390} 391 392 393/* ---------------------------------------------------------------- 394 YOUTUBE VIDEO LIGHTBOX 395 ---------------------------------------------------------------- */ 396function openVideoLightbox(youtubeId, title) { 397 const lb = document.getElementById('lightbox'); 398 const wrap = document.getElementById('lightboxImg'); 399 const cap = document.getElementById('lightboxCaption'); 400 const prev = document.getElementById('lightboxPrev'); 401 const next = document.getElementById('lightboxNext'); 402 if (!lb) return; 403 404 if (prev) prev.style.display = 'none'; 405 if (next) next.style.display = 'none'; 406 407 let iframe = lb.querySelector('.video-lightbox__iframe'); 408 if (!iframe) { 409 iframe = document.createElement('iframe'); 410 iframe.className = 'video-lightbox__iframe'; 411 iframe.setAttribute('allowfullscreen', ''); 412 iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'); 413 iframe.setAttribute('title', title || 'YouTube video'); 414 if (wrap) wrap.style.display = 'none'; 415 lb.querySelector('.lightbox__img-wrap').appendChild(iframe); 416 } 417 418 iframe.src = `https://www.youtube.com/embed/${youtubeId}?autoplay=1&rel=0`; 419 if (cap) cap.textContent = title || ''; 420 421 lb.classList.remove('hidden'); 422 lb.dataset.mode = 'video'; 423 document.body.style.overflow = 'hidden'; 424} 425 426function closeVideoLightbox() { 427 const lb = document.getElementById('lightbox'); 428 const wrap = document.getElementById('lightboxImg'); 429 const iframe = lb ? lb.querySelector('.video-lightbox__iframe') : null; 430 const prev = document.getElementById('lightboxPrev'); 431 const next = document.getElementById('lightboxNext'); 432 433 if (iframe) { iframe.src = ''; iframe.remove(); } 434 if (wrap) wrap.style.display = ''; 435 if (prev) prev.style.display = ''; 436 if (next) next.style.display = ''; 437 if (lb) { lb.classList.add('hidden'); delete lb.dataset.mode; } 438 document.body.style.overflow = ''; 439} 440 441 442/* ---------------------------------------------------------------- 443 CONTACT LINKS 444 ---------------------------------------------------------------- */ 445function renderContactLinks() { 446 const el = document.getElementById('contactLinks'); 447 if (!el) return; 448 449 contactInfo.forEach(item => { 450 const link = document.createElement('a'); 451 link.className = 'contact-link'; 452 link.href = item.href; 453 if (item.href.startsWith('http')) { link.target = '_blank'; link.rel = 'noopener noreferrer'; } 454 link.innerHTML = ` 455 <div class="contact-link__icon" aria-hidden="true">${item.icon}</div> 456 <div>
457 <span class="contact-link__label">${item.label}</span> 458 <span class="contact-link__value">${item.value}</span> 459 </div> 460 `; 461 el.appendChild(link); 462 }); 463} 464 465 466/* ---------------------------------------------------------------- 467 CONTACT FORM 468 ---------------------------------------------------------------- */ 469function initContactForm() { 470 const form = document.getElementById('contactForm'); 471 const success = document.getElementById('formSuccess'); 472 if (!form) return; 473 474 form.addEventListener('submit', e => { 475 e.preventDefault(); 476 477 const name = form.querySelector('#contactName'); 478 const email = form.querySelector('#contactEmail'); 479 const message = form.querySelector('#contactMessage'); 480 let valid = true; 481 482 [name, email, message].forEach(field => { 483 field.style.borderColor = ''; 484 if (!field.value.trim()) { field.style.borderColor = '#e05555'; valid = false; } 485 }); 486 487 if (email && email.value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) { 488 email.style.borderColor = '#e05555'; valid = false; 489 } 490 491 if (!valid) return; 492 493 /* 494 NETLIFY FORMS: 495 Once you add data-netlify="true" to the form and deploy, 496 replace this block with: 497 const data = new FormData(form); 498 fetch('/', { method: 'POST', body: data }) 499 .then(() => { form.classList.add('hidden'); success.classList.remove('hidden'); }); 500 */ 501 form.classList.add('hidden'); 502 if (success) success.classList.remove('hidden'); 503 }); 504} 505 506 507/* ---------------------------------------------------------------- 508 GALLERY â reads from gallery-manifest.js 509 ---------------------------------------------------------------- */ 510let lightboxIndex = 0; 511let visibleImages = []; 512 513function renderGallery() { 514 const grid = document.getElementById('galleryMasonry'); 515 if (!grid) return; 516 517 grid.innerHTML = ''; 518 519 const images = typeof galleryManifest !== 'undefined' ? galleryManifest : []; 520 visibleImages = images; 521 522 if (images.length === 0) { 523 grid.innerHTML = ` 524 <div class="gallery__empty glass-panel"> 525 <p>Photos coming soon â add images to <code>assets/images/gallery/</code> and update <code>gallery-manifest.js</code>.</p> 526 </div> 527 `; 528 return; 529 } 530 531 images.forEach((img, i) => { 532 const item = document.createElement('div'); 533 item.className = 'gallery__item reveal'; 534 item.style.transitionDelay = `${(i % 8) * 0.04}s`; 535 item.setAttribute('role', 'button'); 536 item.setAttribute('tabindex', '0'); 537 item.setAttribute('aria-label', img.caption || `Gallery photo ${i + 1}`); 538 539 item.innerHTML = ` 540 <img src="${img.src}" alt="${img.caption || ''}" loading="lazy" /> 541 ${img.caption ? `<span class="gallery__item-caption">${img.caption}</span>` : ''} 542 `; 543 544 item.addEventListener('click', () => openLightbox(i)); 545 item.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openLightbox(i); } }); 546 547 grid.appendChild(item); 548 }); 549 550 observeNew(); 551} 552 553 554/* ---------------------------------------------------------------- 555 LIGHTBOX 556 ---------------------------------------------------------------- */ 557function openLightbox(index) { 558 const lb = document.getElementById('lightbox'); 559 const lbImg = document.getElementById('lightboxImg'); 560 const lbCap = document.getElementById('lightboxCaption'); 561 if (!lb) return; 562 563 lightboxIndex = index; 564 const img = visibleImages[index]; 565 566 lbImg.src = img.src || ''; 567 lbImg.alt = img.caption || ''; 568 if (lbCap) lbCap.textContent = img.caption || ''; 569 lb.classList.remove('hidden'); 570 document.body.style.overflow = 'hidden'; 571} 572 573function navigateLightbox(dir) { 574 if (visibleImages.length === 0) return; 575 lightboxIndex = (lightboxIndex + dir + visibleImages.length) % visibleImages.length; 576 openLightbox(lightboxIndex); 577} 578 579function closeLightbox() { 580 const lb = document.getElementById('lightbox'); 581 if (!lb) return; 582 if (lb.dataset.mode === 'video') { 583 closeVideoLightbox(); 584 } else { 585 lb.classList.add('hidden'); 586 document.body.style.overflow = ''; 587 } 588} 589 590function initLightbox() { 591 const lb = document.getElementById('lightbox'); 592 const close = document.getElementById('lightboxClose'); 593 const prev = document.getElementById('lightboxPrev'); 594 const next = document.getElementById('lightboxNext'); 595 596 if (close) close.addEventListener('click', closeLightbox); 597 if (prev) prev.addEventListener('click', () => navigateLightbox(-1)); 598 if (next) next.addEventListener('click', () => navigateLightbox(+1)); 599 if (lb) lb.addEventListener('click', e => { if (e.target === lb) closeLightbox(); }); 600 601 document.addEventListener('keydown', e => { 602 if (!lb || lb.classList.contains('hidden')) return; 603 if (e.key === 'Escape') closeLightbox(); 604 if (e.key === 'ArrowLeft') navigateLightbox(-1); 605 if (e.key === 'ArrowRight') navigateLightbox(+1); 606 }); 607} 608 609 610/* ---------------------------------------------------------------- 611 SCROLL REVEAL 612 ---------------------------------------------------------------- */ 613function initScrollReveal() { 614 const obs = new IntersectionObserver( 615 entries =>
615 entries.forEach(entry => { 616 if (entry.isIntersecting) { entry.target.classList.add('is-visible'); obs.unobserve(entry.target); } 617 }), 618 { threshold: 0.1, rootMargin: '0px 0px -30px 0px' } 619 ); 620 document.querySelectorAll('.reveal').forEach(el => obs.observe(el)); 621} 622 623function observeNew() { 624 const obs = new IntersectionObserver( 625 entries => entries.forEach(entry => { 626 if (entry.isIntersecting) { entry.target.classList.add('is-visible'); obs.unobserve(entry.target); } 627 }), 628 { threshold: 0.08 } 629 ); 630 document.querySelectorAll('.reveal:not(.is-visible)').forEach(el => obs.observe(el)); 631} 632 633 634/* ---------------------------------------------------------------- 635 ANIMATED COUNTERS (hero stats) 636 ---------------------------------------------------------------- */ 637function animateCounter(el) { 638 const target = parseInt(el.dataset.target, 10); 639 const duration = 1800; 640 const steps = Math.ceil(duration / 16); 641 let count = 0; 642 643 const timer = setInterval(() => { 644 count++; 645 const value = Math.round(target * (count / steps)); 646 el.textContent = value >= 1000 ? (value / 1000).toFixed(value < 10000 ? 1 : 0) + 'K' : value; 647 if (count >= steps) { 648 el.textContent = target >= 1000 ? (target / 1000).toFixed(target < 10000 ? 1 : 0) + 'K' : target; 649 clearInterval(timer); 650 } 651 }, 16); 652} 653 654function initCounters() { 655 const obs = new IntersectionObserver(entries => { 656 entries.forEach(entry => { 657 if (entry.isIntersecting) { animateCounter(entry.target); obs.unobserve(entry.target); } 658 }); 659 }, { threshold: 0.5 }); 660 661 document.querySelectorAll('.hero__stat-number[data-target]').forEach(el => obs.observe(el)); 662} 663 664 665/* ---------------------------------------------------------------- 666 STICKY NAV + ACTIVE LINK HIGHLIGHTING 667 ---------------------------------------------------------------- */ 668function initNav() { 669 const nav = document.getElementById('nav'); 670 const toggle = document.getElementById('navToggle'); 671 const links = document.getElementById('navLinks'); 672 673 window.addEventListener('scroll', () => { 674 nav.classList.toggle('scrolled', window.scrollY > 40); 675 }, { passive: true }); 676 677 if (toggle && links) { 678 toggle.addEventListener('click', () => { 679 const open = links.classList.toggle('is-open'); 680 toggle.classList.toggle('is-open', open); 681 toggle.setAttribute('aria-expanded', String(open)); 682 }); 683 684 links.querySelectorAll('a').forEach(a => { 685 a.addEventListener('click', () => { 686 links.classList.remove('is-open'); 687 toggle.classList.remove('is-open'); 688 toggle.setAttribute('aria-expanded', 'false'); 689 }); 690 }); 691 } 692} 693 694 695/* ---------------------------------------------------------------- 696 FOOTER YEAR 697 ---------------------------------------------------------------- */ 698function setFooterYear() { 699 const el = document.getElementById('footerYear'); 700 if (el) el.textContent = new Date().getFullYear(); 701} 702 703 704/* ================================================================ 705 INIT 706 ================================================================ */ 707document.addEventListener('DOMContentLoaded', () => { 708 setFooterYear(); 709 initNav(); 710 711 renderTimeline(); 712 renderCalendar(); 713 714 renderSpeakingTopics(); 715 renderSpeakingDifferentiators(); 716 renderSpeakingAudiences(); 717 renderSpeakingVenues(); 718 renderSpeakingFormats(); 719 720 renderFeaturedOrgs(); 721 renderPartners(); 722 723 renderSocialLinks(); 724 725 renderLinkedInPost(); 726 renderFeaturedContent(); 727 728 renderContactLinks(); 729 initContactForm(); 730 731 renderGallery(); 732 initLightbox(); 733 734 initScrollReveal(); 735 observeNew(); 736 initCounters(); 737});
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.