PageSourceSearch

https://shuu.app/script.js

js shuu.app collected 2026-09-24 14:32:17 UTC 8,492 bytes, 252 lines download raw bytes

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.