Profile
๐Ÿ‘ค
Paste image URL or type an emoji
? for shortcuts
`; } // In-app preview return `
${avatar}
${p.profile.name?`
${esc(p.profile.name)}
`:''} ${p.profile.headline?`
${esc(p.profile.headline)}
`:''} ${p.profile.bio?`
${esc(p.profile.bio)}
`:''} ${socialsHTML} ${footerHTML}
`; } function renderPreview() { const p = getPage(); if(!p) return; document.getElementById('phoneContent').innerHTML = generatePreviewHTML(p); window._trackClick = (linkId) => { p.analytics.totalClicks = (p.analytics.totalClicks||0)+1; p.analytics.linkClicks[linkId] = (p.analytics.linkClicks[linkId]||0)+1; save(); if(state.activeTab==='analytics') renderAnalytics(); }; } // === ACTIONS === function createPage() { const p = defaultPage('New Page'); state.pages.push(p); state.activePage = p.id; save(); renderAll(); } function selectPage(id) { state.activePage=id; save(); renderAll(); } function deletePage(id) { if(state.pages.length<=1) return; if(!confirm('Delete this page?')) return; state.pages = state.pages.filter(p=>p.id!==id); if(state.activePage===id) state.activePage=state.pages[0]?.id; save(); renderAll(); } function updatePage() { const p = getPage(); if(!p) return; p.profile.name = document.getElementById('profileName').value; p.profile.headline = document.getElementById('profileHeadline').value; p.profile.bio = document.getElementById('profileBio').value; p.profile.avatar = document.getElementById('avatarUrl').value; p.profile.footer = document.getElementById('footerText').value; p.appearance.accentColor = document.getElementById('accentColor').value; p.appearance.font = document.getElementById('fontSelect').value; renderAvatarPreview(); save(); renderPreview(); } function addLink() { const p = getPage(); if(!p) return; p.links.push({ id:'l'+Date.now(), title:'New Link', url:'https://', desc:'', icon:'๐Ÿ”—', enabled:true }); save(); renderLinks(); renderPreview(); } function updLink(idx, field, value) { const p = getPage(); if(!p) return; p.links[idx][field] = value; save(); renderLinks(); renderPreview(); } function deleteLink(idx) { const p = getPage(); if(!p) return; p.links.splice(idx, 1); save(); renderLinks(); renderPreview(); } function toggleLink(idx) { const p = getPage(); if(!p) return; p.links[idx].enabled = !p.links[idx].enabled; save(); renderLinks(); renderPreview(); } function moveLink(idx, dir) { const p = getPage(); if(!p) return; const ni = idx+dir; if(ni<0||ni>=p.links.length) return; [p.links[idx], p.links[ni]] = [p.links[ni], p.links[idx]]; save(); renderLinks(); renderPreview(); } function updSocial(id, value) { const p = getPage(); if(!p) return; if(value) p.socials[id]=value; else delete p.socials[id]; save(); renderPreview(); } function setTheme(id) { const p = getPage(); if(!p) return; p.appearance.theme = id; save(); renderAppearance(); renderPreview(); } function setLinkStyle(id) { const p = getPage(); if(!p) return; p.appearance.linkStyle = id; save(); renderAppearance(); renderPreview(); } function toggleOpt(key) { const p = getPage(); if(!p) return; p.appearance[key] = !p.appearance[key]; save(); renderAppearance(); renderPreview(); } function syncAccent(v) { if(/^#[0-9a-fA-F]{6}$/.test(v)) { document.getElementById('accentColor').value = v; document.getElementById('accentSwatch').style.background = v; updatePage(); } } // Drag and drop let dragIdx = null; function dragStart(e,i) { dragIdx=i; e.target.classList.add('dragging'); e.dataTransfer.effectAllowed='move'; } function dragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect='move'; } function drop(e,i) { e.preventDefault(); if(dragIdx===null||dragIdx===i) return; const p=getPage(); if(!p) return; const item = p.links.splice(dragIdx,1)[0]; p.links.splice(i,0,item); dragIdx=null; save(); renderLinks(); renderPreview(); } function dragEnd(e) { e.target.classList.remove('dragging'); dragIdx=null; } // Analytics function resetAnalytics() { const p = getPage(); if(!p) return; p.analytics = { totalClicks:0, linkClicks:{} }; save(); renderAnalytics(); } // Tabs function switchTab(tab) { state.activeTab = tab; document.querySelectorAll('.tab-content').forEach(el=>el.style.display='none'); document.getElementById('tab-'+tab).style.display='block'; document.querySelectorAll('.editor-tab').forEach(el=>el.classList.toggle('active',el.dataset.tab===tab)); } // Export HTML function exportHTML() { const p = getPage(); if(!p) return; const html = generatePreviewHTML(p, true); const el = document.getElementById('modalContent'); el.innerHTML = `

Export as HTML

Copy this HTML and host it anywhere โ€” GitHub Pages, Netlify, Vercel, or just open it locally.

${esc(html)}
`; document.getElementById('modalOverlay').style.display='flex'; } function copyExport() { const p = getPage(); if(!p) return; navigator.clipboard.writeText(generatePreviewHTML(p, true)); alert('Copied to clipboard!'); } function downloadExport() { const p = getPage(); if(!p) return; const html = generatePreviewHTML(p, true); const blob = new Blob([html], {type:'text/html'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (p.profile.name||'linkhub').toLowerCase().replace(/\s+/g,'-')+'.html'; a.click(); } // Import/Export JSON function exportAll() { const json = JSON.stringify(state, null, 2); const el = document.getElementById('modalContent'); el.innerHTML = `

Export All Data

${esc(json)}
`; document.getElementById('modalOverlay').style.display='flex'; } function downloadJSON() { const blob = new Blob([JSON.stringify(state,null,2)], {type:'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'linkhub-backup.json'; a.click(); } function importAll() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = e.target.files[0]; if(!file) return; const reader = new FileReader(); reader.onload = (ev) => { try { const d = JSON.parse(ev.target.result); if(d.pages) { state = d; save(); renderAll(); } else alert('Invalid file format'); } catch(e) { alert('Error parsing file: '+e.message); } }; reader.readAsText(file); }; input.click(); } // Fullscreen preview function previewFullscreen() { const p = getPage(); if(!p) return; const el = document.getElementById('fullscreenContent'); el.innerHTML = generatePreviewHTML(p, false).replace('min-height:100%','min-height:100vh'); document.getElementById('fullscreenPreview').style.display='block'; } function closeFullscreen() { document.getElementById('fullscreenPreview').style.display='none'; } // Modal function closeModal(e) { if(e && e.target !== document.getElementById('modalOverlay')) return; document.getElementById('modalOverlay').style.display='none'; } // Keyboard shortcuts document.addEventListener('keydown', (e) => { if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.tagName==='SELECT') return; if(e.key==='n'||e.key==='N') { e.preventDefault(); createPage(); } if(e.key==='l'||e.key==='L') { e.preventDefault(); switchTab('links'); addLink(); } if(e.key==='f'||e.key==='F') { e.preventDefault(); previewFullscreen(); } if(e.key==='e'||e.key==='E') { e.preventDefault(); exportHTML(); } if(e.key==='d'||e.key==='D') { e.preventDefault(); switchTab('appearance'); } if(e.key==='Escape') { closeFullscreen(); document.getElementById('modalOverlay').style.display='none'; } if(e.key==='1') { e.preventDefault(); switchTab('profile'); } if(e.key==='2') { e.preventDefault(); switchTab('links'); } if(e.key==='3') { e.preventDefault(); switchTab('social'); } if(e.key==='4') { e.preventDefault(); switchTab('appearance'); } if(e.key==='5') { e.preventDefault(); switchTab('analytics'); } if(e.key==='?') { e.preventDefault(); const el = document.getElementById('modalContent'); el.innerHTML = `

โŒจ๏ธ Keyboard Shortcuts

NNew page LAdd link FFullscreen preview EExport HTML DAppearance tab 1-5Switch tabs EscClose modal/preview
`; document.getElementById('modalOverlay').style.display='flex'; } }); // Rename page on double-click document.getElementById('pageList').addEventListener('dblclick', (e) => { const item = e.target.closest('.page-item'); if(!item) return; const p = getPage(); if(!p) return; const name = prompt('Page name:', p.name); if(name!==null) { p.name=name; save(); renderPageList(); } }); // Util function esc(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function renderAll() { renderPageList(); renderEditor(); } // Init load(); renderAll();