// QR Rapido Speed Generator class QRRapidoGenerator { constructor() { this.startTime = 0; this.currentQR = null; this.timerInterval = null; this.languageStrings = { 'pt-BR': { tagline: 'Gere QR codes em segundos!', generating: 'Gerando...', generated: 'Gerado em', seconds: 's', ultraFast: 'Geração ultra rápida!', fast: 'Geração rápida!', normal: 'Geração normal', error: 'Erro na geração. Tente novamente.', success: 'QR Code salvo no histórico!' }, 'es': { tagline: '¡Genera códigos QR en segundos!', generating: 'Generando...', generated: 'Generado en', seconds: 's', ultraFast: '¡Generación ultra rápida!', fast: '¡Generación rápida!', normal: 'Generación normal', error: 'Error en la generación. Inténtalo de nuevo.', success: '¡Código QR guardado en el historial!' }, 'en': { tagline: 'Generate QR codes in seconds!', generating: 'Generating...', generated: 'Generated in', seconds: 's', ultraFast: 'Ultra fast generation!', fast: 'Fast generation!', normal: 'Normal generation', error: 'Generation error. Please try again.', success: 'QR Code saved to history!' } }; this.currentLang = localStorage.getItem('qrrapido-lang') || 'pt-BR'; this.initializeEvents(); this.checkAdFreeStatus(); this.updateLanguage(); this.updateStatsCounters(); } initializeEvents() { // Form submission with timer const form = document.getElementById('qr-speed-form'); if (form) { form.addEventListener('submit', this.generateQRWithTimer.bind(this)); } // Quick style selection document.querySelectorAll('input[name="quick-style"]').forEach(radio => { radio.addEventListener('change', this.applyQuickStyle.bind(this)); }); // Logo upload feedback const logoUpload = document.getElementById('logo-upload'); if (logoUpload) { logoUpload.addEventListener('change', this.handleLogoSelection.bind(this)); } // Corner style validation for non-premium users const cornerStyle = document.getElementById('corner-style'); if (cornerStyle) { cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this)); } // QR type change with hints const qrType = document.getElementById('qr-type'); if (qrType) { qrType.addEventListener('change', this.updateContentHints.bind(this)); } // Language selector document.querySelectorAll('[data-lang]').forEach(link => { link.addEventListener('click', this.changeLanguage.bind(this)); }); // Real-time preview for premium users if (this.isPremiumUser()) { this.setupRealTimePreview(); } // Download buttons this.setupDownloadButtons(); // Share functionality this.setupShareButtons(); // Save to history const saveBtn = document.getElementById('save-to-history'); if (saveBtn) { saveBtn.addEventListener('click', this.saveToHistory.bind(this)); } } setupDownloadButtons() { const pngBtn = document.getElementById('download-png'); const svgBtn = document.getElementById('download-svg'); const pdfBtn = document.getElementById('download-pdf'); if (pngBtn) pngBtn.addEventListener('click', () => this.downloadQR('png')); if (svgBtn) svgBtn.addEventListener('click', () => this.downloadQR('svg')); if (pdfBtn) pdfBtn.addEventListener('click', () => this.downloadQR('pdf')); } setupShareButtons() { // Check if Web Share API is supported and show/hide native share option if (navigator.share && this.isMobileDevice()) { const nativeShareOption = document.getElementById('native-share-option'); if (nativeShareOption) { nativeShareOption.classList.remove('d-none'); } } // Show save to gallery option on mobile if (this.isMobileDevice()) { const saveGalleryOption = document.getElementById('save-gallery-option'); if (saveGalleryOption) { saveGalleryOption.classList.remove('d-none'); } } // Add event listeners to share buttons const shareButtons = { 'native-share': () => this.shareNative(), 'share-whatsapp': () => this.shareWhatsApp(), 'share-telegram': () => this.shareTelegram(), 'share-email': () => this.shareEmail(), 'copy-qr-link': () => this.copyToClipboard(), 'save-to-gallery': () => this.saveToGallery() }; Object.entries(shareButtons).forEach(([id, handler]) => { const button = document.getElementById(id); if (button) { button.addEventListener('click', (e) => { e.preventDefault(); handler(); }); } }); } isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform)); } async shareNative() { if (!this.currentQR || !navigator.share) return; try { // Create a blob from the base64 image const base64Response = await fetch(`data:image/png;base64,${this.currentQR.base64}`); const blob = await base64Response.blob(); const file = new File([blob], 'qrcode.png', { type: 'image/png' }); const shareData = { title: 'QR Code - QR Rapido', text: 'QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!', url: window.location.origin, files: [file] }; // Check if files can be shared if (navigator.canShare && navigator.canShare(shareData)) { await navigator.share(shareData); } else { // Fallback without files await navigator.share({ title: shareData.title, text: shareData.text, url: shareData.url }); } this.trackShareEvent('native'); } catch (error) { console.error('Error sharing:', error); if (error.name !== 'AbortError') { this.showError('Erro ao compartilhar. Tente outro método.'); } } } shareWhatsApp() { if (!this.currentQR) return; const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil! ' + window.location.origin); const url = `https://wa.me/?text=${text}`; if (this.isMobileDevice()) { window.open(url, '_blank'); } else { window.open(`https://web.whatsapp.com/send?text=${text}`, '_blank'); } this.trackShareEvent('whatsapp'); } shareTelegram() { if (!this.currentQR) return; const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!'); const url = encodeURIComponent(window.location.origin); const telegramUrl = `https://t.me/share/url?url=${url}&text=${text}`; window.open(telegramUrl, '_blank'); this.trackShareEvent('telegram'); } shareEmail() { if (!this.currentQR) return; const subject = encodeURIComponent('QR Code - QR Rapido'); const body = encodeURIComponent(`Olá!\n\nCompartilho com você este QR Code gerado no QR Rapido, o gerador mais rápido do Brasil!\n\nAcesse: ${window.location.origin}\n\nAbraços!`); const mailtoUrl = `mailto:?subject=${subject}&body=${body}`; window.location.href = mailtoUrl; this.trackShareEvent('email'); } async copyToClipboard() { if (!this.currentQR) return; try { const shareText = `QR Code gerado com QR Rapido - ${window.location.origin}`; if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(shareText); } else { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = shareText; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); document.execCommand('copy'); textArea.remove(); } this.showSuccess('Link copiado para a área de transferência!'); this.trackShareEvent('copy'); } catch (error) { console.error('Error copying to clipboard:', error); this.showError('Erro ao copiar link. Tente novamente.'); } } async saveToGallery() { if (!this.currentQR) return; try { // Create a blob from the base64 image const base64Response = await fetch(`data:image/png;base64,${this.currentQR.base64}`); const blob = await base64Response.blob(); // Create download link const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `qrrapido-${new Date().toISOString().slice(0,10)}-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); this.showSuccess('QR Code baixado! Verifique sua galeria/downloads.'); this.trackShareEvent('gallery'); } catch (error) { console.error('Error saving to gallery:', error); this.showError('Erro ao salvar na galeria. Tente novamente.'); } } trackShareEvent(method) { // Google Analytics if (typeof gtag !== 'undefined') { gtag('event', 'qr_shared', { 'share_method': method, 'user_type': this.isPremiumUser() ? 'premium' : 'free', 'language': this.currentLang }); } // Internal tracking console.log(`QR Code shared via ${method}`); } async generateQRWithTimer(e) { e.preventDefault(); // Validation if (!this.validateForm()) return; // Start timer this.startTime = performance.now(); this.showGenerationStarted(); const requestData = this.collectFormData(); try { // Build fetch options based on request type const fetchOptions = { method: 'POST', body: requestData.isMultipart ? requestData.data : JSON.stringify(requestData.data) }; // Add Content-Type header only for JSON requests (FormData sets its own) if (!requestData.isMultipart) { fetchOptions.headers = { 'Content-Type': 'application/json' }; } const response = await fetch(requestData.endpoint, fetchOptions); if (!response.ok) { const errorData = await response.json().catch(() => ({})); if (response.status === 429) { this.showUpgradeModal('Limite de QR codes atingido! Upgrade para QR Rapido Premium e gere códigos ilimitados.'); return; } if (response.status === 400 && errorData.requiresPremium) { this.showUpgradeModal(errorData.error || 'Logo personalizado é exclusivo do plano Premium.'); return; } throw new Error(errorData.error || 'Erro na geração'); } const result = await response.json(); if (!result.success) { if (result.requiresPremium) { this.showUpgradeModal(result.error || 'Logo personalizado é exclusivo do plano Premium.'); return; } throw new Error(result.error || 'Erro desconhecido'); } const generationTime = ((performance.now() - this.startTime) / 1000).toFixed(1); this.displayQRResult(result, generationTime); this.updateSpeedStats(generationTime); this.trackGenerationEvent(requestData.data.type || requestData.data.get('type'), generationTime); } catch (error) { console.error('Erro ao gerar QR:', error); this.showError(this.languageStrings[this.currentLang].error); } finally { this.hideGenerationLoading(); } } validateForm() { const qrType = document.getElementById('qr-type').value; const qrContent = document.getElementById('qr-content').value.trim(); if (!qrType) { this.showError('Selecione o tipo de QR code'); return false; } if (!qrContent) { this.showError('Digite o conteúdo do QR code'); return false; } if (qrContent.length > 4000) { this.showError('Conteúdo muito longo. Máximo 4000 caracteres.'); return false; } return true; } collectFormData() { const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic'; const styleSettings = this.getStyleSettings(quickStyle); // Check if logo is selected for premium users const logoUpload = document.getElementById('logo-upload'); const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0]; if (hasLogo) { // Use FormData for premium users with logo const formData = new FormData(); // Get user-selected colors with proper priority const userPrimaryColor = document.getElementById('primary-color').value; const userBackgroundColor = document.getElementById('bg-color').value; // Priority: User selection > Style defaults > Fallback // Always use user selection if it exists, regardless of what color it is const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000'); const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF'); // Debug logging for color selection console.log('🎨 Color Selection Debug (FormData):'); console.log(' Style:', quickStyle); console.log(' Style Default Primary:', styleSettings.primaryColor); console.log(' User Selected Primary:', userPrimaryColor); console.log(' Final Primary Color:', finalPrimaryColor); console.log(' Final Background Color:', finalBackgroundColor); // Add basic form fields formData.append('type', document.getElementById('qr-type').value); formData.append('content', document.getElementById('qr-content').value); formData.append('quickStyle', quickStyle); formData.append('primaryColor', finalPrimaryColor); formData.append('backgroundColor', finalBackgroundColor); formData.append('size', parseInt(document.getElementById('qr-size').value)); formData.append('margin', parseInt(document.getElementById('qr-margin').value)); formData.append('cornerStyle', document.getElementById('corner-style')?.value || 'square'); formData.append('optimizeForSpeed', 'true'); formData.append('language', this.currentLang); // Add logo file formData.append('logo', logoUpload.files[0]); console.log('Logo file added to form data:', logoUpload.files[0].name, logoUpload.files[0].size + ' bytes'); return { data: formData, isMultipart: true, endpoint: '/api/QR/GenerateRapidWithLogo' }; } else { // Use JSON for basic QR generation (original working method) // Get user-selected colors const userPrimaryColor = document.getElementById('primary-color').value; const userBackgroundColor = document.getElementById('bg-color').value; // Priority: User selection > Style defaults > Fallback // Always use user selection if it exists, regardless of what color it is const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000'); const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF'); // Debug logging for color selection console.log('🎨 Color Selection Debug (JSON):'); console.log(' Style:', quickStyle); console.log(' Style Default Primary:', styleSettings.primaryColor); console.log(' User Selected Primary:', userPrimaryColor); console.log(' Final Primary Color:', finalPrimaryColor); console.log(' Final Background Color:', finalBackgroundColor); return { data: { type: document.getElementById('qr-type').value, content: document.getElementById('qr-content').value, quickStyle: quickStyle, primaryColor: finalPrimaryColor, backgroundColor: finalBackgroundColor, size: parseInt(document.getElementById('qr-size').value), margin: parseInt(document.getElementById('qr-margin').value), cornerStyle: document.getElementById('corner-style')?.value || 'square', optimizeForSpeed: true, language: this.currentLang }, isMultipart: false, endpoint: '/api/QR/GenerateRapid' }; } } getStyleSettings(style) { const styles = { classic: { primaryColor: '#000000', backgroundColor: '#FFFFFF' }, modern: { primaryColor: '#007BFF', backgroundColor: '#F8F9FA' }, colorful: { primaryColor: '#FF6B35', backgroundColor: '#FFF3E0' } }; return styles[style] || styles.classic; } displayQRResult(result, generationTime) { const previewDiv = document.getElementById('qr-preview'); if (!previewDiv) return; previewDiv.innerHTML = ` QR Code gerado em ${generationTime}s `; // Show generation statistics this.showGenerationStats(generationTime); // Show download buttons const downloadSection = document.getElementById('download-section'); if (downloadSection) { downloadSection.style.display = 'block'; } // Save current data this.currentQR = { base64: result.qrCodeBase64, id: result.qrId, generationTime: generationTime }; // Update counter for free users if (result.remainingQRs !== undefined) { this.updateRemainingCounter(result.remainingQRs); } } showGenerationStarted() { const button = document.getElementById('generate-btn'); const spinner = button?.querySelector('.spinner-border'); const timer = document.querySelector('.generation-timer'); if (button) button.disabled = true; if (spinner) spinner.classList.remove('d-none'); if (timer) timer.classList.remove('d-none'); // Update timer in real time this.timerInterval = setInterval(() => { const elapsed = ((performance.now() - this.startTime) / 1000).toFixed(1); const timerSpan = timer?.querySelector('span'); if (timerSpan) { timerSpan.textContent = `${elapsed}s`; } }, 100); // Preview loading const preview = document.getElementById('qr-preview'); if (preview) { preview.innerHTML = `

${this.languageStrings[this.currentLang].generating}

`; } } showGenerationStats(generationTime) { const statsDiv = document.querySelector('.generation-stats'); const speedBadge = document.querySelector('.speed-badge'); if (statsDiv) { statsDiv.classList.remove('d-none'); const timeSpan = statsDiv.querySelector('.generation-time'); if (timeSpan) { timeSpan.textContent = `${generationTime}s`; } } // Show speed badge if (speedBadge) { const strings = this.languageStrings[this.currentLang]; let badgeText = strings.normal; let badgeClass = 'bg-secondary'; if (generationTime < 1.0) { badgeText = strings.ultraFast; badgeClass = 'bg-success'; } else if (generationTime < 2.0) { badgeText = strings.fast; badgeClass = 'bg-primary'; } speedBadge.innerHTML = ` ${badgeText} `; speedBadge.classList.remove('d-none'); } } hideGenerationLoading() { const button = document.getElementById('generate-btn'); const spinner = button?.querySelector('.spinner-border'); if (button) button.disabled = false; if (spinner) spinner.classList.add('d-none'); if (this.timerInterval) { clearInterval(this.timerInterval); this.timerInterval = null; } } updateContentHints() { const type = document.getElementById('qr-type')?.value; const hintsElement = document.getElementById('content-hints'); if (!hintsElement || !type) return; const hints = { 'pt-BR': { 'url': 'Ex: https://www.exemplo.com.br', 'text': 'Digite qualquer texto que desejar', 'wifi': 'Nome da rede;Senha;Tipo de segurança (WPA/WEP)', 'vcard': 'Nome;Telefone;Email;Empresa', 'sms': 'Número;Mensagem', 'email': 'email@exemplo.com;Assunto;Mensagem' }, 'es': { 'url': 'Ej: https://www.ejemplo.com', 'text': 'Escribe cualquier texto que desees', 'wifi': 'Nombre de red;Contraseña;Tipo de seguridad (WPA/WEP)', 'vcard': 'Nombre;Teléfono;Email;Empresa', 'sms': 'Número;Mensaje', 'email': 'email@ejemplo.com;Asunto;Mensaje' } }; const langHints = hints[this.currentLang] || hints['pt-BR']; hintsElement.textContent = langHints[type] || 'Digite o conteúdo apropriado para o tipo selecionado'; } changeLanguage(e) { e.preventDefault(); this.currentLang = e.target.dataset.lang; this.updateLanguage(); this.updateContentHints(); // Save preference localStorage.setItem('qrrapido-lang', this.currentLang); // Track language change window.trackLanguageChange && window.trackLanguageChange('pt-BR', this.currentLang); } updateLanguage() { const strings = this.languageStrings[this.currentLang]; // Update tagline const tagline = document.getElementById('tagline'); if (tagline) { tagline.textContent = strings.tagline; } // Update language selector const langMap = { 'pt-BR': 'PT', 'es': 'ES', 'en': 'EN' }; const currentLang = document.getElementById('current-lang'); if (currentLang) { currentLang.textContent = langMap[this.currentLang]; } // Update hints if type already selected const qrType = document.getElementById('qr-type'); if (qrType?.value) { this.updateContentHints(); } } handleLogoSelection(e) { const file = e.target.files[0]; const logoPreview = document.getElementById('logo-preview'); const logoFilename = document.getElementById('logo-filename'); if (file) { // Validate file size (2MB max) if (file.size > 2 * 1024 * 1024) { this.showError('Logo muito grande. Máximo 2MB.'); e.target.value = ''; // Clear the input logoPreview?.classList.add('d-none'); return; } // Validate file type const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg']; if (!allowedTypes.includes(file.type)) { this.showError('Formato inválido. Use PNG ou JPG.'); e.target.value = ''; // Clear the input logoPreview?.classList.add('d-none'); return; } // Show success feedback if (logoFilename) { const fileSizeKB = Math.round(file.size / 1024); logoFilename.textContent = `${file.name} (${fileSizeKB}KB)`; } logoPreview?.classList.remove('d-none'); console.log('Logo selected:', file.name, file.size + ' bytes', file.type); } else { // Hide preview when no file selected logoPreview?.classList.add('d-none'); } } handleCornerStyleChange(e) { const selectedStyle = e.target.value; const premiumStyles = ['rounded', 'circle', 'leaf']; if (premiumStyles.includes(selectedStyle)) { // Check if user is premium (we can detect this by checking if the option is disabled) const option = e.target.options[e.target.selectedIndex]; if (option.disabled) { // Reset to square e.target.value = 'square'; this.showUpgradeModal('Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.'); return; } } console.log('Corner style selected:', selectedStyle); } applyQuickStyle(e) { const style = e.target.value; const settings = this.getStyleSettings(style); const primaryColor = document.getElementById('primary-color'); const bgColor = document.getElementById('bg-color'); if (primaryColor) primaryColor.value = settings.primaryColor; if (bgColor) bgColor.value = settings.backgroundColor; } updateStatsCounters() { // Simulate real-time counters setInterval(() => { const totalElement = document.getElementById('total-qrs'); if (totalElement) { const current = parseFloat(totalElement.textContent.replace('K', '')) || 10.5; const newValue = (current + Math.random() * 0.1).toFixed(1); totalElement.textContent = `${newValue}K`; } // Update average time based on real performance const avgElement = document.getElementById('avg-generation-time'); if (avgElement && window.qrRapidoStats) { const avg = window.qrRapidoStats.getAverageTime(); avgElement.textContent = `${avg}s`; } }, 30000); // Update every 30 seconds } trackGenerationEvent(type, time) { // Google Analytics if (typeof gtag !== 'undefined') { gtag('event', 'qr_generated', { 'qr_type': type, 'generation_time': parseFloat(time), 'user_type': this.isPremiumUser() ? 'premium' : 'free', 'language': this.currentLang }); } // Internal statistics if (!window.qrRapidoStats) { window.qrRapidoStats = { times: [], getAverageTime: function() { if (this.times.length === 0) return '1.2'; const avg = this.times.reduce((a, b) => a + b) / this.times.length; return avg.toFixed(1); } }; } window.qrRapidoStats.times.push(parseFloat(time)); } updateSpeedStats(generationTime) { // Update the live statistics display const timeFloat = parseFloat(generationTime); // Update average time display in the stats cards const avgElement = document.querySelector('.card-body h5:contains("1.2s")'); if (avgElement) { const avgTime = window.qrRapidoStats ? window.qrRapidoStats.getAverageTime() : generationTime; avgElement.innerHTML = ` ${avgTime}s`; } // Update the generation timer in the header const timerElement = document.querySelector('.generation-timer span'); if (timerElement) { timerElement.textContent = `${generationTime}s`; } // Update performance statistics if (!window.qrRapidoPerformance) { window.qrRapidoPerformance = { totalGenerations: 0, totalTime: 0, bestTime: Infinity, worstTime: 0 }; } const perf = window.qrRapidoPerformance; perf.totalGenerations++; perf.totalTime += timeFloat; perf.bestTime = Math.min(perf.bestTime, timeFloat); perf.worstTime = Math.max(perf.worstTime, timeFloat); // Log performance statistics for debugging console.log('📊 Performance Update:', { currentTime: `${generationTime}s`, averageTime: `${(perf.totalTime / perf.totalGenerations).toFixed(1)}s`, bestTime: `${perf.bestTime.toFixed(1)}s`, totalGenerations: perf.totalGenerations }); } isPremiumUser() { return document.querySelector('.text-success')?.textContent.includes('Premium Ativo') || false; } async downloadQR(format) { if (!this.currentQR) return; try { const response = await fetch(`/api/QR/Download/${this.currentQR.id}?format=${format}`); if (!response.ok) throw new Error('Download failed'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `qrrapido-${new Date().toISOString().slice(0,10)}.${format}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } catch (error) { console.error('Download error:', error); this.showError('Erro ao fazer download. Tente novamente.'); } } async saveToHistory() { if (!this.currentQR) return; try { const response = await fetch('/api/QR/SaveToHistory', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ qrId: this.currentQR.id }) }); if (response.ok) { this.showSuccess(this.languageStrings[this.currentLang].success); } else { throw new Error('Failed to save'); } } catch (error) { console.error('Save error:', error); this.showError('Erro ao salvar no histórico.'); } } async checkAdFreeStatus() { try { const response = await fetch('/Account/AdFreeStatus'); const status = await response.json(); if (status.isAdFree) { this.hideAllAds(); this.showAdFreeMessage(status.timeRemaining); } } catch (error) { console.error('Error checking ad-free status:', error); } } hideAllAds() { document.querySelectorAll('.ad-container').forEach(ad => { ad.style.display = 'none'; }); } showAdFreeMessage(timeRemaining) { if (timeRemaining <= 0) return; const existing = document.querySelector('.ad-free-notice'); if (existing) return; // Already shown const message = document.createElement('div'); message.className = 'alert alert-success text-center mb-3 ad-free-notice'; message.innerHTML = ` Sessão sem anúncios ativa! Tempo restante: ${this.formatTime(timeRemaining)} Tornar Permanente `; const container = document.querySelector('.container'); const row = container?.querySelector('.row'); if (container && row) { container.insertBefore(message, row); } } formatTime(minutes) { if (minutes === 0) return '0m'; const days = Math.floor(minutes / 1440); const hours = Math.floor((minutes % 1440) / 60); const mins = minutes % 60; if (days > 0) return `${days}d ${hours}h ${mins}m`; if (hours > 0) return `${hours}h ${mins}m`; return `${mins}m`; } showUpgradeModal(message) { const modal = document.createElement('div'); modal.className = 'modal fade'; modal.innerHTML = ` `; document.body.appendChild(modal); const bsModal = new bootstrap.Modal(modal); bsModal.show(); // Remove modal when closed modal.addEventListener('hidden.bs.modal', () => { document.body.removeChild(modal); }); } updateRemainingCounter(remaining) { const counterElement = document.querySelector('.qr-counter'); if (counterElement) { counterElement.textContent = `${remaining} QR codes restantes hoje`; if (remaining <= 3) { counterElement.className = 'badge bg-warning qr-counter'; } if (remaining === 0) { counterElement.className = 'badge bg-danger qr-counter'; } } } showError(message) { this.showAlert(message, 'danger'); } showSuccess(message) { this.showAlert(message, 'success'); } showAlert(message, type) { const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible fade show`; alert.innerHTML = ` ${message} `; const container = document.querySelector('.container'); const row = container?.querySelector('.row'); if (container && row) { container.insertBefore(alert, row); } // Auto-remove after delay setTimeout(() => { if (alert.parentNode) { alert.parentNode.removeChild(alert); } }, type === 'success' ? 3000 : 5000); } setupRealTimePreview() { const contentField = document.getElementById('qr-content'); const typeField = document.getElementById('qr-type'); if (contentField && typeField) { let previewTimeout; const updatePreview = () => { clearTimeout(previewTimeout); previewTimeout = setTimeout(() => { if (contentField.value.trim() && typeField.value) { // Could implement real-time preview for premium users console.log('Real-time preview update'); } }, 500); }; contentField.addEventListener('input', updatePreview); typeField.addEventListener('change', updatePreview); } } } // Initialize when DOM loads document.addEventListener('DOMContentLoaded', () => { window.qrGenerator = new QRRapidoGenerator(); // Initialize AdSense if necessary if (window.adsbygoogle && document.querySelector('.adsbygoogle')) { (adsbygoogle = window.adsbygoogle || []).push({}); } }); // Global functions for ad control window.QRApp = { refreshAds: function() { if (window.adsbygoogle) { document.querySelectorAll('.adsbygoogle').forEach(ad => { (adsbygoogle = window.adsbygoogle || []).push({}); }); } }, hideAds: function() { document.querySelectorAll('.ad-container').forEach(ad => { ad.style.display = 'none'; }); } };