// ── CLUBE67 PREMIUM INTERACTIVE SCRIPT ── document.addEventListener('DOMContentLoaded', () => { // Initialize Lucide Icons lucide.createIcons(); // ── STATE MANAGEMENT ── let activeTab = 'catalog'; let benefits = []; let activeVoucher = null; let countdownInterval = null; let timerTotalSeconds = 300; // 5 minutes let timerRemainingSeconds = 300; // Premium Mock Data in case backend returns empty/offline to guarantee flawless display const mockBenefits = [ { id: 1, title: "Consulta + Limpeza Geral", description: "Ganhe 50% de desconto na primeira consulta preventiva e profilaxia.", category: "odontologia", partner: "ScoreOdonto MS", icon: "🦷", rating: "4.9", discount: "50% OFF" }, { id: 2, title: "Checkout Cardiológico Completo", description: "Desconto especial de 30% em exames de eletrocardiograma e ecocardiograma.", category: "saude", partner: "CardioClinic CG", icon: "🩺", rating: "4.8", discount: "30% OFF" }, { id: 3, title: "Menu Degustação Premium (Casal)", description: "Compre um menu degustação de 5 etapas e ganhe uma garrafa de vinho importado.", category: "gastronomia", partner: "Le Jardin Bistrô", icon: "🍔", rating: "5.0", discount: "Vinho Cortesia" }, { id: 4, title: "Day Use Completo no Eco Resort", description: "Acesso total às piscinas naturais, trilhas e almoço pantaneiro incluso com 25% OFF.", category: "lazer", partner: "Bonito Eco Resort", icon: "🌴", rating: "4.7", discount: "25% OFF" }, { id: 5, title: "Fórmulas Manipuladas e Vitaminas", description: "Desconto exclusivo de 20% em qualquer fórmula médica manipulada.", category: "farmacia", partner: "Fórmula Ativa", icon: "💊", rating: "4.9", discount: "20% OFF" }, { id: 6, title: "MBA Executivo & Especializações", description: "Isenção na taxa de matrícula e 35% de desconto nas mensalidades do curso.", category: "educacao", partner: "Unigran Capital", icon: "📚", rating: "4.6", discount: "35% OFF" } ]; // Analytics Dashboard Data const analyticsData = { members: 1248, redemptions: 384, savings: 14820, goalPercentage: 96, weeklyDistribution: { seg: 25, ter: 45, qua: 70, qui: 55, sex: 85, sab: 100, dom: 35 } }; // ── DOM ELEMENTS ── const navTabs = { catalog: document.getElementById('tab-catalog'), redeem: document.getElementById('tab-redeem'), analytics: document.getElementById('tab-analytics') }; const sections = { catalog: document.getElementById('section-catalog'), redeem: document.getElementById('section-redeem'), analytics: document.getElementById('section-analytics') }; const searchInput = document.getElementById('search-input'); const categoryFilters = document.getElementById('category-filters'); const benefitsGrid = document.getElementById('benefits-grid'); // Redeem tab elements const redeemCardEmpty = document.getElementById('redeem-card-empty'); const redeemCardActive = document.getElementById('redeem-card-active'); const voucherIcon = document.getElementById('voucher-icon'); const voucherTitle = document.getElementById('voucher-title'); const voucherPartner = document.getElementById('voucher-partner'); const voucherToken = document.getElementById('voucher-token'); const qrCodeImg = document.getElementById('qr-code-img'); const timerText = document.getElementById('timer-text'); const timerBar = document.getElementById('timer-bar'); const btnValidate = document.getElementById('btn-validate'); const btnCancel = document.getElementById('btn-cancel'); // Analytics elements const statMembers = document.getElementById('stat-members'); const statRedemptions = document.getElementById('stat-redemptions'); const statSavings = document.getElementById('stat-savings'); const radialProgressBar = document.getElementById('radial-progress-bar'); const radialPercentage = document.getElementById('radial-percentage'); const barSeg = document.getElementById('bar-seg'); const barTer = document.getElementById('bar-ter'); const barQua = document.getElementById('bar-qua'); const barQui = document.getElementById('bar-qui'); const barSex = document.getElementById('bar-sex'); const barSab = document.getElementById('bar-sab'); const barDom = document.getElementById('bar-dom'); // ── NAVIGATION LOGIC ── function switchTab(tabId) { if (activeTab === tabId) return; // Update tabs active state Object.keys(navTabs).forEach(key => { if (key === tabId) { navTabs[key].classList.add('active'); } else { navTabs[key].classList.remove('active'); } }); // Hide old section, show new section Object.keys(sections).forEach(key => { if (key === tabId) { sections[key].classList.remove('hidden'); } else { sections[key].classList.add('hidden'); } }); activeTab = tabId; // Perform specific triggers per tab if (tabId === 'analytics') { loadAnalyticsCharts(); } } Object.keys(navTabs).forEach(key => { navTabs[key].addEventListener('click', () => switchTab(key)); }); // ── BENEFIT FETCHING & RENDERING ── async function loadBenefits() { try { // Sincronized backend API consume const response = await fetch('/api/benefits'); if (response.ok) { const data = await response.json(); benefits = data && data.length > 0 ? data : mockBenefits; } else { benefits = mockBenefits; } } catch (err) { console.warn('[Clube67] API offline, running with premium fallback simulation.'); benefits = mockBenefits; } renderBenefits(benefits); } function renderBenefits(list) { const loadingEl = document.getElementById('catalog-loading'); if (loadingEl) loadingEl.remove(); benefitsGrid.innerHTML = ''; if (list.length === 0) { benefitsGrid.innerHTML = `

Nenhum benefício encontrado para esta categoria ou busca.

`; lucide.createIcons(); return; } list.forEach(benefit => { const card = document.createElement('div'); card.className = 'benefit-card'; // Mouse moves 3D lighting effect card.addEventListener('mousemove', e => { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; card.style.setProperty('--mouse-x', `${x}px`); card.style.setProperty('--mouse-y', `${y}px`); }); card.innerHTML = `
${benefit.discount || 'Desconto'}
${benefit.icon || '🎁'}
${benefit.partner || 'Estabelecimento'}

${benefit.title}

${benefit.description}

${benefit.rating || '4.8'}
`; benefitsGrid.appendChild(card); }); // Add event listeners to voucher buttons document.querySelectorAll('.btn-redeem-trigger').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.getAttribute('data-id'), 10); const selected = benefits.find(b => b.id === id); if (selected) { triggerVoucherRedemption(selected); } }); }); lucide.createIcons(); } // ── SEARCH & FILTERING ── let activeCategory = 'all'; function filterBenefits() { const query = searchInput.value.toLowerCase().trim(); const filtered = benefits.filter(b => { const matchesCategory = activeCategory === 'all' || b.category === activeCategory; const matchesSearch = b.title.toLowerCase().includes(query) || b.description.toLowerCase().includes(query) || (b.partner && b.partner.toLowerCase().includes(query)); return matchesCategory && matchesSearch; }); renderBenefits(filtered); } searchInput.addEventListener('input', filterBenefits); categoryFilters.querySelectorAll('.filter-pill').forEach(pill => { pill.addEventListener('click', () => { categoryFilters.querySelector('.filter-pill.active').classList.remove('active'); pill.classList.add('active'); activeCategory = pill.getAttribute('data-category'); filterBenefits(); }); }); // ── VOUCHER REDEMPTION & SCANNER TIMER ── function triggerVoucherRedemption(benefit) { activeVoucher = benefit; // Populate DOM elements voucherIcon.textContent = benefit.icon || '🎁'; voucherTitle.textContent = benefit.title; voucherPartner.textContent = benefit.partner || 'Estabelecimento'; // Generate dynamic unique token const randToken = 'C67-' + Math.random().toString(36).substring(2, 7).toUpperCase(); voucherToken.textContent = randToken; // Update QR Code Source with parameters qrCodeImg.src = `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${randToken}&color=0f172a`; // Switch to redeem container view redeemCardEmpty.classList.add('hidden'); redeemCardActive.classList.remove('hidden'); // Switch view tab switchTab('redeem'); // Start 5 minutes expiration timer startVoucherTimer(); } function startVoucherTimer() { if (countdownInterval) clearInterval(countdownInterval); timerRemainingSeconds = timerTotalSeconds; updateTimerDisplay(); countdownInterval = setInterval(() => { timerRemainingSeconds--; updateTimerDisplay(); if (timerRemainingSeconds <= 0) { handleVoucherExpiration(); } }, 1000); } function updateTimerDisplay() { const min = Math.floor(timerRemainingSeconds / 60); const sec = timerRemainingSeconds % 60; timerText.textContent = `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`; // Progress bar percentage shrink const pct = (timerRemainingSeconds / timerTotalSeconds) * 100; timerBar.style.width = `${pct}%`; // Danger colors for low time if (timerRemainingSeconds < 60) { timerBar.className = 'h-full bg-gradient-to-r from-red-500 to-rose-500 transition-all duration-1000'; timerText.className = 'text-red-400 font-mono'; } else { timerBar.className = 'h-full bg-gradient-to-r from-indigo-500 to-violet-500 transition-all duration-1000'; timerText.className = 'text-indigo-400 font-mono'; } } function handleVoucherExpiration() { clearInterval(countdownInterval); alert('Este token de resgate expirou por limite de tempo de segurança. Por favor, gere um novo voucher.'); cancelVoucher(); } function cancelVoucher() { if (countdownInterval) clearInterval(countdownInterval); activeVoucher = null; redeemCardActive.classList.add('hidden'); redeemCardEmpty.classList.remove('hidden'); } btnCancel.addEventListener('click', cancelVoucher); // ── VOUCHER VALIDATION SIMULATOR (API CONSUME) ── btnValidate.addEventListener('click', async () => { if (!activeVoucher) return; btnValidate.disabled = true; btnValidate.textContent = 'VALIDANDO...'; try { // Dynamic validation call to backend API const response = await fetch('/api/benefits/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tokenId: voucherToken.textContent, benefitId: activeVoucher.id }) }); // Simulated UI validation popup for high wow-factor and offline resilience setTimeout(() => { alert(`✅ SUCESSO! O Voucher "${activeVoucher.title}" foi validado com sucesso e consumido pelo parceiro! Token: ${voucherToken.textContent} Estabelecimento: ${activeVoucher.partner || 'Clube67'}`); // Update analytics counters to reflect live usage! analyticsData.redemptions++; analyticsData.savings += 45; // average discount saving value cancelVoucher(); btnValidate.disabled = false; btnValidate.textContent = 'Simular Validação (Estabelecimento)'; // Automatically redirect to Analytics to see the metric update live! switchTab('analytics'); }, 1200); } catch (err) { console.error('Validation API fail:', err); btnValidate.disabled = false; btnValidate.textContent = 'Simular Validação (Estabelecimento)'; } }); // ── ANALYTICS CHARTS & COUNTERS ── function loadAnalyticsCharts() { // Load live dynamic counters statMembers.textContent = analyticsData.members.toLocaleString(); statRedemptions.textContent = analyticsData.redemptions.toLocaleString(); statSavings.textContent = `R$ ${analyticsData.savings.toLocaleString('pt-BR')}`; // Load SVG gauge radial chart progress const radius = 64; const circumference = 2 * Math.PI * radius; // 402.12 const pct = analyticsData.goalPercentage; const offset = circumference - (pct / 100) * circumference; radialProgressBar.style.strokeDasharray = `${circumference}`; radialProgressBar.style.strokeDashoffset = `${offset}`; radialPercentage.textContent = `${pct}%`; // Load vertical bar charts heights setTimeout(() => { barSeg.style.height = `${analyticsData.weeklyDistribution.seg}%`; barTer.style.height = `${analyticsData.weeklyDistribution.ter}%`; barQua.style.height = `${analyticsData.weeklyDistribution.qua}%`; barQui.style.height = `${analyticsData.weeklyDistribution.qui}%`; barSex.style.height = `${analyticsData.weeklyDistribution.sex}%`; barSab.style.height = `${analyticsData.weeklyDistribution.sab}%`; barDom.style.height = `${analyticsData.weeklyDistribution.dom}%`; }, 100); } // Initialize Catalog Fetching loadBenefits(); });