1const BREATHING_COMPLETION_STORAGE_KEY = 'shuu:breathing-completion-count'; 2 3document.addEventListener('DOMContentLoaded', function() { 4 initFeedbackForm(); 5 initAppStoreButtonTracking(); 6 initGooglePlayButtonTracking(); 7 initInlineStoreButtons(); 8 initSupportLine(); 9}); 10 11function initFeedbackForm() { 12 const form = document.getElementById('feedback-form'); 13 if (!form) { 14 return; 15 } 16 17 const platformField = document.getElementById('platform-field'); 18 if (platformField) { 19 const params = new URLSearchParams(window.location.search); 20 if (params.has('ios')) { 21 platformField.value = 'ios'; 22 } else if (params.has('android')) { 23 platformField.value = 'android'; 24 } 25 } 26 27 const successMessage = document.getElementById('feedback-success'); 28 const details = document.querySelector('.feedback-section details'); 29 30 form.addEventListener('submit', function(event) { 31 event.preventDefault(); 32 33 const formData = new FormData(form); 34 35 fetch('/', { 36 method: 'POST', 37 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 38 body: new URLSearchParams(formData).toString() 39 }) 40 .then(() => { 41 form.style.display = 'none'; 42 if (successMessage) { 43 successMessage.style.display = 'block'; 44 } 45 if (details) { 46 details.open = true; 47 } 48 }) 49 .catch(error => { 50 console.error('Error:', error); 51 alert('Sorry, there was an error submitting your feedback. Please try again.'); 52 }); 53 }); 54} 55 56function initAppStoreButtonTracking() { 57 const appStoreButton = document.getElementById('app-store-link'); 58 if (!appStoreButton) { 59 return; 60 } 61 62 appStoreButton.addEventListener('click', function() { 63 if (typeof gtag === 'function') { 64 gtag('event', 'get_on_appstore'); 65 } 66 }); 67} 68 69function initGooglePlayButtonTracking() { 70 const googlePlayButton = document.getElementById('google-play-link'); 71 if (!googlePlayButton) { 72 return; 73 } 74 75 googlePlayButton.addEventListener('click', function() { 76 if (typeof gtag === 'function') { 77 gtag('event', 'get_on_googleplay'); 78 } 79 }); 80} 81 82function initInlineStoreButtons() { 83 const inlineStoreButtons = document.getElementById('inline-store-buttons'); 84 if (!inlineStoreButtons) { 85 return; 86 } 87 88 const ua = navigator.userAgent; 89 const isIOS = /iP(hone|od|ad)/.test(ua); 90 const isIpadOS = /Macintosh/.test(ua) && navigator.maxTouchPoints > 1; 91 const isSafari = /Safari/.test(ua) && !/(CriOS|FxiOS|EdgiOS|OPiOS|DuckDuckGo|GSA|YaBrowser)/.test(ua); 92 const isSafariOnIOS = (isIOS || isIpadOS) && isSafari; 93 94 if (isSafariOnIOS) { 95 return; 96 } 97 98 inlineStoreButtons.style.display = 'inline-flex'; 99 100 const inlineAppStoreLink = inlineStoreButtons.querySelector('[data-store="appstore"]'); 101 if (inlineAppStoreLink) { 102 inlineAppStoreLink.addEventListener('click', function() { 103 if (typeof gtag === 'function') { 104 gtag('event', 'get_on_appstore'); 105 } 106 }); 107 } 108 109 const inlineGooglePlayLink = inlineStoreButtons.querySelector('[data-store="googleplay"]'); 110 if (inlineGooglePlayLink) { 111 inlineGooglePlayLink.addEventListener('click', function() { 112 if (typeof gtag === 'function') { 113 gtag('event', 'get_on_googleplay'); 114 } 115 }); 116 } 117} 118 119function initSupportLine() { 120 const supportLine = document.querySelector('.support-line'); 121 if (!supportLine) { 122 return; 123 } 124 125 const buttonContainer = supportLine.querySelector('[data-support-button]'); 126 let buttonLoaded = false; 127 let fallbackCompletionCount = 0; 128 129 function incrementCompletionCount() { 130 try { 131 const rawValue = localStorage.getItem(BREATHING_COMPLETION_STORAGE_KEY); 132 let count = rawValue ? parseInt(rawValue, 10) : 0; 133 if (Number.isNaN(count)) { 134 count = 0; 135 } 136 count += 1; 137 localStorage.setItem(BREATHING_COMPLETION_STORAGE_KEY, Str
137ing(count)); 138 return count; 139 } catch (error) { 140 fallbackCompletionCount += 1; 141 return fallbackCompletionCount; 142 } 143 } 144 145 function loadSupportButton() { 146 if (buttonLoaded || !buttonContainer) { 147 console.log('Support button load skipped:', { buttonLoaded, hasContainer: !!buttonContainer }); 148 return; 149 } 150 151 console.log('Loading Buy Me a Coffee button...'); 152 buttonContainer.innerHTML = ''; 153 154 // Create Buy Me a Coffee button as a direct link instead of using their script 155 const link = document.createElement('a'); 156 link.href = 'https://www.buymeacoffee.com/O92FGY9B03'; 157 link.target = '_blank'; 158 link.rel = 'noopener noreferrer'; 159 link.style.cssText = ` 160 display: inline-flex; 161 align-items: center; 162 gap: 8px; 163 padding: 12px 24px; 164 background-color: transparent; 165 color: #ffffff; 166 font-family: 'Bree', sans-serif; 167 font-size: 16px; 168 font-weight: 600; 169 text-decoration: none; 170 border-radius: 8px; 171 border: 2px solid rgba(255, 255, 255, 0.8); 172 transition: transform 0.2s ease, border-color 0.2s ease; 173 `; 174 175 link.innerHTML = '<span style="font-size: 20px;">ð</span> buy me a coffee'; 176 177 // Add hover effect 178 link.addEventListener('mouseenter', () => { 179 link.style.transform = 'translateY(-2px)'; 180 link.style.borderColor = 'rgba(255, 255, 255, 1)'; 181 }); 182 183 link.addEventListener('mouseleave', () => { 184 link.style.transform = 'translateY(0)'; 185 link.style.borderColor = 'rgba(255, 255, 255, 0.8)'; 186 }); 187 188 buttonContainer.appendChild(link); 189 buttonLoaded = true; 190 console.log('Buy Me a Coffee button created successfully'); 191 } 192 193 function revealSupportLine() { 194 supportLine.hidden = false; 195 supportLine.classList.add('is-visible'); 196 } 197 198 const supportBadges = supportLine.querySelectorAll('[data-support-store]'); 199 supportBadges.forEach(function(badge) { 200 badge.addEventListener('click', function() { 201 if (typeof gtag !== 'function') { 202 return; 203 } 204 const store = badge.getAttribute('data-support-store'); 205 if (store === 'appstore') { 206 gtag('event', 'get_on_appstore', { event_label: 'exercise_widget' }); 207 } else if (store === 'googleplay') { 208 gtag('event', 'get_on_googleplay', { event_label: 'exercise_widget' }); 209 } 210 }); 211 }); 212 213 const path = window.location.pathname; 214 const isHomepage = path === '/' || path === '/index.html'; 215 216 document.addEventListener('shuu:breathing-session-complete', () => { 217 const count = incrementCompletionCount(); 218 console.log('Breathing session complete. Total completions:', count); 219 if (isHomepage || (count > 0 && count % 3 === 0)) { 220 console.log('Showing support line'); 221 loadSupportButton(); 222 revealSupportLine(); 223 } 224 }); 225 226 // Expose test functions for debugging 227 window.shuuTestSupportLine = function() { 228 console.log('TEST MODE: Forcing support line to show'); 229 loadSupportButton(); 230 revealSupportLine(); 231 }; 232 233 window.shuuSetCompletionCount = function(count) { 234 try { 235 localStorage.setItem(BREATHING_COMPLETION_STORAGE_KEY, String(count)); 236 console.log(`Completion count set to ${count}. Complete one more exercise to trigger support line.`); 237 } catch (error) { 238 console.error('Failed to set completion count:', error); 239 } 240 }; 241 242 window.shuuGetCompletionCount = function() { 243 try { 244 const count = parseInt(localStorage.getItem(BREATHING_COMPLETION_STORAGE_KEY) || '0', 10); 245 console.log('Current completion count:', count); 246 return count; 247 } catch (error) { 248 console.error('Failed to get completion count:', error); 249 return 0; 250 } 251 }; 252}
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.