@@ -101,10 +101,96 @@ function initMemorialModals() {
101101 const grid = document . getElementById ( 'memoriam-grid' ) ;
102102 if ( ! grid ) return ;
103103
104+ const isTouch = 'ontouchstart' in window || navigator . maxTouchPoints > 0 ;
105+ const LONG_PRESS_MS = 500 ;
106+ const MOVE_THRESHOLD = 10 ; // px — cancel if finger moves too far
107+
108+ /* ── Touch: long-press toggles .lit, quick tap opens modal ── */
109+ if ( isTouch ) {
110+ let pressTimer = null ;
111+ let startX = 0 , startY = 0 ;
112+ let didLongPress = false ;
113+ let activeCard = null ;
114+
115+ // Show a one-time hint on first visit
116+ const hintKey = 'pawsitive-memoriam-hint-shown' ;
117+ if ( ! sessionStorage . getItem ( hintKey ) ) {
118+ requestAnimationFrame ( ( ) => {
119+ const hint = document . createElement ( 'div' ) ;
120+ hint . className = 'memoriam-hint' ;
121+ hint . textContent = 'hold a card to light a candle 🕯️' ;
122+ document . body . appendChild ( hint ) ;
123+ setTimeout ( ( ) => hint . classList . add ( 'show' ) , 600 ) ;
124+ setTimeout ( ( ) => {
125+ hint . classList . remove ( 'show' ) ;
126+ setTimeout ( ( ) => hint . remove ( ) , 500 ) ;
127+ } , 4000 ) ;
128+ sessionStorage . setItem ( hintKey , '1' ) ;
129+ } ) ;
130+ }
131+
132+ grid . addEventListener ( 'touchstart' , e => {
133+ const card = e . target . closest ( '.dog-card' ) ;
134+ if ( ! card ) return ;
135+ activeCard = card ;
136+ didLongPress = false ;
137+ const touch = e . touches [ 0 ] ;
138+ startX = touch . clientX ;
139+ startY = touch . clientY ;
140+
141+ pressTimer = setTimeout ( ( ) => {
142+ didLongPress = true ;
143+ card . classList . toggle ( 'lit' ) ;
144+ if ( navigator . vibrate ) navigator . vibrate ( 50 ) ;
145+ } , LONG_PRESS_MS ) ;
146+ } , { passive : true } ) ;
147+
148+ grid . addEventListener ( 'touchmove' , e => {
149+ if ( ! pressTimer ) return ;
150+ const touch = e . touches [ 0 ] ;
151+ const dx = Math . abs ( touch . clientX - startX ) ;
152+ const dy = Math . abs ( touch . clientY - startY ) ;
153+ if ( dx > MOVE_THRESHOLD || dy > MOVE_THRESHOLD ) {
154+ clearTimeout ( pressTimer ) ;
155+ pressTimer = null ;
156+ }
157+ } , { passive : true } ) ;
158+
159+ grid . addEventListener ( 'touchend' , e => {
160+ clearTimeout ( pressTimer ) ;
161+ pressTimer = null ;
162+ if ( didLongPress ) {
163+ didLongPress = false ;
164+ return ;
165+ }
166+ if ( activeCard ) openMemorialModal ( activeCard ) ;
167+ activeCard = null ;
168+ } ) ;
169+
170+ grid . addEventListener ( 'touchcancel' , ( ) => {
171+ clearTimeout ( pressTimer ) ;
172+ pressTimer = null ;
173+ didLongPress = false ;
174+ activeCard = null ;
175+ } ) ;
176+
177+ // Suppress native context menu on long press within the grid
178+ grid . addEventListener ( 'contextmenu' , e => {
179+ if ( e . target . closest ( '.dog-card' ) ) e . preventDefault ( ) ;
180+ } ) ;
181+ }
182+
183+ /* ── Desktop: click opens modal (hover handles candle/colour via CSS) ── */
104184 grid . addEventListener ( 'click' , e => {
185+ if ( isTouch ) return ;
105186 const card = e . target . closest ( '.dog-card' ) ;
106187 if ( ! card ) return ;
188+ openMemorialModal ( card ) ;
189+ } ) ;
190+ }
107191
192+ /** Build and show the memorial detail modal */
193+ function openMemorialModal ( card ) {
108194 const dark = document . documentElement . dataset . theme === 'dark' ;
109195 const bg = dark ? ( card . dataset . bgDark || '' ) : ( card . dataset . bgLight || '' ) ;
110196
@@ -121,8 +207,6 @@ function initMemorialModals() {
121207 . map ( t => `<span class="dog-modal-tag">${ t } </span>` )
122208 . join ( '' ) ;
123209
124- /* Modal photo — rendered WITHOUT the grayscale filter because the overlay
125- is appended directly to <body>, outside .memoriam-page-wrap */
126210 const photoSection = card . dataset . image
127211 ? `<div class="dog-modal-photo memorial-modal-photo-wrap">
128212 <img src="${ card . dataset . image } " alt="${ card . dataset . name } " style="opacity:0;transition:opacity 0.3s" onload="this.style.opacity=1">
@@ -156,7 +240,6 @@ function initMemorialModals() {
156240 document . addEventListener ( 'keydown' , e => {
157241 if ( e . key === 'Escape' ) close ( ) ;
158242 } , { signal : ac . signal } ) ;
159- } ) ;
160243}
161244
162245/** Main entry: fetch manifest, load all memorial dogs, render to grid */
0 commit comments