PageSourceSearch

https://www.loc-atr.me/script.js

js loc-atr.me collected 2026-10-02 02:00:22 UTC 22,273 bytes, 566 lines download raw bytes

1"use strict";
2const getConfig = () => {
3    const firebaseConfig = window.__FIREBASE_CONFIG__ || {
4        apiKey: "AIzaSyCWNfG7vP8swLWC_dZMQFJ0CHqlALEE0Hs",
5        authDomain: "geotap-441a7.firebaseapp.com",
6        projectId: "geotap-441a7",
7        storageBucket: "geotap-441a7.firebasestorage.app",
8        messagingSenderId: "541755752861",
9        appId: "1:541755752861:web:4028ca25b676f262a92f74",
10        measurementId: "G-C89EQ4L3FP"
11    };
12    const stripeConfig = window.__STRIPE_CONFIG__ || { publicKey: '' };
13    const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
14    const apiConfig = {
15        baseUrl: isDevelopment ? 'http://localhost:3000' : 'https://www.loc-atr.me'
16    };
17    console.log('🔧 Configuration loaded:', {
18        stripeConfigured: !!stripeConfig.publicKey,
19        environment: isDevelopment ? 'development' : 'production',
20        apiBaseUrl: apiConfig.baseUrl
21    });
22    return { firebaseConfig, stripeConfig, apiConfig };
23};
24function initializeWebsite() {
25    console.log('Initializing Locatr website...');
26    setupMobileNavigation();
27    setupSmoothScrolling();
28    setupNavbarScrollEffects();
29    setupIntersectionObservers();
30    setupExtensionPreview();
31    setupSearchDemo();
32    setupStatsAnimation();
33    setupPricingCards();
34    setupFeatureCards();
35    setupButtonEffects();
36    setupDownloadButtons();
37    setupContactForm();
38    console.log('Website initialization complete');
39}
40function setupMobileNavigation() {
41    const navToggle = document.querySelector('.nav-toggle');
42    const navLinks = document.querySelector('.nav-links');
43    if (navToggle && navLinks) {
44        navToggle.addEventListener('click', () => {
45            navLinks.classList.toggle('active');
46            navToggle.classList.toggle('active');
47        });
48    }
49}
50function setupSmoothScrolling() {
51    const navLinksElements = document.querySelectorAll('a[href^="#"]');
52    navLinksElements.forEach((link) => {
53        link.addEventListener('click', (e) => {
54            e.preventDefault();
55            const targetId = link.getAttribute('href');
56            const targetSection = document.querySelector(targetId);
57            if (targetSection) {
58                const offsetTop = targetSection.offsetTop - 70;
59                window.scrollTo({
60                    top: offsetTop,
61                    behavior: 'smooth'
62                });
63                const navLinks = document.querySelector('.nav-links');
64                const navToggle = document.querySelector('.nav-toggle');
65                if (navLinks && navLinks.classList.contains('active')) {
66                    navLinks.classList.remove('active');
67                    navToggle?.classList.remove('active');
68                }
69            }
70        });
71    });
72}
73function setupNavbarScrollEffects() {
74    const navbar = document.querySelector('.navbar');
75    let lastScrollTop = 0;
76    if (!navbar)
77        return;
78    window.addEventListener('scroll', () => {
79        const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
80        if (scrollTop > 50) {
81            navbar.classList.add('scrolled');
82        }
83        else {
84            navbar.classList.remove('scrolled');
85        }
86        if (scrollTop > lastScrollTop && scrollTop > 100) {
87            navbar.style.transform = 'translateY(-100%)';
88        }
89        else {
90            navbar.style.transform = 'translateY(0)';
91        }
92        lastScrollTop = scrollTop;
93    });
94}
95function setupIntersectionObservers() {
96    const observerOptions = {
97        threshold: 0.1,
98        rootMargin: '0px 0px -50px 0px'
99    };
100    const observer = new IntersectionObserver((entries) => {
101        entries.forEach((entry) => {
102            if (entry.isIntersecting) {
103                entry.target.classList.add('animate-in');
104            }
105        });
106    }, observerOptions);
107    const animateElements = document.querySelectorAll('.feature-card, .step, .pricing-card');
108    animateElements.forEach((el) => {
109        observer.observe(el);
110    });
111}
112function setupExtensionPreview() {
113    const extensionPreview = document.querySelector('.extension-preview');
114    if (extensionPreview) {
115        extensionPreview.addEventListener('mouseenter', function () {
116            this.style.transform = 'perspective(1000px) rotateY(-5deg) rotateX(2deg) scale(1.02)';
117        });
118        extensionPreview.addEventListener('mouseleave', function () {
119            this.style.transform = 'perspective(1000px) rotateY(-15deg) rotateX(5deg) scale(1)';
120        });
121    }
122}
123function setupSearchDemo() {
124    const searchInput = document.querySelector('.search-demo input');
125    if (searchInput) {
126        const originalValue = searchInput.value;
127        searchInput.value = '';
128        let i = 0;
129        const typeWriter = () => {
130            if (i < originalValue.length) {
131                searchInput.value += originalValue.charAt(i);
132                i++;
133                setTimeout(typeWriter, 100);
134            }
135        };
136        setTimeout(typeWriter, 2000);
137    }
138}
139function setupStatsAnimation() {
140    const statsNumbers = document.querySelectorAll('.stat-number');
141    const animateStats = () => {
142        statsNumbers.forEach((stat) => {
143            const target = parseInt(stat.textContent?.replace(/[^\d]/g, '') || '0');
144            const increment = target / 100;
145            let current = 0;
146            const config = {
147                increment,
148                target,
149                hasK: stat.textContent?.includes('K') || false,
150                hasStar: stat.textContent?.includes('★') || false
151            };
152            const updateCounter = () => {
153                if (current < config.target) {
154                    current += config.increment;
155                    if (config.hasK) {
156                        stat.textContent = Math.ceil(current / 1000) + 'K+';
157                    }
158                    else if (config.hasStar) {
159                        stat.textContent = (current / 10).toFixed(1) + '★';
160                    }
161                    else {
162                        stat.textContent = Math.ceil(current / 1000) + 'K+';
163                    }
164                    requestAnimationFrame(updateCounter);
165                }
166            };
167            updateCounter();
168        });
169    };
170    const heroSection = document.querySelector('.hero');
171    if (heroSection) {
172        const heroObserver = new IntersectionObserver((entries) => {
173            entries.forEach((entry) => {
174                if (entry.isIntersecting) {
175                    setTimeout(animateStats, 1000);
176                    heroObserver.unobserve(entry.target);
177                }
178            });
179        }, { threshold: 0.5 });
180        heroObserver.observe(heroSection);
181    }
182}
183function setupPricingCards() {
184    const pricingCards = document.querySelectorAll('.pricing-card');
185    pricingCards.forEach((card) => {
186        card.addEventListener('mouseenter', function () {
187            this.style.transform = 'translateY(-10px) scale(1.02)';
188        });
189        card.addEventListener('mouseleave', function () {
190            if (this.classList.contains('featured')) {
191                this.style.transform = 'scale(1.05)';
192            }
193            else {
194                this.style.transform = 'translateY(0) scale(1)';
195            }
196        });
197    });
198}
199function setupFeatureCards() {
200    const featureCards = document.querySelectorAll('.feature-card');
201    const featureObserver = new IntersectionObserver((entries) => {
202        entries.forEach((entry, index) => {
203            if (entry.isIntersecting) {
204                setTimeout(() => {
205                    const target = entry.target;
206                    target.style.opacity = '1';
207                    target.style.transform = 'translateY(0)';
208                }, index * 100);
209                featureObserver.unobserve(entry.target);
210            }
211        });
212    }, { threshold: 0.1 });
213    featureCards.forEach((card) => {
214        card.style.opacity = '0';
215        card.style.transform = 'translateY(30px)';
216        card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
217        featureObserver.observe(card);
218    });
219}
220function setupButtonEffects() {
221    const buttons = document.querySelectorAll('.btn');
222    buttons.forEach((button) => {
223        button.addEventListener('click', function (e) {
224            const ripple = document.createElement('span');
225            const rect = this.getBoundingClientRect();
226            const size = Math.max(rect.width, rect.height);
227            const x = e.clientX - rect.left - size / 2;
228            const y = e.clientY - rect.top - size / 2;
229            ripple.style.width = ripple.style.height = size + 'px';
230            ripple.style.left = x + 'px';
231            ripple.style.top = y + 'px';
232            ripple.classList.add('ripple');
233            this.appendChild(ripple);
234            setTimeout(() => {
235                ripple.remove();
236            }, 600);
237        });
238    });
239}
240function setupDownloadButtons() {
241    const downloadButtons = document.querySelectorAll('[data-action="download"]');
242    downloadButtons.forEach((button) => {
243        button.addEventListener('click', (e) => {
244            e.preventDefault();
245            if (typeof gtag !== 'undefined') {
246                gtag('event', 'download_extension', {
247                    event_category: 'engagement',
248                    event_label: 'chrome_extension'
249                });
250            }
251            window.open('https://chrome.google.com/webstore/detail/locatr/extension-id', '_blank');
252        });
253    });
254}
255function setupContactForm() {
256    const contactForm = document.querySelector('#contact-form');
257    if (contactForm) {
258        contactForm.addEventListener('submit', async (e) => {
259            e.preventDefault();
260            const formData = new FormData(contactForm);
261            const data = {
262                name: formData.get('name'),
263                email: formData.get('email'),
264                message: formData.get('message')
265            };
266            try {
267                console.log('Contact form submission:', data);
268                showNotification('Thank you for your message! We\'ll get back to you soon.', 'success');
269                contactForm.reset();
270            }
271            catch (error) {
272                console.error('Error submitting contact form:', error);
273                showNotification('There was an error sending your message. Please try again.', 'error');
274            }
275        });
276    }
277}
278function showNotification(message, type = 'info') {
279    const notification = document.createElement('div');
280    notification.className = `notification notification-${type}`;
281    notification.textContent = message;
282    Object.assign(notification.style, {
283        position: 'fixed',
284        top: '20px',
285        right: '20px',
286        padding: '15px 20px',
287        borderRadius: '5px',
288        color: 'white',
289        backgroundColor: type === 'success' ? '#4CAF50' : type === 'error' ? '#f44336' : '#2196F3',
290        zIndex: '10000',
291        boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
292        opacity: '0',
293        transform: 'translateX(100%)',
294        transition: 'all 0.3s ease'
295    });
296    document.body.appendChild(notification);
297    setTimeout(() => {
298        notification.style.opacity = '1';
299        notification.style.transform = 'translateX(0)';
300    }, 100);
301    setTimeout(() => {
302        notification.style.opacity = '0';
303        notification.style.transform = 'translateX(100%)';
304        setTimeout(() => {
305            notification.remove();
306        }, 300);
307    }, 5000);
308}
309function debounce(func, wait) {
310    let timeout;
311    return function executedFunction(...args) {
312        const later = () => {
313            clearTimeout(timeout);
314            func(...args);
315        };
316        clearTimeout(timeout);
317        timeout = setTimeout(later, wait);
318    };
319}
320const optimizedScrollHandler = debounce(() => {
321}, 16);
322function addRippleStyles() {
323    if (document.querySelector('#ripple-styles'))
324        return;
325    const style = document.createElement('style');
326    style.id = 'ripple-styles';
327    style.textContent = `
328        .btn { position: relative; overflow: hidden; }
329        .ripple {
330            position: absolute;
331            background: rgba(255,255,255,0.3);
332            border-radius: 50%;
333            transform: scale(0);
334            animation: ripple 0.6s linear;
335            pointer-events: none;
336        }
337        @keyframes ripple {
338            to { transform: scale(4); opacity: 0; }
339        }
340    `;
341    document.head.appendChild(style);
342}
343document.addEventListener('DOMContentLoaded', () => {
344    console.log('[Website] DOM loaded, initializing...');
345    initializeWebsite();
346    addRippleStyles();
347    setupPurchaseSystem();
348});
349document.addEventListener('visibilitychange', () => {
350    if (document.hidden) {
351        console.log('Tab hidden - pausing animations');
352    }
353    else {
354        console.log('Tab visible - resuming animations');
355    }
356});
357let currentUser = null;
358let stripe = null;
359let auth = null;
360function initializePurchaseSystem() {
361    console.log('[Purchase] Initializing purchase system...');
362    const { firebaseConfig, stripeConfig } = getConfig();
363    if (typeof firebase !== 'undefined') {
364        try {
365            const app = firebase.initializeApp(firebaseConfig);
366            auth = firebase.auth();
367            auth.onAuthStateChanged((user) => {
368                currentUser = user;
369                console.log('[Purchase] Auth state changed:', user ? user.email : 'Not logged in');
370                updatePurchaseButtons();
371            });
372            console.log('[Purchase] Firebase initialized successfully');
373        }
374        catch (error) {
375            console.error('[Purchase] Firebase initialization error:', error);
376        }
377    }
378    else {
379        console.warn('[Purchase] Firebase not available');
380    }
381    if (typeof Stripe !== 'undefined') {
382        if (stripeConfig.publicKey) {
383            try {
384                stripe = Stripe(stripeConfig.publicKey);
385                console.log('[Purchase] Stripe initialized successfully');
386                console.log('[Purchase] Stripe key prefix:', stripeConfig.publicKey.substring(0, 8) + '...');
387                console.log('[Purchase] Is production key:', stripeConfig.publicKey.startsWith('pk_live_'));
388            }
389            catch (error) {
390                console.error('[Purchase] Stripe initialization error:', error);
391            }
392        }
393        else {
394            console.warn('[Purchase] Stripe public key not configured');
395            console.log('[Purchase] stripeConfig:', stripeConfig);
396        }
397    }
398    else {
399        console.warn('[Purchase] Stripe not available');
400    }
401}
402function updatePurchaseButtons() {
403    const purchaseButtons = document.querySelectorAll('.purchase-btn');
404    console.log('Updating purchase buttons, found:', purchaseButtons.length);
405    purchaseButtons.forEach((button) => {
406        if (currentUser) {
407            button.disabled = false;
408            const originalText = button.textContent?.replace(' (Sign in required)', '') || '';
409            button.textContent = originalText;
410        }
411        else {
412            button.disabled = false;
413            const originalText = button.textContent?.replace(' (Sign in required)', '') || '';
414            button.textContent = originalText + ' (Sign in required)';
415        }
416    });
417}
418async function handlePurchase(tier, price, credits) {
419    console.log(`Purchase initiated: ${tier} pack ($${price} for ${credits} searches)`);
420    if (!currentUser) {
421        const confirmed = confirm('Please sign in first to make a purchase. Click OK to open the account page.');
422        if (confirmed) {
423            window.open('https://www.loc-atr.me/account.html', '_blank');
424        }
425        return;
426    }
427    const { apiConfig, stripeConfig } = getConfig();
428    const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
429    if (isDevelopment && !stripeConfig.publicKey) {
430        const confirmed = confirm(`Development Mode Detected!\n\n` +
431            `The purchase would be for: ${tier} pack ($${price} for ${credits} searches)\n\n` +
432            `To test payments:\n` +
433            `1. Start API server: cd server && npm run dev:ts\n` +
434            `2. Or click OK to simulate payment completion`);
435        if (confirmed) {
436            console.log('[Purchase] Development mode: Simulating successful payment');
437            alert(`✅ Mock Payment Successful!\n\nYou would have purchased:\n${tier} pack - $${price} for ${credits} searches\n\nIn production, this would redirect to Stripe checkout.`);
438        }
439        return;
440    }
441    if (isDevelopment) {
442        try {
443            const healthCheck = await fetch(`${apiConfig.baseUrl}/health`, { method: 'HEAD' });
444            if (!healthCheck.ok) {
445                throw new Error('API server not responding');
446            }
447        }
448        catch (error) {
449            console.warn('[Purchase] API server not available:', error);
450            const confirmed = confirm(`⚠️ Development API Server Not Running\n\n` +
451                `Cannot connect to ${apiConfig.baseUrl}\n\n` +
452                `Options:\n` +
453                `1. Start API server: cd server && npm run dev:ts\n` +
454                `2. Click OK to simulate successful payment\n` +
455                `3. Click Cancel to abort`);
456            if (confirmed) {
457                console.log('[Purchase] Development mode: Simulating payment due to API unavailability');
458                alert(`✅ Mock Payment Successful!\n\nYou would have purchased:\n${tier} pack - $${price} for ${credits} searches\n\nNote: This was simulated because the API server isn't running.\nIn production, this would redirect to Stripe checkout.`);
459            }
460            return;
461        }
462    }
463    try {
464        const token = await currentUser.getIdToken();
465        console.log('[Purchase] Got user token, making API call...');
466        const isProduction = window.location.hostname === 'www.loc-atr.me' ||
467            window.location.hostname === 'loc-atr.me';
468        const baseUrl = isProduction ? 'https://www.loc-atr.me' : window.location.origin;
469        function validateUrl(url) {
470            try {
471                const parsed = new URL(url);
472                return parsed.protocol === 'http:' || parsed.protocol === 'https:';
473            }
474            catch {
475                return false;
476            }
477        }
478        const successUrl = `${baseUrl}/account.html?payment=success`;
479        const cancelUrl = `${baseUrl}/#pricing`;
480        const fallbackUrls = {
481            success: 'https://www.loc-atr.me/account.html?payment=success',
482            cancel: 'https://www.loc-atr.me/#pricing'
483        };
484        const finalSuccessUrl = validateUrl(successUrl) ? successUrl : fallbackUrls.success;
485        const finalCancelUrl = validateUrl(cancelUrl) ? cancelUrl : fallbackUrls.cancel;
486        console.log('Payment Debug:', {
487            windowOrigin: window.location.origin,
488            hostname: window.location.hostname,
489            isProduction: isProduction,
490            baseUrl: baseUrl,
491            generatedUrls: { successUrl, cancelUrl },
492            finalUrls: { finalSuccessUrl, finalCancelUrl },
493            userAgent: navigator.userAgent,
494            timestamp: new Date().toISOString()
495        });
496        const response = await fetch(`${apiConfig.baseUrl}/api/payment/create-checkout-session`, {
497            method: 'POST',
498            headers: {
499                'Authorization': `Bearer ${token}`,
500                'Content-Type': 'application/json'
501            },
502            body: JSON.stringify({
503                tier: tier,
504                successUrl: finalSuccessUrl,
505                cancelUrl: finalCancelUrl
506            })
507        });
508        const data = await response.json();
509        console.log('Checkout session response:', data);
510        if (data.success && data.sessionId) {
511            console.log('Redirecting to Stripe checkout...');
512            if (!stripe) {
513                console.error('[Purchase] Stripe object is null - initialization failed');
514                alert('Payment system error: Stripe not initialized. Please refresh the page and try again.');
515                return;
516            }
517            const { error }
517 = await stripe.redirectToCheckout({
518                sessionId: data.sessionId
519            });
520            if (error) {
521                console.error('Stripe checkout error:', error);
522                alert('Payment failed: ' + error.message);
523            }
524        }
525        else {
526            console.error('Failed to create checkout session:', data);
527            alert('Unable to process payment: ' + (data.error || 'Unknown error'));
528        }
529    }
530    catch (error) {
531        console.error('Purchase error:', error);
532        alert('An error occurred: ' + error.message);
533    }
534}
535function initializePurchaseButtons() {
536    console.log('Initializing purchase buttons...');
537    const purchaseButtons = document.querySelectorAll('.purchase-btn');
538    console.log('Found purchase buttons:', purchaseButtons.length);
539    purchaseButtons.forEach((button, index) => {
540        console.log(`Setting up button ${index}:`, button.dataset);
541        const newButton = button.cloneNode(true);
542        button.parentNode?.replaceChild(newButton, button);
543        newButton.addEventListener('click', (e) => {
544            console.log('Purchase button clicked!', e.target.dataset);
545            e.preventDefault();
546            e.stopPropagation();
547            const tier = newButton.dataset.tier || '';
548            const price = newButton.dataset.price || '';
549            const credits = newButton.dataset.credits || '';
550            console.log('Calling handlePurchase with:', { tier, price, credits });
551            handlePurchase(tier, price, credits);
552        });
553    });
554    updatePurchaseButtons();
555}
556function setupPurchaseSystem() {
557    initializePurchaseSystem();
558    if (document.readyState === 'loading') {
559        document.addEventListener('DOMContentLoaded', initializePurchaseButtons);
560    }
561    else {
562        initializePurchaseButtons();
563    }
564    setTimeout(initializePurchaseButtons, 1000);
565}
566window.handlePurchase = handlePurchase;

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.