@@ -72,14 +72,17 @@ function renderLeadershipCinema(sections, container) {
7272 const bio = member . body || 'Bio coming soon...' ;
7373 const hasImg = imageExists ( m ) ;
7474 const activeClass = idx === 0 ? ' active' : '' ;
75+ const images = [ m . image , m . image2 , m . image3 , m . image4 ] . filter ( x => x && x . trim ( ) ) ;
7576 cardsHtml += `<div class="cinema-card${ activeClass } " data-idx="${ idx } "
7677 data-name="${ esc ( m . name ) } " data-role="${ esc ( m . role ) } "
7778 data-batch="${ esc ( m . batch ) } " data-bio="${ esc ( bio ) } "
78- data-image="${ esc ( m . image ) } " data-section-label="${ esc ( member . sectionLabel ) } ">
79+ data-spirit-dog="${ esc ( m . spirit_dog || '' ) } "
80+ data-images="${ esc ( images . join ( '|' ) ) } " data-section-label="${ esc ( member . sectionLabel ) } ">
7981 ${ cinemaAvatarHtml ( m , idx , hasImg ) }
8082 <div class="cinema-info">
8183 <h3 class="cinema-name">${ esc ( m . name ) } </h3>
8284 <span class="cinema-role">${ esc ( m . role ) } </span>
85+ ${ m . spirit_dog ? `<span class="cinema-spirit-dog">🐾 ${ esc ( m . spirit_dog ) } </span>` : '' }
8386 </div>
8487 </div>` ;
8588 dotsHtml += `<button class="cinema-dot${ idx === 0 ? ' active' : '' } " data-idx="${ idx } " aria-label="Go to ${ esc ( m . name ) } "></button>` ;
@@ -105,9 +108,17 @@ function renderCoreGrid(members, container) {
105108 members . forEach ( ( member , idx ) => {
106109 const m = member . meta ;
107110 const hasImg = imageExists ( m ) ;
108- html += `<div class="core-grid-item" style="--i:${ idx } ">
111+ const coreBio = member . body ? member . body . trim ( ) : '' ;
112+ const coreSDog = m . spirit_dog || '' ;
113+ const hasPopup = coreBio || coreSDog ;
114+ const coreImages = [ m . image , m . image2 , m . image3 , m . image4 ] . filter ( x => x && x . trim ( ) ) ;
115+ html += `<div class="core-grid-item${ hasPopup ? ' core-grid-item--has-popup' : '' } " style="--i:${ idx } "
116+ data-name="${ esc ( m . name ) } " data-role=""
117+ data-batch="${ esc ( m . batch || '' ) } " data-bio="${ esc ( coreBio ) } "
118+ data-spirit-dog="${ esc ( coreSDog ) } " data-images="${ esc ( coreImages . join ( '|' ) ) } ">
109119 ${ coreAvatarHtml ( m , idx , hasImg ) }
110120 <span class="core-name">${ esc ( m . name ) } </span>
121+ ${ coreSDog ? `<span class="core-spirit-dog">🐾 ${ esc ( coreSDog ) } </span>` : '' }
111122 </div>` ;
112123 } ) ;
113124 container . innerHTML = `<div class="core-grid">${ html } </div>` ;
@@ -141,6 +152,9 @@ function initLeaderCarousel() {
141152 dots . forEach ( ( d , i ) => d . classList . toggle ( 'active' , i === currentIdx ) ) ;
142153 const sectionLabel = cards [ currentIdx ] . dataset . sectionLabel ;
143154 if ( label && sectionLabel ) label . textContent = sectionLabel ;
155+ if ( typeof window . __syncTeamPopupFromLeaderCard === 'function' ) {
156+ window . __syncTeamPopupFromLeaderCard ( cards [ currentIdx ] ) ;
157+ }
144158 resetProgress ( ) ;
145159 }
146160
@@ -196,43 +210,221 @@ function initLeaderCarousel() {
196210function initTeamPopup ( ) {
197211 const overlay = document . getElementById ( 'team-popup-overlay' ) ;
198212 if ( ! overlay ) return ;
199-
200- const closePopup = ( ) => overlay . classList . remove ( 'active' ) ;
213+ const prevBtn = overlay . querySelector ( '.team-popup-nav-prev' ) ;
214+ const nextBtn = overlay . querySelector ( '.team-popup-nav-next' ) ;
215+
216+ const closePopup = ( ) => {
217+ overlay . classList . remove ( 'active' ) ;
218+ overlay . dataset . popupSource = '' ;
219+ overlay . dataset . currentName = '' ;
220+ } ;
201221 overlay . querySelector ( '.team-popup-close' ) . addEventListener ( 'click' , closePopup ) ;
202222 overlay . addEventListener ( 'click' , e => { if ( e . target === overlay ) closePopup ( ) ; } ) ;
203223 document . addEventListener ( 'keydown' , e => { if ( e . key === 'Escape' ) closePopup ( ) ; } ) ;
204224
205- const leadershipSection = document . querySelector ( '[data-section="leadership"]' ) ;
206- if ( ! leadershipSection ) return ;
207-
208- leadershipSection . addEventListener ( 'click' , e => {
209- const card = e . target . closest ( '.cinema-card' ) ;
210- if ( ! card ) return ;
211-
212- const name = card . dataset . name ;
213- const role = card . dataset . role ;
214- const batch = card . dataset . batch ;
215- const bio = card . dataset . bio ;
216- const image = card . dataset . image ;
217-
225+ function openPopup ( name , role , batch , bio , images , spiritDog , source = '' ) {
218226 const popupCard = overlay . querySelector ( '.team-popup-card' ) ;
219227 const avatarEl = popupCard . querySelector ( '.team-popup-avatar' ) ;
220- const imgPath = image && image . trim ( ) ;
228+ const stripEl = popupCard . querySelector ( '.team-popup-img-strip' ) ;
229+ let activeImageIndex = 0 ;
221230
222- if ( imgPath ) {
223- avatarEl . innerHTML = `<img class="team-popup-avatar-img" src="${ esc ( imgPath ) } " alt="${ esc ( name ) } "
231+ function setMain ( src ) {
232+ avatarEl . innerHTML = `<img class="team-popup-avatar-img" src="${ esc ( src ) } " alt="${ esc ( name ) } "
224233 onerror="this.remove();this.parentNode.innerHTML='<span class=\\'team-popup-emoji\\'>🐾</span>'">` ;
234+ }
235+
236+ if ( images . length ) {
237+ activeImageIndex = 0 ;
238+ setMain ( images [ 0 ] ) ;
239+ avatarEl . classList . add ( 'team-popup-avatar--zoomable' ) ;
240+ avatarEl . title = 'Click to enlarge' ;
241+ avatarEl . onclick = ( ) => {
242+ if ( typeof openDeptLightbox === 'function' ) {
243+ openDeptLightbox ( images , activeImageIndex ) ;
244+ }
245+ } ;
225246 } else {
226247 avatarEl . innerHTML = `<span class="team-popup-emoji">🐾</span>` ;
248+ avatarEl . classList . remove ( 'team-popup-avatar--zoomable' ) ;
249+ avatarEl . title = '' ;
250+ avatarEl . onclick = null ;
251+ }
252+
253+ if ( images . length > 1 ) {
254+ stripEl . classList . add ( 'has-thumbs' ) ;
255+ stripEl . innerHTML = images . map ( ( src , i ) =>
256+ `<button class="popup-thumb${ i === 0 ? ' active' : '' } " data-src="${ esc ( src ) } " data-idx="${ i } ">
257+ <img src="${ esc ( src ) } " alt="" loading="lazy">
258+ </button>`
259+ ) . join ( '' ) ;
260+ stripEl . querySelectorAll ( '.popup-thumb' ) . forEach ( btn => {
261+ btn . addEventListener ( 'click' , ( ) => {
262+ stripEl . querySelectorAll ( '.popup-thumb' ) . forEach ( b => b . classList . remove ( 'active' ) ) ;
263+ btn . classList . add ( 'active' ) ;
264+ activeImageIndex = Number ( btn . dataset . idx || 0 ) ;
265+ setMain ( btn . dataset . src ) ;
266+ } ) ;
267+ } ) ;
268+ } else {
269+ stripEl . classList . remove ( 'has-thumbs' ) ;
270+ stripEl . innerHTML = '' ;
227271 }
228272
229273 popupCard . querySelector ( '.team-popup-name' ) . textContent = name ;
230274 popupCard . querySelector ( '.team-popup-role' ) . textContent = role ;
231275 popupCard . querySelector ( '.team-popup-batch' ) . textContent = batch ;
232- popupCard . querySelector ( '.team-popup-bio' ) . textContent = bio ;
233-
276+ const sdEl = popupCard . querySelector ( '.team-popup-spirit-dog' ) ;
277+ sdEl . innerHTML = '' ;
278+ if ( spiritDog ) {
279+ const lbl = document . createElement ( 'span' ) ;
280+ lbl . className = 'team-popup-spirit-dog-label' ;
281+ lbl . textContent = 'spirit dog: ' ;
282+ sdEl . appendChild ( lbl ) ;
283+
284+ const dogNames = spiritDog
285+ . split ( '/' )
286+ . map ( s => s . trim ( ) )
287+ . filter ( Boolean ) ;
288+
289+ dogNames . forEach ( ( dogName , idx ) => {
290+ const a = document . createElement ( 'a' ) ;
291+ a . className = 'team-popup-spirit-dog-link' ;
292+ a . textContent = dogName ;
293+ a . href = '#' ;
294+ a . addEventListener ( 'click' , async e => {
295+ e . preventDefault ( ) ;
296+ closePopup ( ) ;
297+ await showPage ( 'dogs' ) ;
298+
299+ // Wait until dog cards are rendered, then open the matched dog card directly.
300+ function tryOpenDog ( ) {
301+ const input = document . getElementById ( 'dogs-search' ) ;
302+ const grid = document . getElementById ( 'dogs-grid' ) ;
303+ if ( input && grid && grid . querySelector ( '.dog-card' ) ) {
304+ input . value = '' ;
305+ input . dispatchEvent ( new Event ( 'input' ) ) ;
306+
307+ const cards = [ ...grid . querySelectorAll ( '.dog-card' ) ] ;
308+ const target = dogName . trim ( ) . toLowerCase ( ) ;
309+ const exact = cards . find ( c => ( c . dataset . name || '' ) . trim ( ) . toLowerCase ( ) === target ) ;
310+ const starts = cards . find ( c => ( c . dataset . name || '' ) . trim ( ) . toLowerCase ( ) . startsWith ( target ) ) ;
311+ const partial = cards . find ( c => ( c . dataset . name || '' ) . trim ( ) . toLowerCase ( ) . includes ( target ) ) ;
312+ const match = exact || starts || partial ;
313+ if ( match ) {
314+ match . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
315+ setTimeout ( ( ) => match . click ( ) , 120 ) ;
316+ }
317+ } else {
318+ setTimeout ( tryOpenDog , 80 ) ;
319+ }
320+ }
321+
322+ setTimeout ( tryOpenDog , 0 ) ;
323+ } ) ;
324+
325+ sdEl . appendChild ( a ) ;
326+ if ( idx < dogNames . length - 1 ) {
327+ sdEl . appendChild ( document . createTextNode ( ' / ' ) ) ;
328+ }
329+ } ) ;
330+ }
331+ popupCard . querySelector ( '.team-popup-bio' ) . textContent = bio ;
332+ overlay . dataset . popupSource = source ;
333+ overlay . dataset . currentName = name ;
234334 overlay . classList . add ( 'active' ) ;
235- } ) ;
335+
336+ if ( prevBtn ) prevBtn . disabled = false ;
337+ if ( nextBtn ) nextBtn . disabled = false ;
338+ }
339+
340+ function openPopupFromCard ( card , source = 'leadership' ) {
341+ if ( ! card ) return ;
342+ openPopup (
343+ card . dataset . name ,
344+ card . dataset . role ,
345+ card . dataset . batch ,
346+ card . dataset . bio ,
347+ ( card . dataset . images || '' ) . split ( '|' ) . filter ( Boolean ) ,
348+ card . dataset . spiritDog || '' ,
349+ source
350+ ) ;
351+ }
352+
353+ // Used by the leadership carousel so an open leadership popup tracks auto/manual slide changes.
354+ window . __syncTeamPopupFromLeaderCard = card => {
355+ if ( ! overlay . classList . contains ( 'active' ) ) return ;
356+ if ( overlay . dataset . popupSource !== 'leadership' ) return ;
357+ openPopupFromCard ( card , 'leadership' ) ;
358+ } ;
359+
360+ function stepCorePopup ( dir ) {
361+ const coreItems = [ ...document . querySelectorAll ( '[data-section="core"] .core-grid-item--has-popup' ) ] ;
362+ if ( ! coreItems . length ) return ;
363+ const currentName = ( overlay . dataset . currentName || '' ) . trim ( ) ;
364+ let idx = coreItems . findIndex ( item => ( item . dataset . name || '' ) . trim ( ) === currentName ) ;
365+ if ( idx < 0 ) idx = 0 ;
366+ const nextIdx = ( idx + dir + coreItems . length ) % coreItems . length ;
367+ const item = coreItems [ nextIdx ] ;
368+ openPopup (
369+ item . dataset . name ,
370+ item . dataset . role ,
371+ item . dataset . batch ,
372+ item . dataset . bio ,
373+ ( item . dataset . images || '' ) . split ( '|' ) . filter ( Boolean ) ,
374+ item . dataset . spiritDog || '' ,
375+ 'core'
376+ ) ;
377+ }
378+
379+ if ( prevBtn ) {
380+ prevBtn . addEventListener ( 'click' , ( ) => {
381+ if ( ! overlay . classList . contains ( 'active' ) ) return ;
382+ if ( overlay . dataset . popupSource === 'leadership' ) {
383+ const arrow = document . querySelector ( '.cinema-carousel .cinema-nav-arrow[data-dir="prev"]' ) ;
384+ if ( arrow ) arrow . click ( ) ;
385+ } else if ( overlay . dataset . popupSource === 'core' ) {
386+ stepCorePopup ( - 1 ) ;
387+ }
388+ } ) ;
389+ }
390+
391+ if ( nextBtn ) {
392+ nextBtn . addEventListener ( 'click' , ( ) => {
393+ if ( ! overlay . classList . contains ( 'active' ) ) return ;
394+ if ( overlay . dataset . popupSource === 'leadership' ) {
395+ const arrow = document . querySelector ( '.cinema-carousel .cinema-nav-arrow[data-dir="next"]' ) ;
396+ if ( arrow ) arrow . click ( ) ;
397+ } else if ( overlay . dataset . popupSource === 'core' ) {
398+ stepCorePopup ( 1 ) ;
399+ }
400+ } ) ;
401+ }
402+
403+ const leadershipSection = document . querySelector ( '[data-section="leadership"]' ) ;
404+ if ( leadershipSection ) {
405+ leadershipSection . addEventListener ( 'click' , e => {
406+ const card = e . target . closest ( '.cinema-card' ) ;
407+ if ( ! card ) return ;
408+ openPopupFromCard ( card , 'leadership' ) ;
409+ } ) ;
410+ }
411+
412+ const coreSection = document . querySelector ( '[data-section="core"]' ) ;
413+ if ( coreSection ) {
414+ coreSection . addEventListener ( 'click' , e => {
415+ const item = e . target . closest ( '.core-grid-item--has-popup' ) ;
416+ if ( ! item ) return ;
417+ openPopup (
418+ item . dataset . name ,
419+ item . dataset . role ,
420+ item . dataset . batch ,
421+ item . dataset . bio ,
422+ ( item . dataset . images || '' ) . split ( '|' ) . filter ( Boolean ) ,
423+ item . dataset . spiritDog || '' ,
424+ 'core'
425+ ) ;
426+ } ) ;
427+ }
236428}
237429
238430/* ── Main loader ── */
@@ -295,3 +487,21 @@ async function loadTeam() {
295487 ⚠️ couldn't load team data. try refreshing.</div>` ;
296488 }
297489}
490+
491+ function enableTouchGestures ( carousel ) {
492+ let startX ;
493+ carousel . addEventListener ( 'touchstart' , ( e ) => {
494+ startX = e . touches [ 0 ] . clientX ;
495+ } ) ;
496+ carousel . addEventListener ( 'touchmove' , ( e ) => {
497+ if ( ! startX ) return ;
498+ const diffX = startX - e . touches [ 0 ] . clientX ;
499+ if ( Math . abs ( diffX ) > 50 ) {
500+ const dir = diffX > 0 ? 'next' : 'prev' ;
501+ carousel . querySelector ( `.cinema-nav-arrow[data-dir="${ dir } "]` ) . click ( ) ;
502+ startX = null ;
503+ }
504+ } ) ;
505+ }
506+
507+ document . querySelectorAll ( '.cinema-carousel' ) . forEach ( enableTouchGestures ) ;
0 commit comments