PageSourceSearch

https://umis.com.au/cms.js

js umis.com.au collected 2026-10-06 12:42:05 UTC 22,916 bytes, 510 lines download raw bytes

1// GOOGLE SHEETS CONFIG
2var SHEETS = {
3    banner:        'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=322705111&single=true&output=csv',
4    events:        'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=486574204&single=true&output=csv',
5    announcements: 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=1065844255&single=true&output=csv',
6    slideshow:     'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=770268520&single=true&output=csv',
7    resources:     'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=328470026&single=true&output=csv',
8    donations:     'https://docs.google.com/spreadsheets/d/e/2PACX-1vQi4aHMdzZZtSyXUUwjUhFT8zgLMOFP9d3TnnjO56ToerUSz6hjxu448TvrtxQ2KkX3Y4UM3itcXIbr/pub?gid=361270418&single=true&output=csv'
9};
10
11
12var IC_ARR = '→';
13var IC_CAL = '📅';
14var IC_PIN = '📍';
15
16// Simple CSV parser (handles quoted fields)
17function parseCSV(text) {
18  var lines = text.trim().replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
19  if (lines.length < 2) return [];
20  var headers = lines[0].split(',').map(function (h) {
21    return h.trim().replace(/"/g, '');
22  });
23  return lines.slice(1).map(function (line) {
24    var values = [], cur = '', inQ = false;
25    for (var i = 0; i < line.length; i++) {
26      var ch = line[i];
27      if (ch === '"') {
28        inQ = !inQ;
29      } else if (ch === ',' && !inQ) {
30        values.push(cur.trim());
31        cur = '';
32      } else {
33        cur += ch;
34      }
35    }
36    values.push(cur.trim());
37    var obj = {};
38    headers.forEach(function (h, i) {
39      obj[h] = (values[i] || '').replace(/"/g, '').trim();
40    });
41    return obj;
42  });
43}
44
45function fetchSheet(url, callback) {
46  fetch(url)
47    .then(function (r) { return r.text(); })
48    .then(function (text) { callback(parseCSV(text)); })
49    .catch(function (err) {
50      console.error('Failed to load sheet:', err);
51      callback([]);
52    });
53}
54
55// SITE BANNER – dismissible alert from Google Sheet (active, message, link_text, link_url, urgency)
56// urgency: "urgent" or "yes" = red; otherwise = yellow (normal)
57function loadBanner() {
58  var container = document.getElementById('siteBanner');
59  if (!container) return;
60
61  var dismissed = sessionStorage.getItem('siteBannerDismissed');
62  if (dismissed) return;
63
64  fetchSheet(SHEETS.banner, function (rows) {
65    var row = (rows || []).find(function (r) { return r.active === 'yes'; });
66    if (!row || !row.message) return;
67
68    var msg = (row.message || '').trim();
69    var linkText = (row.link_text || '').trim();
70    var linkUrl = (row.link_url || '').trim();
71    var linkHtml = linkText && linkUrl
72      ? ' <a href="' + linkUrl + '" target="_blank" rel="noopener" class="underline hover:no-underline font-medium text-inherit">' + linkText + '</a>'
73      : '';
74
75    var urgency = (row.urgency || '').toLowerCase().trim();
76    var isUrgent = urgency === 'urgent' || urgency === 'yes';
77
78    var bgClass = isUrgent ? 'bg-red-500/50' : 'bg-gold/40';
79    var dotClass = isUrgent ? 'bg-red-400' : 'bg-gold';
80    var btnClass = isUrgent ? 'text-red-200 hover:text-white' : 'text-gold hover:text-white';
81
82    container.className = bgClass + ' text-white/95 px-4 py-3 flex items-center gap-3 text-sm md:text-base';
83    container.innerHTML =
84      '<span class="w-2 h-2 rounded-full ' + dotClass + ' flex-shrink-0"></span>' +
85      '<span class="flex-grow">' + msg + linkHtml + '</span>' +
86      '<button type="button" id="bannerClose" aria-label="Dismiss" class="' + btnClass + ' transition-colors flex-shrink-0 p-1">' +
87        '<i class="fas fa-times"></i>' +
88      '</button>';
89    container.classList.remove('hidden');
90
91    var btn = document.getElementById('bannerClose');
92    if (btn) {
93      btn.addEventListener('click', function () {
94        container.classList.add('hidden');
95        sessionStorage.setItem('siteBannerDismissed', '1');
96      });
97    }
98  });
99}
100
101// OFFICIAL ANNOUNCEMENTS – homepage preview
102function loadAnnouncementsPreview() {
103  var container = document.getElementById('announcementPreview');
104  var section = document.getElementById('section-announcements-home');
105  if (!container) return;
106
107  fetchSheet(SHEETS.announcements, function (rows) {
108    var published = (rows || [])
109      .filter(function (r) { return r.published === 'yes'; })
110      .sort(function (a, b) { return new Date(b.date) - new Date(a.date); });
111
112    if (!published.length) {
113      if (section) {
114        section.classList.add('hidden');
115        section.setAttribute('aria-hidden', 'true');
116      }
117      return;
118    }
119
120    if (section) {
121      section.classList.remove('hidden');
122      section.setAttribute('aria-hidden', 'false');
123    }
124
125    var html = '';
126    published.forEach(function (r, index) {
127      var linkHtml = '';
128      if (r.link_text && r.link_url) {
129        linkHtml =
130          '<a href="' + r.link_url + '" target="_blank" rel="noopener" class="inline-flex items-center gap-1 text-gold hover:text-gold/90 text-sm font-medium mt
130-4">' +
131            r.link_text + ' ' + IC_ARR +
132          '</a>';
133      }
134
135      html +=
136        '<article class="' + (index > 0 ? 'mt-6 pt-6 border-t border-gold/40 ' : '') + 'block">' +
137          '<p class="text-gold font-bold text-sm mb-2">' + (r.date || '') + '</p>' +
138          '<h3 class="text-white font-bold text-lg md:text-xl mb-4">' + (r.title || '') + '</h3>' +
139          '<p class="text-white/90 text-sm md:text-base leading-relaxed">' + (r.body || '') + '</p>' +
140          linkHtml +
141        '</article>';
142    });
143
144    container.innerHTML = html;
145  });
146}
147
148// Format date for archive display (e.g. "14 JAN 2025")
149function formatDateArchive(dateStr) {
150  if (!dateStr) return '';
151  var d = new Date(dateStr);
152  if (isNaN(d.getTime())) return dateStr;
153  var months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
154  return d.getDate() + ' ' + months[d.getMonth()] + ' ' + d.getFullYear();
155}
156
157// OFFICIAL ANNOUNCEMENTS ARCHIVE – Recent + Past sections
158function loadAnnouncementsArchive() {
159  var recentEl = document.getElementById('announcementsRecent');
160  var pastEl = document.getElementById('announcementsPast');
161  if (!recentEl && !pastEl) return;
162
163  fetchSheet(SHEETS.announcements, function (rows) {
164    var published = (rows || [])
165      .filter(function (r) { return r.published === 'yes'; })
166      .sort(function (a, b) { return new Date(b.date) - new Date(a.date); });
167
168    var recent = published.slice(0, 2);
169    var past = published.slice(2);
170
171    function cardHtml(r) {
172      var linkHtml = (r.link_text && r.link_url)
173        ? '<a href="' + r.link_url + '" target="_blank" rel="noopener" class="inline-flex items-center gap-1 text-gold hover:text-gold/90 text-sm font-medium mt-3">' + r.link_text + ' ' + IC_ARR + '</a>'
174        : '';
175      return '<article class="rounded-2xl bg-navy border border-gold/40 p-6 flex flex-col md:flex-row md:items-start gap-4">' +
176        '<p class="text-gold font-bold text-sm whitespace-nowrap flex-shrink-0">' + formatDateArchive(r.date) + '</p>' +
177        '<div class="flex-grow min-w-0">' +
178          '<h3 class="text-white font-bold text-lg md:text-xl mb-2">' + (r.title || '') + '</h3>' +
179          '<p class="text-white/85 text-sm md:text-base leading-relaxed">' + (r.body || '') + '</p>' +
180          linkHtml +
181        '</div>' +
182      '</article>';
183    }
184
185    if (recentEl) {
186      recentEl.innerHTML = recent.length ? recent.map(cardHtml).join('') : '<p class="text-white/60 text-sm">No recent announcements.</p>';
187    }
188    if (pastEl) {
189      pastEl.innerHTML = past.length ? past.map(cardHtml).join('') : '<p class="text-white/60 text-sm">No past announcements.</p>';
190    }
191  });
192}
193
194// UPCOMING EVENTS – featured + grid
195function loadEvents() {
196  var featuredEl = document.getElementById('featuredEvent');
197  var gridEl = document.getElementById('eventsGrid');
198  if (!featuredEl && !gridEl) return;
199
200  fetchSheet(SHEETS.events, function (rows) {
201    if (!rows || !rows.length) return;
202
203    var featured = null;
204    var others = [];
205
206    rows.forEach(function (r) {
207      if (r.published && r.published !== 'yes') return;
208      if (!r.title) return;
209      if (r.featured === 'yes' && !featured) {
210        featured = r;
211      } else {
212        others.push(r);
213      }
214    });
215
216    // Featured card
217    if (featuredEl) {
218      if (!featured) {
219        featuredEl.innerHTML =
220          '<div class="rounded-2xl bg-navy-dark border border-white/10 p-6 text-white/70 text-sm">No featured event at the moment.</div>';
221      } else {
222        var fImg = featured.image_url
223          ? '<div class="h-48 w-full overflow-hidden rounded-t-2xl"><img src="' + featured.image_url + '" alt="' + featured.title + '" class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div>'
224          : '';
225        var fDate = featured.date || '';
226        var fTime = featured.time || '';
227        var fLoc = featured.location || '';
228        var fContent = fImg +
229          '<div class="px-6 py-5 md:px-8 md:py-6">' +
230            '<p class="text-white/70 text-xs uppercase tracking-[0.2em] mb-1">Featured</p>' +
231            '<h3 class="text-white font-bold text-2xl md:text-3xl mb-3">' + featured.title + '</h3>' +
232            '<div class="flex flex-wrap items-center gap-3 text-sm text-white/80 mb-3">' +
233              (fDate || fTime ? '<span>' + IC_CAL + ' ' + fDate + (fTime ? ' · ' + fTime : '') + '</span>' : '') +
234              (fLoc ? '<span>' + IC_PIN + ' ' + fLoc + '</span>' : '') +
235            '</div>' +
236            '<p class="text-white/80 text-sm md:text-base leading-relaxed">' + (featured.description || '') + '</p>' +
237            (featured.rsvp_link ? '<span class="inline-flex items-center justify-center px-6 py-2 mt-4 rounded-lg bg-gold text-navy font-semibold text-sm group-hover:bg-gold-dark transition">RSVP ' + IC_ARR + '</span>' : '') +
238          '</div>';
239        var fClasses = 'group rounded-2xl bg-navy-dark gold-glow-border transition-all duration-300 ease-out hover:-translate-y-1 block';
240        featuredEl.innerHTML = featured.rsvp_link
241          ? '<a href="' + featured.rsvp_link + '" target="_blank" rel="noopener" class="' + fClasses + '">' + fContent + '</a>'
242          : '<article class="' + fClasses + '">' + fContent + '</article>';
243      }
244    }
245
246    // Grid cards (homepage: 3 events; events page: all + filter)
247    if (gridEl) {
248      if (!others.length) {
249        gridEl.innerHTML =
250          '<div class="col-span-full rounded-2xl bg-navy-dark border border-white/10 p-6 text-white/70 text-sm">No upcoming events to show right now.</div>';
251      } else {
252        var isEventsPage = /events\.html/.test(window.location.pathname) || window.location.href.indexOf('events.html') !== -1;
253        var gridEvents = isEventsPage ? others : others.slice(0, 3);
254
255        // Build filter UI on events page
256        var filterEl = document.getElementById('eventsFilter');
257        if (isEventsPage && filterEl) {
258          var categories = [];
259          others.forEach(function (ev) {
260            var cat = (ev.category || '').trim();
261            if (cat && categories.indexOf(cat) === -1) categories.push(cat);
262          });
263          categories.sort();
264          var filterHtml = '<div class="flex flex-wrap items-center gap-2 mb-6">' +
265            '<span class="text-white/70 text-sm mr-2">Filter:</span>' +
266            '<button type="button" class="events-filter-btn px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 bg-gold text-navy" data-category="">All</button>';
267          categories.forEach(function (cat) {
268            filterHtml += '<button type="button" class="events-filter-btn px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 bg-white/10 text-white/90 hover:bg-gold/30 hover:text-white border border-white/20" data-category="' + cat.replace(/"/g, '&quot;') + '" data-audience="">' + cat + '</button>';
269          });
270          filterHtml += '<span class="text-white/40 text-sm mx-1">|</span>' +
271            '<button type="button" class="events-filter-btn px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 bg-white/10 text-white/90 hover:bg-gold/30 hover:text-white border border-white/20" data-category="" data-audience="women">Women</button>' +
272            '<button type="button" class="events-filter-btn px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 bg-white/10 text-white/90 hover:bg-gold/30 hover:text-white border border-white/20" data-category="" data-audience="men">Men</button>' +
273            '</div>';
274          filterEl.innerHTML = filterHtml;
275        }
276
277        function renderEventCard(ev) {
278          var eImg = ev.image_url
279            ? '<div class="h-40 w-full overflow-hidden rounded-t-2xl"><img src="' + ev.image_url + '" alt="' + (ev.title || '') + '" class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"></div>'
280            : '<div class="h-40 w-full overflow-hidden rounded-t-2xl bg-navy-dark flex items-center justify-center"><i class="fas fa-calendar-alt text-white/30 text-4xl"></i></div>';
281          var eBtn = '<span class="inline-flex items-center gap-1 text-gold font-semibold text-sm mt-4 group-hover:text-gold/90 transition-colors">Learn more ' + IC_ARR + '</span>';
282          var eDate = ev.date || '';
283          var eTime = ev.time || '';
284          var eLoc = ev.location || '';
285          var cardClasses = 'group rounded-2xl bg-navy-dark gold-glow-border flex flex-col justify-between transition-all duration-300 ease-out hover:-translate-y-1 block';
286          var cardContent = eImg +
287            '<div class="p-6 flex flex-col flex-grow">' +
288              '<div class="flex-grow">' +
289                '<p class="text-white/70 text-xs uppercase tracking-[0.2em] mb-1">' +
290                  (ev.category || '') +
291                  (ev.audience ? ' · ' + ev.audience : '') +
292                '</p>' +
293                '<h3 class="text-white font-bold text-lg md:text-xl mb-3">' + ev.title + '</h3>' +
294                '<div class="text-gold text-sm mb-2">' +
295                  (eDate || eTime ? IC_CAL + ' ' + eDate + (eTime ? ' · ' + eTime : '') : '') +
296                '</div>' +
297                (eLoc ? '<div class="text-gold text-sm mb-2">' + IC_PIN + ' ' + eLoc + '</div>' : '') +
298                '<p class="text-white/80 text-sm leading-relaxed">' + (ev.description || '') + '</p>' +
299              '</div>' +
300              eBtn +
301            '</div>';
302          if (ev.rsvp_link) {
303            return '<a href="' + ev.rsvp_link + '" target="_blank" rel="noopener" class="' + cardClasses + '">' + cardContent + '</a>';
304          }
305          return '<article class="' + cardClasses + '">' + cardContent + '</article>';
306        }
307
308        function renderGrid(eventsToShow) {
309          var html = '';
310          eventsToShow.forEach(function (ev) {
311            html += renderEventCard(ev);
312          });
313          gridEl.innerHTML = html || '<div class="col-span-full rounded-2xl bg-navy-dark border border-white/10 p-6 text-white/70 text-sm">No events in this category.</div>';
314        }
315
316        renderGrid(gridEvents);
317
318        // Wire filter buttons
319        if (isEventsPage && filterEl) {
320          filterEl.querySelectorAll('.events-filter-btn').forEach(function (btn) {
321            btn.addEventListener('click', function () {
322              var cat = this.getAttribute('data-category') || '';
323              var aud = (this.getAttribute('data-audience') || '').toLowerCase();
324              filterEl.querySelectorAll('.events-filter-btn').forEach(function (b) {
325                var bCat = b.getAttribute('data-category') || '';
326                var bAud = (b.getAttribute('data-audience') || '').toLowerCase();
327                var isActive = bCat === cat && bAud === aud;
328                b.className = 'events-filter-btn px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 ' +
329                  (isActive ? 'bg-gold text-navy' : 'bg-white/10 text-white/90 hover:bg-gold/30 hover:text-white border border-white/20');
330              });
331              var filtered = others.filter(function (ev) {
332                var matchCat = !cat || (ev.category || '').trim() === cat;
333                var audStr = (ev.audience || '').toLowerCase();
334                var matchAud = !aud ||
335                  (aud === 'women' && (audStr.indexOf('women') !== -1 || audStr.indexOf('sisters') !== -1)) ||
336                  (aud === 'men' && (audStr.indexOf('brothers') !== -1 || (audStr.indexOf('men') !== -1 && audStr.indexOf('women') === -1)));
337                return matchCat && matchAud;
338              });
339              renderGrid(filtered);
340            });
341          });
342        }
343      }
344    }
345  });
346}
347
348// FEATURED ITEMS SLIDESHOW
349function loadSlideshow() {
350  var track = document.getElementById('slideshowTrack');
351  var dots  = document.getElementById('slideshowDots');
352  var section = document.getElementById('section-featured-home');
353  if (!track || !dots) return;
354
355  fetchSheet(SHEETS.slideshow, function (rows) {
356    var slides = (rows || [])
357      .filter(function (r) { return r.active === 'yes'; })
358      .sort(function (a, b) {
359        return (parseInt(a.order, 10) || 0) - (parseInt(b.order, 10) || 0);
360      });
361
362    if (!slides.length) {
363      if (section) {
364        section.classList.add('hidden');
365        section.setAttribute('aria-hidden', 'true');
366      }
367      track.innerHTML = '';
368      dots.innerHTML = '';
369      return;
370    }
371
372    if (section) {
373      section.classList.remove('hidden');
374      section.setAttribute('aria-hidden', 'false');
375    }
376
377    // Build slides
378    var html = '';
379    slides.forEach(function (s) {
380      var url = s.image_url || '';
381      html +=
382        '<div class="w-full h-full flex-shrink-0">' +
383          '<img src="' + url + '" alt="featured item" class="w-full h-full object-cover" />' +
384        '</div>';
385    });
386    track.innerHTML = html;
387
388    // Build dots (slightly larger for visibility)
389    var dotsHtml = '';
390    slides.forEach(function (_, i) {
391      dotsHtml +=
392        '<button type="button" class="w-3 h-3 md:w-3.5 md:h-3.5 rounded-full ' +
393        (i === 0 ? 'bg-gold' : 'bg-white/50') +
394        '" data-index="' + i + '"></button>';
395    });
396    dots.innerHTML = dotsHtml;
397
398    var current = 0;
399    function goTo(index) {
400      current = index;
401      track.style.transform = 'translateX(-' + (current * 100) + '%)';
402      var dotEls = dots.querySelectorAll('button');
403      dotEls.forEach(function (d, i) {
404        d.classList.toggle('bg-gold', i === current);
405        d.classList.toggle('bg-white/50', i !== current);
406      });
407    }
408
409    // Wire dot clicks
410    dots.querySelectorAll('button').forEach(function (btn) {
411      btn.addEventListener('click', function () {
412        var idx = parseInt(this.getAttribute('data-index'), 10) || 0;
413        goTo(idx);
414      });
415    });
416
417    // Auto-advance
418    setInterval(function () {
419      if (!slides.length) return;
420      goTo((current + 1) % slides.length);
421    }, 4000);
422  });
423}
424
425// RESOURCES PAGE – cards from Google Sheet (id, active, title, description, tag, link [, link_text])
426function loadResources() {
427  var container = document.getElementById('resourcesList');
428  if (!container) return;
429
430  fetchSheet(SHEETS.resources, function (rows) {
431    var active = (rows || []).filter(function (r) {
432      return r.active === 'yes' && (r.title || r.link);
433    });
434
435    if (!active.length) {
436      container.innerHTML =
437        '<div class="rounded-2xl bg-navy-dark border border-white/10 p-8 text-white/70 text-sm">No resources to show right now.</div>';
438      return;
439    }
440
441    var html = '';
442    active.forEach(function (r) {
443      var tags = (r.tag || '').split(/\s*[–\-]\s*/).filter(Boolean);
444      var tagsHtml = tags.length
445        ? tags.map(function (t) {
446            return '<span class="inline-block px-2.5 py-0.5 rounded text-xs font-medium bg-white/15 text-white/90">' + (t.trim()) + '</span>';
447          }).join(' ')
448        : '';
449      var ctaText = (r.link_text || '').trim() || 'Go to resource';
450      var linkHtml = r.link
451        ? '<a href="' + r.link + '" target="_blank" rel="noopener" class="inline-flex items-center gap-1 text-gold font-semibold text-sm mt-4 hover:text-gold/90 transition-colors">' + ctaText + ' ' + IC_ARR + '</a>'
452        : '';
453
454      html +=
455        '<article class="rounded-2xl bg-navy border border-gold/40 p-6 flex flex-col">' +
456          '<div class="flex flex-wrap gap-2 mb-3">' + tagsHtml + '</div>' +
457          '<h3 class="text-white font-bold text-xl md:text-2xl mb-3">' + (r.title || '') + '</h3>' +
458          '<p class="text-white/70 text-sm md:text-base leading-relaxed flex-grow">' + (r.description || '') + '</p>' +
459          linkHtml +
460        '</article>';
461    });
462    container.innerHTML = html;
463  });
464}
465
466// DONATE PAGE – causes/campaigns from Google Sheet (id, active, order, title, link)
467function loadCampaigns() {
468  var container = document.getElementById('campaignsList');
469  if (!container) return;
470
471  fetchSheet(SHEETS.donations, function (rows) {
472    var active = (rows || [])
473      .filter(function (r) { return r.active === 'yes' && (r.title || r.link); })
474      .sort(function (a, b) {
475        return (parseInt(a.order, 10) || 0) - (parseInt(b.order, 10) || 0);
476      });
477
478    if (!active.length) {
479      container.innerHTML =
480        '<div class="rounded-2xl bg-navy-dark border border-white/10 p-6 text-white/70 text-sm">No campaigns to show right now.</div>';
481      return;
482    }
483
484    var html = '';
485    active.forEach(function (c) {
486      var link = (c.link || '').trim();
487      var isLink = link && link.indexOf('http') === 0;
488      var cardContent =
489        '<span class="text-white font-bold text-lg md:text-xl flex-grow">' + (c.title || '') + '</span>' +
490        (isLink ? '<i class="fas fa-arrow-right text-gold text-sm flex-shrink-0"></i>' : '');
491      var wrapper = isLink
492        ? '<a href="' + link + '" target="_blank" rel="noopener" class="rounded-2xl bg-navy border border-gold/40 p-6 flex items-center gap-4 hover:border-gold/60 transition-colors">' + cardContent + '</a>'
493        : '<div class="rounded-2xl bg-navy border border-gold/40 p-6 flex items-center gap-4">' + cardContent + '</div>';
494      html += wrapper;
495    });
496    container.innerHTML = html;
497  });
498}
499
500// Init for homepage + resources + donate pages
501document.addEventListener('DOMContentLoaded', function () {
502  loadBanner();
503  loadAnnouncementsPreview();
504  loadAnnouncementsArchive();
505  loadEvents();
506  loadSlideshow();
507  loadResources();
508  loadCampaigns();
509});
510

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.