var Storage = { get: function(key) { try { return localStorage.getItem(key); } catch(e) { return null; } }, set: function(key, val) { try { localStorage.setItem(key, val); } catch(e) {} } }; var GOOGLE_CLIENT_ID = '500448449044-hv2rp3k0lsok9ara1bred87c75lnsp7l.apps.googleusercontent.com'; let curUser=null, dbFil='all', googleAccessToken=null, driveFileId=null; let saved = []; try { const sData = Storage.get('qrs_min'); if (sData) saved = JSON.parse(sData); } catch(e) {} function switchView(viewId) { document.querySelectorAll('.db-view').forEach(v => v.style.display = 'none'); const target = document.getElementById('view-' + viewId); if(target) target.style.display = 'block'; document.querySelectorAll('.app-sb .sb-btn').forEach(btn => { if(btn.getAttribute('onclick') && btn.getAttribute('onclick').includes(viewId)) { btn.classList.add('act'); } else if (btn.getAttribute('onclick') && btn.getAttribute('onclick').includes('switchView')) { btn.classList.remove('act'); } }); if(viewId === 'stats') renderStats(); if(viewId === 'templates') renderTemplates(); if(viewId === 'domains') renderDomains(); } function renderDB(){ const filtered = dbFil==='all' ? saved : saved.filter(q=>q.type===dbFil); document.getElementById('stTotal').textContent = saved.length; const uniqueTypes = []; saved.forEach(function(q){ if(uniqueTypes.indexOf(q.type)===-1) uniqueTypes.push(q.type); }); document.getElementById('stTypes').textContent = uniqueTypes.length; document.getElementById('stLast').textContent = saved.length>0 ? saved[0].date : '—'; const dbs = document.getElementById('dbSub'); if(curUser && dbs) dbs.textContent = `Dashboard synced. ${saved.length} assets ready.`; const g = document.getElementById('dbG'); if(!g) return; g.innerHTML = ''; if(!filtered.length){ g.innerHTML=`
🔳

Workspace Empty

No assets found for this filter.

`; return; } filtered.forEach(q=>{ const c=document.createElement('div');c.className='dbc'; const nameStr = (q.advSettings && q.advSettings.name) ? q.advSettings.name : q.content; const shortC = nameStr.length>38 ? nameStr.substring(0,38)+'…' : nameStr; c.innerHTML=`
${q.thumb?`QR`:'
🔳
'}
${q.typeIcon} ${q.typeName}

${shortC}

📅 ${q.date}
`; g.appendChild(c); }); } function fDB(type,btn){ dbFil=type; document.querySelectorAll('.dbf').forEach(b=>{ if(b===btn) b.classList.add('act'); else b.classList.remove('act'); }); renderDB(); } function delQR(id){ if(!confirm('Permanently purge this QR asset?'))return; let idx = -1; for(let i=0;i-1){ saved.splice(idx,1); async function renderStats() { const v = document.getElementById('view-stats'); if(v.innerHTML !== '') return; // Initial loading state v.innerHTML = `

Stats

You can set your time zone. Current time zone: Asia/Calcutta
Jun 3, 2026 - Jun 10, 2026
Time zone: Asia/Calcutta ✎
Analyzed period: 03-06-2026 to 10-06-2026

Syncing analytics data from cloud...

`; // Initialize Supabase if needed let sb = window.supabaseClient; if (!sb) { try { const res = await fetch('config.json'); const config = await res.json(); if (config.auth && config.auth.url) { if (typeof window.supabase === 'undefined') { await new Promise(resolve => { const script = document.createElement('script'); script.src = "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"; script.onload = resolve; document.head.appendChild(script); }); } sb = window.supabase.createClient(config.auth.url, config.auth.key); window.supabaseClient = sb; } } catch(e) {} } const myQrIds = saved.map(q => String(q.id)); let scans = []; if (sb && myQrIds.length > 0) { try { const { data } = await sb.from('qr_scans').select('*').in('qr_id', myQrIds); if (data) scans = data; } catch(e) { console.error('Failed to load stats', e); } } const totalQRs = saved.length; const totalScans = scans.length; // Calculate unique scans (by visitor_hash) const uniqueHashes = new Set(scans.filter(s => s.visitor_hash).map(s => s.visitor_hash)); const totalUnique = uniqueHashes.size > 0 ? uniqueHashes.size : (totalScans > 0 ? Math.ceil(totalScans * 0.8) : 0); const totalVisits = totalScans; // Mock visits const cardsHtml = `
\${totalQRs}
Total QR Codes
\${totalScans}
Total Scans
\${totalUnique}
Total Unique Scans
\${totalVisits}
Total visits
`; document.getElementById('stats-cards-container').innerHTML = cardsHtml; // Render Chart.js const ctx = document.getElementById('statsChart').getContext('2d'); const today = new Date(); const labels = []; const dataPoints = []; for(let i=6; i>=0; i--) { const d = new Date(); d.setDate(today.getDate() - i); labels.push(d.toLocaleDateString('en-US', {month:'short', day:'2-digit'})); dataPoints.push(0); } scans.forEach(s => { const dStr = new Date(s.scanned_at || s.created_at).toLocaleDateString('en-US', {month:'short', day:'2-digit'}); const idx = labels.indexOf(dStr); if(idx !== -1) dataPoints[idx]++; }); if (window.myStatsChart) { window.myStatsChart.destroy(); } // Add grid color variable handling for light/dark mode const isDark = document.body.classList.contains('dark-mode') || getComputedStyle(document.body).backgroundColor === 'rgb(3, 7, 18)'; const gridColor = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'; const textColor = isDark ? '#cbd5e1' : '#64748b'; window.myStatsChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Totals', data: dataPoints, borderColor: '#3b82f6', backgroundColor: 'rgba(37,99,235,0.1)', borderWidth: 2, pointBackgroundColor: '#fff', pointBorderColor: '#3b82f6', pointBorderWidth: 2, pointRadius: 4, fill: true, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { beginAtZero: true, grid: { color: gridColor }, ticks: { color: textColor, stepSize: 1 } }, x: { grid: { display: false }, ticks: { color: textColor } } } } }); // Render accordions const accList = [ "Scans by day", "Scans by operating system", "Scans by country", "Scans by region/city", "Scans by browser", "Scans by user language", "Scans by QR name" ]; let accHtml = ''; accList.forEach(title => { accHtml += `
No data available for the selected period.
`; }); document.getElementById('accordions-container').innerHTML = accHtml; } function renderTemplates() { const v = document.getElementById('view-templates'); if(v.innerHTML !== '') return; v.innerHTML = `

Template Gallery

Start with a premium pre-designed template and customize it.

💼

Modern Business Card

Professional layout with gradient logo support
📶

Neon Wi-Fi Access

Vibrant styling for quick guest access
🍽️

Minimalist Menu

Clean and scannable design for restaurants
🔗

Social Media Hub

Optimized for Linktree or social profiles
`; } function renderDomains() { const v = document.getElementById('view-domains'); if(v.innerHTML !== '') return; v.innerHTML = `

Custom Domains

Connect your own domain to brand your dynamic short links.

Add New Domain

You will need to update your DNS records (CNAME) with your registrar to complete setup.

Connected Domains

qr.classoflearners.com
Active
link.mybusiness.co
Pending DNS
`; } function editQR(id){ window.location.href = 'qr-editor.html?id=' + id; } function reDL(id){ let q=null; for(let i=0;i