PageSourceSearch

https://newagealpha.com/js/pages/about.js

js newagealpha.com collected 2026-09-28 12:35:43 UTC 12,165 bytes, 369 lines download raw bytes

1/**
2 * NAA Web - About Page Module
3 * Handles team display, profile modals, and fullscreen functionality
4 *
5 * Modernized from legacy about.aspx inline scripts
6 */
7
8import { getTeams } from '../core/api.js';
9import { init as initTerritoryMap } from '../components/territory-map.js';
10
11let teamData = [];
12let currentProfile = null;
13
14/** Rewrite pdfs.newagealpha.com image URLs to proxy through /api/img/ */
15function proxyImageUrl(url) {
16    if (!url) return url;
17    const prefix = 'https://pdfs.newagealpha.com/';
18    if (url.startsWith(prefix)) {
19        return '/api/img/' + url.substring(prefix.length);
20    }
21    return url;
22}
23
24/**
25 * Initialize the about page
26 */
27export async function init() {
28    try {
29
30        await loadTeamData();
31
32        setupFullscreen();
33
34        setupScrollBehavior();
35
36        handleHashNavigation();
37
38    } catch (error) {
39        console.error('Error initializing about page:', error);
40    } finally {
41        // Isolated: the territory map enhances server-rendered markup, so a
42        // failure there must not take the rest of the page with it — and the
43        // team grid failing must not stop the map from drawing.
44        try {
45            initTerritoryMap();
46        } catch (error) {
47            console.error('Error initializing territory map:', error);
48        }
49
50        if (window.NAALoader) NAALoader.release();
51    }
52}
53
54/**
55 * Load team data from the API and render the team grid
56 */
57async function loadTeamData() {
58    try {
59        teamData = await getTeams();
60
61        if (teamData && teamData.length > 0) {
62            renderTeamGrid(teamData);
63            setupTeamInteractions();
64        }
65    } catch (error) {
66        console.error('Error loading team data:', error);
67
68    }
69}
70
71/**
72 * Render the team members grid
73 * @param {Array} members - Array of team member objects
74 */
75function renderTeamGrid(members) {
76    const container = document.getElementById('teamContent');
77    if (!container) return;
78
79    container.innerHTML = '';
80
81    members.forEach(function(member) {
82        const memberEl = createTeamMemberElement(member);
83        container.appendChild(memberEl);
84    });
85}
86
87/**
88 * Create a team member element
89 * @param {Object} member - Team member data
90 * @returns {HTMLElement} Team member DOM element
91 */
92function createTeamMemberElement(member) {
93    const wrapper = document.createElement('div');
94    wrapper.className = 'dgrid';
95
96    const hasDescription = member.description && member.description.trim().length > 0;
97    const hasLinkedIn = member.linkedin && member.linkedin.trim().length > 0;
98    const hasTwitter = member.twitter && member.twitter.trim().length > 0;
99
100    const profileImage = proxyImageUrl(member.profile_Image || member.profile_image || member.profileImage) || '/assets/images/placeholder.png';
101
102    wrapper.innerHTML = `
103        <div class="team-members teamMem ${escapeHtml(member.name)}" id="${escapeHtml(member.code)}" style="cursor: ${hasDescription ? 'pointer' : 'default'};">
104            <div class="social_media">
105                ${hasLinkedIn ? `
106                    <a class="linkedin" data-social-url="${escapeAttr(member.linkedin)}">
107                        <svg viewBox="0 0 24 24" style="width: 20px; height: 20px;">
108                            <path fill="#ffffff" d="M4.98 3.5c0 1.381-1.11 2.5-2.48 2.5s-2.48-1.119-2.48-2.5c0-1.38 1.11-2.5 2.48-2.5s2.48 1.12 2.48 2.5zm.02 4.5h-5v16h5v-16zm7.982 0h-4.968v16h4.969v-8.399c0-4.67 6.029-5.052 6.029 0v8.399h4.988v-10.131c0-7.88-8.922-7.593-11.018-3.714v-2.155z"></path>
109                        </svg>
110                    </a>
111                ` : ''}
112                ${hasTwitter ? `
113                    <a class="twitter" data-social-url="${escapeAttr(member.twitter)}">
114                        <svg xmlns="http://www.w3.org/2000/svg" style="width: 20px; height: 20px;" viewBox="0 0 512 512">
115                            <path fill="#fff" d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"></path>
116                        </svg>
117                    </a>
118                ` : ''}
119            </div>
120            <div class="team-profile-pic"
121                 id="${escapeHtml(member.code)}Image"
122                 data-member-code="${escapeAttr(member.code)}"
123                 ${hasDescription ? 'data-bs-toggle="modal" data-bs-target="#myMo
123dal"' : ''}>
124                <img src="${escapeAttr(profileImage)}"
125                     alt="${escapeAttr(member.name)}"
126                     onerror="this.src='/assets/images/placeholder.png'" />
127            </div>
128            <div class="teamsWrapper ${escapeHtml(member.code)}Overview prof-dec"
129                 id="${escapeHtml(member.code)}Wrapper"
130                 data-member-code="${escapeAttr(member.code)}"
131                 ${hasDescription ? 'data-bs-toggle="modal" data-bs-target="#myModal"' : ''}>
132                <h5 class="teamMemberTitle">${escapeHtml(member.name)}</h5>
133                <p class="role">${escapeHtml(member.title || member.role || '')}</p>
134            </div>
135            <p style="display: none;" class="des" id="${escapeHtml(member.code)}Des">${member.description || ''}</p>
136        </div>
137    `;
138
139    return wrapper;
140}
141
142/**
143 * Set up team member click interactions
144 */
145function setupTeamInteractions() {
146    const container = document.getElementById('teamContent');
147    if (!container) return;
148
149    container.addEventListener('click', function(e) {
150        const profilePic = e.target.closest('.team-profile-pic[data-member-code]');
151        const wrapper = e.target.closest('.teamsWrapper[data-member-code]');
152        const socialLink = e.target.closest('.social_media a');
153
154        if (socialLink) {
155            e.preventDefault();
156            e.stopPropagation();
157            const url = socialLink.dataset.socialUrl;
158            if (url) {
159                openExternalLink(url);
160            }
161            return;
162        }
163
164        const memberCode = profilePic?.dataset.memberCode || wrapper?.dataset.memberCode;
165        if (memberCode) {
166            const member = teamData.find(m => m.code === memberCode);
167            if (member && member.description && member.description.trim().length > 0) {
168                showProfileModal(member);
169            }
170        }
171    });
172}
173
174/**
175 * Show the profile modal with member details
176 * @param {Object} member - Team member data
177 */
178function showProfileModal(member) {
179    currentProfile = member;
180
181    const profileImage = proxyImageUrl(member.profile_Image || member.profile_image || member.profileImage) || '/assets/images/placeholder.png';
182
183    const imageContainer = document.getElementById('Image');
184    if (imageContainer) {
185        imageContainer.innerHTML = `<img src="${escapeAttr(profileImage)}" alt="${escapeAttr(member.name)}" onerror="this.src='/assets/images/placeholder.png'" />`;
186    }
187
188    const modalName = document.getElementById('modalName');
189    if (modalName) {
190        modalName.innerHTML = `
191            <h5 class="teamMemberTitle">${escapeHtml(member.name)}</h5>
192            <p class="role">${escapeHtml(member.title || member.role || '')}</p>
193            <p class="description">${member.description || ''}</p>
194        `;
195    }
196
197    const linkedinLink = document.querySelector('#myModal #linkedin');
198    const twitterLink = document.querySelector('#myModal #twitter');
199
200    if (linkedinLink) {
201        if (member.linkedin && member.linkedin.trim().length > 0) {
202            linkedinLink.style.display = 'inline-block';
203            linkedinLink.onclick = function(e) {
204                e.preventDefault();
205                openExternalLink(member.linkedin);
206            };
207        } else {
208            linkedinLink.style.display = 'none';
209        }
210    }
211
212    if (twitterLink) {
213        if (member.twitter && member.twitter.trim().length > 0) {
214            twitterLink.style.display = 'inline-block';
215            twitterLink.onclick = function(e) {
216                e.preventDefault();
217                openExternalLink(member.twitter);
218            };
219        } else {
220            twitterLink.style.display = 'none';
221        }
222    }
223}
224
225/**
226 * Open external link with speed bump modal
227 * @param {string} url - External URL to open
228 */
229function openExternalLink(url) {
230    const speedBumpModal = document.getElementById('TeamredirectPopupIn');
231    const acceptBtn = document.getElementById('redirect_accept_teams');
232
233    if (speedBumpModal && acceptBtn) {
234        acceptBtn.href = url;
235
236        const modal = bootstrap.Modal.getOrCreateInstance(speedBumpModal);
237        modal.show();
238    } else {
239
240        window.open(url, '_blank');
241    }
242}
243
244/**
245 * Set up fullscreen functionality for the history timeline
246 */
247function setupFullscreen() {
248    const fullscreenToggle = document.getElementById('fullscreenToggle');
249    const historyElement = document.getElementById('myHistory');
250
251    if (fullscreenToggle && historyElement) {
252        fullscreenToggle.addEventListener('click', function(e) {
253            e.preventDefault();
254            openFullscreen(historyElement);
255        });
256    }
257
258    document.addEventListener('fullscreenchange', onFullscreenChange);
259    document.addEventListener('webkitfullscreenchange', onFullscreenChange);
260    document.addEventListener('msfullscreenchange', onFullscreenChange);
261}
262
263/**
264 * Open element in fullscreen mode
265 * @param {HTMLElement} elem - Element to display fullscreen
266 */
267function openFullscreen(elem) {
268    if (elem.requestFullscreen) {
269        elem.requestFullscreen();
270    } else if (elem.webkitRequestFullscreen) {
271        elem.webkitRequestFullscreen();
272    } else if (elem.msRequestFullscreen) {
273        elem.msRequestFullscreen();
274    }
275
276    const toggleImg = document.querySelector('.fullscreen_toggle img');
277    if (toggleImg) {
278        toggleImg.style.display = 'none';
279    }
280}
281
282/**
283 * Handle fullscreen change events
284 */
285function onFullscreenChange() {
286    if (!document.fullscreenElement &&
287        !document.webkitFullscreenElement &&
288        !document.msFullscreenElement) {
289
290        const toggleImg = document.querySelector('.fullscreen_toggle img');
291        if (toggleImg) {
292            toggleImg.style.display = '';
293        }
294    }
295}
296
297/**
298 * Set up scroll behavior for section navigation
299 */
300function setupScrollBehavior() {
301
302    window.addEventListener('scroll', function() {
303        const footer = document.getElementById('footer');
304        const navigation = document.querySelector('.ourTeamnavigation');
305
306        if (navigation && footer) {
307            const scrollPosition = window.scrollY + window.innerHeight;
308            const footerTop = document.body.scrollHeight - footer.offsetHeight;
309
310            if (scrollPosition > footerTop) {
311                navigation.classList.add('hideFooter');
312            } else {
313                navigation.classList.remove('hideFooter');
314            }
315        }
316    });
317}
318
319/**
320 * Handle hash navigation (e.g., #team)
321 */
322function handleHashNavigation() {
323    const hash = window.location.hash.substring(1);
324    if (hash) {
325        scrollToSection(hash);
326    }
327}
328
329/**
330 * Scroll to a section by ID
331 * @param {string} sectionId - Section element ID
332 */
333function scrollToSection(sectionId) {
334    const target = document.getElementById(sectionId);
335    if (target) {
336        setTimeout(function() {
337            const offset = target.getBoundingClientRect().top + window.pageYOffset - 32;
338            window.scrollTo({
339                top: offset,
340                behavior: 'smooth'
341            });
342        }, 100);
343    }
344}
345
346/**
347 * Escape HTML to prevent XSS
348 * @param {string} text - Text to escape
349 * @returns {string} Escaped text
350 */
351function escapeHtml(text) {
352    if (!text) return '';
353    const div = document.createElement('div');
354    div.textContent = text;
355    return div.innerHTML;
356}
357
358/**
359 * Escape attribute values
360 * @param {string} text - Text to escape for attribute
361 * @returns {string} Escaped text
362 */
363function escapeAttr(text) {
364    if (!text) return '';
365    return text.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
366}
367
368// Init is called externally via dynamic import() from About.razor
369// This ensures re-execution on Blazor enhanced navigation.

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.