PageSourceSearch

https://tailorly.co/js/main.js

js tailorly.co collected 2026-09-24 14:37:00 UTC 9,083 bytes, 265 lines download raw bytes

1// Initialize all components when the DOM is loaded
2document.addEventListener('DOMContentLoaded', function() {
3    initializeWebsite();
4});
5
6function initializeWebsite() {
7    // Initialize all components
8    initScrollEffects();
9    initMobileMenu();
10    initParticles();
11    initCursorEffect();
12    initScrollProgress();
13    initSmoothScrolling();
14    initLocalizedPricing();
15}
16
17// Scroll Effects
18function initScrollEffects() {
19    const header = document.querySelector('.main-header');
20    const fadeElements = document.querySelectorAll('.fade-in');
21    
22    // Header scroll effect
23    window.addEventListener('scroll', () => {
24        if (window.scrollY > 100) {
25            header.classList.add('scrolled');
26        } else {
27            header.classList.remove('scrolled');
28        }
29    });
30
31    // Intersection Observer for fade-in animations
32    if ('IntersectionObserver' in window) {
33        const observerOptions = {
34            threshold: 0.1,
35            rootMargin: '0px 0px -50px 0px'
36        };
37
38        const observer = new IntersectionObserver((entries) => {
39            entries.forEach(entry => {
40                if (entry.isIntersecting) {
41                    entry.target.classList.add('visible');
42                }
43            });
44        }, observerOptions);
45
46        fadeElements.forEach(el => observer.observe(el));
47    } else {
48        // Fallback for browsers that don't support IntersectionObserver
49        fadeElements.forEach(el => el.classList.add('visible'));
50    }
51}
52
53// Mobile Menu
54function initMobileMenu() {
55    const toggle = document.querySelector('.mobile-menu-toggle');
56    const menu = document.querySelector('.mobile-menu');
57    
58    if (toggle && menu) {
59        toggle.addEventListener('click', () => {
60            toggle.classList.toggle('active');
61            menu.classList.toggle('active');
62        });
63
64        // Close menu when clicking links
65        menu.querySelectorAll('a').forEach(link => {
66            link.addEventListener('click', () => {
67                toggle.classList.remove('active');
68                menu.classList.remove('active');
69            });
70        });
71    }
72}
73
74// Floating Particles
75function initParticles() {
76    const particlesContainer = document.getElementById('particles');
77    if (!particlesContainer) return;
78    
79    const particleCount = 15;
80    
81    for (let i = 0; i < particleCount; i++) {
82        createParticle();
83    }
84    
85    function createParticle() {
86        const particle = document.createElement('div');
87        particle.className = 'particle';
88        
89        // Random size and position
90        const size = Math.random() * 6 + 2;
91        particle.style.width = size + 'px';
92        particle.style.height = size + 'px';
93        particle.style.left = Math.random() * 100 + '%';
94        particle.style.animationDuration = (Math.random() * 10 + 15) + 's';
95        particle.style.animationDelay = Math.random() * 5 + 's';
96        
97        particlesContainer.appendChild(particle);
98        
99        // Remove and recreate particle after animation
100        setTimeout(() => {
101            particle.remove();
102            createParticle();
103        }, (15 + Math.random() * 10) * 1000);
104    }
105}
106
107// Cursor Glow Effect
108function initCursorEffect() {
109    const cursor = document.querySelector('.cursor-glow');
110    if (!cursor) return;
111    
112    document.addEventListener('mousemove', (e) => {
113        cursor.style.left = e.clientX + 'px';
114        cursor.style.top = e.clientY + 'px';
115    });
116}
117
118// Scroll Progress Bar
119function initScrollProgress() {
120    const progressBar = document.querySelector('.scroll-progress');
121    if (!progressBar) return;
122    
123    window.addEventListener('scroll', () => {
124        const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
125        const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
126        const scrollPercent = (scrollTop / docHeight) * 100;
127        
128        progressBar.style.width = scrollPercent + '%';
129    });
130}
131
132// Smooth Scrolling Function
133function initSmoothScrolling() {
134    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
135        anchor.addEventListener('click', function (e) {
136            // Skip if it's an empty hash or just "#"
137            if (this.getAttribute('href') === '#' || this.getAttribute('href') === '') return;
138            
139            e.preventDefault();
140            const targetId = this.getAttribute('href');
141            const target = document.querySelector(targetId);
142            
143            if (target) {
144                const headerHeight = document.querySelector('.main-header').offsetHeight;
145                const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight;
146                
147                window.scrollTo({
148                    top: targetPosition,
149                    behavior: 'smooth'
150                });
151                
152                // Update URL hash without jumping
153                history.pushState(null, null, targetId);
154            }
155        });
156    });
157}
158
159// Localized Pricing (free: locale + exchangerate.host)
160function initLocalizedPricing() {
161    const priceNodes = document.querySelectorAll('[data-inr]');
162    if (!priceNodes.length) return;
163
164    const forced = getCurrencyOverride();
165    const targetCurrency = forced || detectCurrencyFromLocale();
166    if (targetCurrency === 'INR') return;
167
168    const rateUrl = `https://api.exchangerate.host/latest?base=INR&symbols=${encodeURIComponent(targetCurrency)}`;
169
170    fetch(rateUrl)
171        .then(response => response.ok ? response.json() : null)
172        .then(data => {
173            const rate = data?.rates?.[targetCurrency];
174            if (!rate) return;
175
176            priceNodes.forEach(node => {
177                const baseInr = parseFloat(node.dataset.inr);
178                if (Number.isNaN(baseInr)) return;
179
180                const prefix = node.dataset.prefix || '';
181                const suffix = node.dataset.suffix || '';
182                const fractionDigits = getCurrencyFractionDigits(targetCurrency);
183                const formatted = new Intl.NumberFormat(undefined, {
184                    style: 'currency',
185                    currency: targetCurrency,
186                    minimumFractionDigits: fractionDigits,
187                    maximumFractionDigits: fractionDigits
188                }).format(baseInr * rate);
189
190                node.textContent = `${prefix}${formatted}${suffix}`;
191            });
192        })
193        .catch(() => {
194            // Silently fallback to INR on any error
195        });
196}
197
198function getCurrencyOverride() {
199    try {
200        const params = new URLSearchParams(window.location.search);
201        const urlParam = params.get('currency') || params.get('forceCurrency');
202        const stored = localStorage.getItem('currencyOverride');
203        const candidate = (urlParam || stored || '').toUpperCase().trim();
204        if (/^[A-Z]{3}$/.test(candidate)) {
205            if (urlParam) {
206                localStorage.setItem('currencyOverride', candidate);
207            }
208            return candidate;
209        }
210    } catch (e) {
211        // Ignore storage issues
212    }
213    return '';
214}
215
216function detectCurrencyFromLocale() {
217    const locale = navigator.language || navigator.userLanguage || '';
218    const region = (locale.split('-')[1] || locale.split('_')[1] || '').toUpperCase();
219    const regionToCurrency = {
220        US: 'USD', CA: 'CAD', MX: 'MXN', BR: 'BRL', AR: 'ARS',
221        GB: 'GBP', IE: 'EUR', FR: 'EUR', DE: 'EUR', ES: 'EUR', IT: 'EUR', NL: 'EUR', BE: 'EUR', AT: 'EUR', PT: 'EUR', FI: 'EUR', GR: 'EUR',
222        AU: 'AUD', NZ: 'NZD', SG: 'SGD', HK: 'HKD', JP: 'JPY', KR: 'KRW',
223        AE: 'AED', SA: 'SAR', QA: 'QAR', KW: 'KWD', BH: 'BHD', OM: 'OMR',
224        ZA: 'ZAR', NG: 'NGN', KE: 'KES', EG: 'EGP',
225        CH: 'CHF', NO: 'NOK', SE: 'SEK', DK: 'DKK',
226        RU: 'RUB', TR: 'TRY', IL: 'ILS', ID: 'IDR', PH: 'PHP', MY: 'MYR', TH: 'THB'
227    };
228
229    return regionToCurrency[region] || 'INR';
230}
231
232function getCurrencyFractionDigits(code) {
233    try {
234        const nf = new Intl.NumberFormat(undefined, { style: 'currency', currency: code });
235        const opts = nf.resolvedOptions();
236        if (typeof opts.minimumFractionDigits === 'number') {
237            const min = opts.minimumFractionDigits;
238            const max = typeof opts.maximumFractionDigits === 'number' ? opts.maximumFractionDigits : min;
239            return Math.max(0, Math.min(Math.max(min, max), 4));
240        }
241    } catch (e) {
242        // Fallback to defaults below
243    }
244
245    const zeroDecimalCurrencies = ['JPY', 'KRW'];
246    return zeroDecimalCurrencies.includes(code) ? 0 : 2;
247}
248
249// Performance Optimization
250function debounce(func, wait) {
251    let timeout;
252    return function executedFunction(...args) {
253        const later = () => {
254            clearTimeout(timeout);
255            func(...args);
256        };
257        clearTimeout(timeout);
258        timeout = setTimeout(later, wait);
259    };
260}
261
262// Optimize scroll events
263window.addEventListener('scroll', debounce(() => {
264    // Scroll-dependent functions here
265}, 10));

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.