🔐 Vault Nerd — seu universo de colecionáveis ⚡ Compras geram Vault XP 🧩 Monte e compartilhe seu Cofre
Colecionar é guardar histórias

Sua coleção começa aqui.

Descubra colecionáveis, construa sua prateleira virtual e transforme cada compra em progresso dentro do universo Vault Nerd.

R$ 1 = 1 XPXP sobre produtos pagos
XP extraem campanhas selecionadas
Desbloqueiosskins, temas e itens visuais
Colecionável demonstrativo
Vault XP+129 XPnesta compra demo
Seu Cofre3 itensna prateleira demo
🧭
Escolha sem pressãoInformação clara para decidir melhor.
🎮
Experiência além da compraXP e coleção virtual conectados à loja.
📦
Pedido em um só lugarStatus e histórico dentro da conta.
🔐
Seu perfil, suas regrasCofre público apenas se você quiser.
Descoberta

Destaques do cofre

Produtos demonstrativos para validar a experiência antes do catálogo real.

Depois que a caixa chega

A coleção continua no Meu Cofre.

Cada item elegível entra no inventário virtual. Você escolhe o que expor, muda a prateleira, personaliza seu avatar e decide se quer compartilhar o perfil.

MINHA PRATELEIRA
Item virtual 1Item virtual 2Item virtual 3
Vault XP

Progresso que vira personalização.

Recompensas cosméticas, sem transformar a experiência em pressão para comprar.

01

Compre

O valor dos produtos elegíveis gera XP. Frete não entra no cálculo.

02

Evolua

A barra de nível mostra o que falta para o próximo desbloqueio.

03

Personalize

Desbloqueie skins do avatar, molduras e temas de prateleira.

04

Compartilhe

Se quiser, transforme seu Cofre em uma página pública para outros colecionadores.

Avatar do usuário
Meu Cofre🔒 privado

Colecionador Demo

@colecionador

Nível 3 — Colecionador780 / 1.200 XP
Próximo desbloqueio Skin Guardião faltam 420 XP
Minha prateleira

Exponha até 6 itens

Arraste não está ativo neste primeiro MVP. Clique em um item para trocar a posição.
Comunidade

Cofres públicos

Descubra coleções compartilhadas por outros usuários. Nenhum perfil precisa ser público para usar a Vault Nerd.

Área interna • demo

Painel Vault Nerd

Primeira visualização administrativa. No backend real, produtos, estoque, pedidos e campanhas virão do banco de dados.

Produtos do catálogo

Campanha de XP

Exemplo de configuração promocional.

,store:'↩'}[k]||'•')} function adminShell(email){ const logo=document.querySelector('.brand img')?.src||document.querySelector('.brand--footer img')?.src||''; return `
Dashboard administrativoGestão operacional da Vault Nerd
Administrador${safe(email||'')}
VN
${loading()}
`; } function navBtn(id,label,pending){return ``} function setTitle(t){const el=document.getElementById('vnMgrTitle');if(el)el.textContent=t} function content(html){const el=document.getElementById('vnMgrContent');if(el)el.innerHTML=html} function refreshNav(){document.querySelectorAll('[data-vna-section]').forEach(b=>b.classList.toggle('active',b.dataset.vnaSection===A.section))} async function adminIdentity(){ const s=await db.auth.getSession();const user=s.data.session?.user||null;if(!user)return {user:null,isAdmin:false}; const a=await db.rpc('is_admin');return {user,isAdmin:!!a.data}; } window.renderAdmin=async function(){ document.body.classList.add('vn-admin-mode'); const host=document.querySelector('[data-page="admin"] .container');if(!host)return; const id=await adminIdentity(); if(!id.user){host.innerHTML='
🔐

Entre para acessar o painel

O painel administrativo exige autenticação.

';document.querySelector('[data-vna-login]')?.addEventListener('click',()=>navigate('account'));return;} if(!id.isAdmin){host.innerHTML='
⛔

Acesso restrito

Esta conta não possui permissão administrativa.

';return;} host.innerHTML=adminShell(id.user.email); bindShell(); await renderSection(); }; function bindShell(){ document.querySelectorAll('[data-vna-section]').forEach(b=>b.onclick=async()=>{A.section=b.dataset.vnaSection;A.productEdit=null;A.categoryEdit=null;refreshNav();await renderSection();}); document.querySelector('[data-vna-store]')?.addEventListener('click',()=>navigate('home')); document.querySelector('[data-vna-logout]')?.addEventListener('click',async()=>{await db.auth.signOut();navigate('home');}); } async function renderSection(){ refreshNav();content(loading()); if(A.section==='dashboard')return dashboard(); if(A.section==='products')return productsPage(); if(A.section==='categories')return categoriesPage(); if(A.section==='orders')return ordersPage(); if(A.section==='clients')return clientsPage(); if(A.section==='banners')return bannersPage(); if(A.section==='template')return templatePage(); if(A.section==='shipping')return integrationPage('shipping'); if(A.section==='payments')return integrationPage('payments'); } async function dashboard(){ setTitle('Dashboard administrativo'); const [m,o,p]=await Promise.all([ db.rpc('admin_dashboard_metrics',{p_days:A.days}), db.rpc('admin_list_orders'), db.from('products').select('id,title,sku,stock,reserved_stock,low_stock_threshold').order('stock',{ascending:true}).limit(8) ]); if(m.error)return content(`

Não foi possível carregar o dashboard

${safe(m.error.message)}

`); const x=m.data||{},orders=o.data||[],daily=x.daily||[]; const statusCounts={};orders.forEach(v=>statusCounts[v.status]=(statusCounts[v.status]||0)+1);const maxStatus=Math.max(1,...Object.values(statusCounts)); const maxVisitors=Math.max(1,...daily.map(d=>Number(d.visitors||0))); const finalized=Number(x.finalized_sales||0),started=Number(x.checkouts_started||0),conv=started?finalized/started*100:0; const low=(p.data||[]).filter(v=>available(v)<=Number(v.low_stock_threshold||0)); content(`

Performance da loja

Métricas administrativas dos últimos ${A.days} dias. A medição de acessos e carrinhos começa a partir desta versão.

${kpi('◉','Pessoas que acessaram',x.visitors||0,'Visitantes únicos por sessão')} ${kpi('🛒','Carrinhos criados',x.carts||0,'Carrinhos que receberam o 1º item')} ${kpi('→','Vendas iniciadas',x.checkouts_started||0,'Checkouts iniciados')} ${kpi('✓','Vendas finalizadas',x.finalized_sales||0,conv.toFixed(1)+'% das iniciadas')} ${kpi('R$','Faturamento',fmtCents(x.revenue_cents||0),'Pedidos finalizados')}

Acessos por dia

Visitantes únicos medidos no site.

● ao vivo
${daily.map(d=>`
${Number(d.visitors||0)}${safe(d.day)}
`).join('')}

Pedidos por status

Distribuição atual dos pedidos.

${['pending','paid','processing','shipped','delivered','cancelled','refunded'].filter(s=>statusCounts[s]).map(s=>`
${statusLabel(s)}${statusCounts[s]}
`).join('')||'
Ainda não há pedidos.
'}

Indicadores adicionais

Operação do período selecionado.

Vendas em aberto${Number(x.open_sales||0)}
Novos clientes${Number(x.new_clients||0)}
Conversão checkout${conv.toFixed(1)}%

Estoque baixo

Produtos no limite configurado.

${low.length?low.map(v=>`
${safe(v.title)}
${safe(v.sku)}
${available(v)} disp.
`).join(''):'
Nenhum produto em estoque baixo.
'}
`); document.getElementById('vnDays').onchange=async e=>{A.days=Number(e.target.value);await dashboard();}; } function kpi(icon,label,value,note){return `
${safe(label)}
${icon}
${safe(value)}${safe(note)}
`} async function productsPage(){ setTitle('Produtos'); const [pr,co,ca,pc]=await Promise.all([ db.from('products').select('*').order('created_at',{ascending:false}), db.from('product_costs').select('*'), db.from('categories').select('*').order('sort_order'), db.from('product_categories').select('*') ]); if(pr.error)return content(`

Erro ao carregar produtos

${safe(pr.error.message)}

`); const ps=pr.data||[],costs=co.data||[],cats=ca.data||[],rels=pc.data||[],edit=A.productEdit?ps.find(x=>x.id===A.productEdit):null,cost=edit?costs.find(x=>x.product_id===edit.id):{},editCat=edit?rels.find(x=>x.product_id===edit.id)?.category_id:''; content(`

Produtos

Cadastro, precificação, estoque e organização do catálogo.

${edit?'Editar produto':'Cadastrar produto'}

Informações comerciais e operacionais.

${productForm(edit,cost||{},cats,editCat)}

${ps.length} produto(s) cadastrado(s)

${productRows(ps,costs,cats,rels)}
ProdutoCategoriaVendaCustoMargemEstoqueStatus
`); const showEditor=()=>{document.getElementById('vnProductEditor').style.display='block';document.getElementById('vnProductEditor').scrollIntoView({behavior:'smooth',block:'start'});bindProductForm(ps,costs,cats,rels);}; document.getElementById('vnNewProduct').onclick=()=>{A.productEdit=null;productsPage().then(()=>{document.getElementById('vnProductEditor').style.display='block';bindProductForm(ps,costs,cats,rels);});}; document.getElementById('vnCloseProduct').onclick=()=>{A.productEdit=null;document.getElementById('vnProductEditor').style.display='none';}; document.querySelectorAll('[data-vnp-edit]').forEach(b=>b.onclick=()=>{A.productEdit=b.dataset.vnpEdit;productsPage();}); document.querySelectorAll('[data-vnp-stock]').forEach(b=>b.onclick=async()=>{const p=ps.find(x=>x.id===b.dataset.vnpStock);const val=prompt(`Ajuste de estoque para ${p.title}.\nUse positivo para entrada e negativo para saída:`,'1');if(val===null)return;const d=Number(val);if(!Number.isFinite(d)||d===0)return toast('Informe uma variação válida.');const reason=prompt('Motivo do ajuste:','Ajuste manual')||'Ajuste manual';const r=await db.rpc('admin_adjust_stock',{p_product_id:p.id,p_delta:d,p_reason:reason});if(r.error)return toast(r.error.message);toast('Estoque atualizado.');productsPage();}); document.getElementById('vnProductSearch').oninput=e=>{const q=e.target.value.toLowerCase().trim();const list=ps.filter(p=>[p.title,p.sku,p.brand,p.universe].join(' ').toLowerCase().includes(q));document.getElementById('vnProductRows').innerHTML=productRows(list,costs,cats,rels);document.querySelectorAll('[data-vnp-edit]').forEach(b=>b.onclick=()=>{A.productEdit=b.dataset.vnpEdit;productsPage();});document.querySelectorAll('[data-vnp-stock]').forEach(b=>b.onclick=async()=>{A.productEdit=null;const p=ps.find(x=>x.id===b.dataset.vnpStock);const val=prompt(`Ajuste de estoque para ${p.title}:`,'1');if(val===null)return;const r=await db.rpc('admin_adjust_stock',{p_product_id:p.id,p_delta:Number(val),p_reason:'Ajuste manual'});if(r.error)return toast(r.error.message);productsPage();});}; if(edit)bindProductForm(ps,costs,cats,rels); } function productForm(p,c,cats,categoryId){p=p||{};c=c||{};const img=(p.image_urls&&p.image_urls[0])||'';return `
${p.id?'':`
`}
`} function productRows(ps,costs,cats,rels){return ps.map(p=>{const c=costs.find(x=>x.product_id===p.id)||{},catId=rels.find(x=>x.product_id===p.id)?.category_id,cat=cats.find(x=>x.id===catId),fee=Math.round(Number(p.price_cents||0)*(Number(c.estimated_fee_percent||0)/100)),profit=Number(p.price_cents||0)-Number(c.unit_cost_cents||0)-Number(c.packaging_cost_cents||0)-fee,margin=p.price_cents?profit/p.price_cents*100:0,av=available(p);return `${safe(p.title)}
${safe(p.sku)}${safe(cat?.name||'—')}${fmtCents(p.price_cents)}${fmtCents(c.unit_cost_cents||0)}${margin.toFixed(1)}%${av} disp.
${p.reserved_stock||0} reservado${p.active?'Ativo':'Inativo'} `}).join('')||'Nenhum produto cadastrado.'} function bindProductForm(ps,costs,cats,rels){const f=document.getElementById('vnProductForm');if(!f)return;document.getElementById('vnCancelProduct').onclick=()=>{A.productEdit=null;productsPage();};f.onsubmit=async e=>{e.preventDefault();const fd=new FormData(f),id=fd.get('id')||null,title=String(fd.get('title')||'').trim();const payload={sku:String(fd.get('sku')||'').trim(),title,slug:String(fd.get('slug')||'').trim()||slugify(title),brand:fd.get('brand')||null,universe:fd.get('universe')||null,description:fd.get('description')||null,barcode:fd.get('barcode')||null,price_cents:cents(fd.get('price')),compare_at_price_cents:fd.get('compare')?cents(fd.get('compare')):null,low_stock_threshold:Number(fd.get('low')||0),active:fd.get('active')==='on',featured:fd.get('featured')==='on',image_urls:fd.get('image')?[String(fd.get('image')).trim()]:[]};let r;if(id)r=await db.from('products').update(payload).eq('id',id).select().single();else{payload.stock=Number(fd.get('stock')||0);r=await db.from('products').insert(payload).select().single();}if(r.error)return toast(r.error.message);const p=r.data;const cr=await db.from('product_costs').upsert({product_id:p.id,unit_cost_cents:cents(fd.get('cost')),packaging_cost_cents:cents(fd.get('pack')),estimated_fee_percent:Number(fd.get('fee')||0)});if(cr.error)return toast(cr.error.message);await db.from('product_categories').delete().eq('product_id',p.id);if(fd.get('category_id')){const rr=await db.from('product_categories').insert({product_id:p.id,category_id:fd.get('category_id')});if(rr.error)return toast(rr.error.message);}if(!id&&payload.stock>0)await db.from('inventory_movements').insert({product_id:p.id,movement_type:'initial',stock_delta:payload.stock,reason:'Estoque inicial'});toast(id?'Produto atualizado.':'Produto cadastrado.');A.productEdit=null;productsPage();};} async function categoriesPage(){ setTitle('Categorias'); const [ca,pc]=await Promise.all([db.from('categories').select('*').order('sort_order'),db.from('product_categories').select('*')]);if(ca.error)return content(`

Erro ao carregar categorias

${safe(ca.error.message)}

`);const cats=ca.data||[],rels=pc.data||[],edit=A.categoryEdit?cats.find(x=>x.id===A.categoryEdit):null; content(`

Categorias

Organize a navegação e a classificação dos produtos.

${edit?'Editar categoria':'Nova categoria'}

Nome, slug e ordem de exibição.

${edit?'':''}

${cats.length} categoria(s)

${cats.map(c=>``).join('')||''}
CategoriaSlugProdutosOrdemStatus
${safe(c.name)}${safe(c.slug)}${rels.filter(r=>r.category_id===c.id).length}${c.sort_order}${c.active?'Ativa':'Inativa'}
Nenhuma categoria.
`); document.querySelectorAll('[data-vnc-edit]').forEach(b=>b.onclick=()=>{A.categoryEdit=b.dataset.vncEdit;categoriesPage();});document.getElementById('vnCancelCategory')?.addEventListener('click',()=>{A.categoryEdit=null;categoriesPage();});document.getElementById('vnCategoryForm').onsubmit=async e=>{e.preventDefault();const fd=new FormData(e.currentTarget),id=fd.get('id')||null,name=String(fd.get('name')||'').trim(),payload={name,slug:String(fd.get('slug')||'').trim()||slugify(name),sort_order:Number(fd.get('sort_order')||0),active:fd.get('active')==='on'};const r=id?await db.from('categories').update(payload).eq('id',id):await db.from('categories').insert(payload);if(r.error)return toast(r.error.message);toast(id?'Categoria atualizada.':'Categoria cadastrada.');A.categoryEdit=null;categoriesPage();}; } async function ordersPage(){ setTitle('Pedidos');const r=await db.rpc('admin_list_orders');if(r.error)return content(`

Erro ao carregar pedidos

${safe(r.error.message)}

`);const orders=r.data||[]; content(`

Pedidos

Acompanhe o ciclo do pedido até a conclusão e liberação do Vault XP.

${orders.length} pedido(s)

${orderRows(orders)}
PedidoClienteDataItensTotalXPStatus
`); const refresh=()=>{const q=document.getElementById('vnOrderSearch').value.toLowerCase().trim(),st=document.getElementById('vnOrderFilter').value;const list=orders.filter(o=>(!st||o.status===st)&&(!q||[o.id,o.customer_email,o.customer_name].join(' ').toLowerCase().includes(q)));document.getElementById('vnOrderRows').innerHTML=orderRows(list);bindOrderSelects();};document.getElementById('vnOrderSearch').oninput=refresh;document.getElementById('vnOrderFilter').onchange=refresh;bindOrderSelects(); } function orderRows(orders){return orders.map(o=>`#${safe(String(o.id).slice(0,8).toUpperCase())}${safe(o.customer_name||'Cliente')}
${safe(o.customer_email||'—')}${nowLocal(o.created_at)}${o.item_count||0}${fmtCents(o.total_cents)}${o.xp_preview||0}`).join('')||'Nenhum pedido encontrado.'} function bindOrderSelects(){document.querySelectorAll('[data-vno-status]').forEach(s=>s.onchange=async()=>{s.disabled=true;const r=await db.rpc('admin_update_order_status',{p_order_id:s.dataset.vnoStatus,p_status:s.value});s.disabled=false;if(r.error){toast(r.error.message);return ordersPage();}toast('Status do pedido atualizado.');ordersPage();});} async function clientsPage(){ setTitle('Clientes');const r=await db.rpc('admin_list_clients');if(r.error)return content(`

Erro ao carregar clientes

${safe(r.error.message)}

`);const clients=r.data||[]; content(`

Clientes

Base de contas cadastradas e histórico comercial consolidado.

${clients.length} cliente(s)

${clientRows(clients)}
ClienteCadastroÚltimo acessoPedidosCompras finalizadasVault XPÚltimo pedido
`); document.getElementById('vnClientSearch').oninput=e=>{const q=e.target.value.toLowerCase().trim();document.getElementById('vnClientRows').innerHTML=clientRows(clients.filter(c=>[c.display_name,c.email,c.handle].join(' ').toLowerCase().includes(q)));}; } function clientRows(cs){return cs.map(c=>`${safe(c.display_name||c.email||'Cliente')}
${safe(c.handle?'@'+c.handle:c.email||'')}${dateLocal(c.created_at)}${nowLocal(c.last_sign_in_at)}${c.orders_count||0}${fmtCents(c.total_spent_cents||0)}${c.xp_lifetime||0}${nowLocal(c.last_order_at)}`).join('')||'Nenhum cliente encontrado.'} async function uploadSiteAsset(file,folder){ if(!file||!file.size)return null; const ext=(file.name.split('.').pop()||'webp').toLowerCase(); const base=(file.name.replace(/\.[^.]+$/,'')||'asset').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,50); const path=`${folder}/${Date.now()}-${base||'asset'}.${ext}`; const up=await db.storage.from('site-assets').upload(path,file,{cacheControl:'3600',upsert:false}); if(up.error)throw up.error; return db.storage.from('site-assets').getPublicUrl(path).data.publicUrl; } async function bannersPage(){ setTitle('Banners'); const [br,ca]=await Promise.all([ db.from('site_banners').select('*').order('placement').order('sort_order'), db.from('categories').select('*').order('sort_order') ]); if(br.error)return content(`

Erro ao carregar banners

${safe(br.error.message)}

`); const list=br.data||[],cats=ca.data||[],edit=A.bannerEdit?list.find(x=>x.id===A.bannerEdit):null; const placements={home_hero:'Banner principal da Home',home_category:'Banner de categoria',home_promo:'Banner promocional',cofre_hero:'Banner do Meu Cofre'}; content(`

Banners e campanhas visuais

Troque as imagens sazonais da Home e do Meu Cofre sem alterar o código do site.

${edit?'Editar banner':'Novo banner'}

Imagem desktop, mobile, período de exibição e chamada.

${edit?.image_url?`
Imagem atual
`:''}
${edit?'':''}

${list.length} banner(s)

Imagens em Storage • até 10 MB
${list.length?list.map(b=>`
${b.image_url?``:'sem imagem'}
${safe(b.name)}${safe(placements[b.placement]||b.placement)}${b.category_id?' • categoria vinculada':''}${b.starts_at?dateLocal(b.starts_at):'imediato'} → ${b.ends_at?dateLocal(b.ends_at):'sem término'}
${b.active?'Ativo':'Inativo'}
`).join(''):'

Nenhum banner cadastrado

Crie o primeiro banner sazonal para a loja.

'}
`); const editor=document.getElementById('vnBannerEditor'); document.getElementById('vnNewBanner').onclick=()=>{A.bannerEdit=null;editor.style.display='block';document.getElementById('vnBannerForm').reset();editor.scrollIntoView({behavior:'smooth'});}; document.getElementById('vnCloseBanner').onclick=()=>{A.bannerEdit=null;bannersPage();}; document.querySelectorAll('[data-vn-banner-edit]').forEach(b=>b.onclick=()=>{A.bannerEdit=b.dataset.vnBannerEdit;bannersPage();}); document.getElementById('vnBannerForm').onsubmit=async e=>{ e.preventDefault();const fd=new FormData(e.currentTarget),id=fd.get('id')||null; try{ const current=id?list.find(x=>x.id===id):null; const desktop=fd.get('desktop_file'),mobile=fd.get('mobile_file'); const imageUrl=desktop&&desktop.size?await uploadSiteAsset(desktop,'banners'):current?.image_url||null; const mobileUrl=mobile&&mobile.size?await uploadSiteAsset(mobile,'banners-mobile'):current?.mobile_image_url||null; const payload={ name:String(fd.get('name')||'').trim(), placement:fd.get('placement'), title:String(fd.get('title')||'').trim()||null, subtitle:String(fd.get('subtitle')||'').trim()||null, image_url:imageUrl,mobile_image_url:mobileUrl, cta_label:String(fd.get('cta_label')||'').trim()||null, cta_url:String(fd.get('cta_url')||'').trim()||null, category_id:fd.get('category_id')||null, active:fd.get('active')==='on', sort_order:Number(fd.get('sort_order')||0), starts_at:fd.get('starts_at')?new Date(fd.get('starts_at')).toISOString():null, ends_at:fd.get('ends_at')?new Date(fd.get('ends_at')).toISOString():null }; const r=id?await db.from('site_banners').update(payload).eq('id',id):await db.from('site_banners').insert(payload); if(r.error)throw r.error; A.bannerEdit=null;toast(id?'Banner atualizado.':'Banner criado.');await bannersPage(); }catch(err){toast(err.message||String(err));} }; document.getElementById('vnDeleteBanner')?.addEventListener('click',async()=>{if(!edit)return;const r=await db.from('site_banners').delete().eq('id',edit.id);if(r.error)return toast(r.error.message);A.bannerEdit=null;toast('Banner excluído.');bannersPage();}); } async function templatePage(){ setTitle('Editor de Template'); const r=await db.from('site_content_blocks').select('*').order('page').order('sort_order'); if(r.error)return content(`

Erro ao carregar conteúdo

${safe(r.error.message)}

`); const blocks=r.data||[]; const pages=[ {id:'home',label:'Home',desc:'Vitrine e conteúdo comercial da loja'}, {id:'cofre',label:'Meu Cofre',desc:'Experiência da coleção virtual'}, {id:'global',label:'Global',desc:'Conteúdos usados em mais de uma área'} ]; const sectionNames={hero:'Hero principal',popular:'Mais procurados',categories:'Categorias',promo:'Bloco promocional',differentials:'Diferenciais',shelf:'Minha prateleira',customization:'Personalização',achievements:'Conquistas',stats:'Estatísticas'}; const sectionIcons={hero:'⚡',popular:'🔥',categories:'🎮',promo:'★',differentials:'◆',shelf:'⬡',customization:'✎',achievements:'🏆',stats:'▥'}; A.templatePage=A.templatePage||'home'; let pageBlocks=blocks.filter(b=>b.page===A.templatePage); if(!pageBlocks.length){A.templatePage='home';pageBlocks=blocks.filter(b=>b.page==='home');} if(!A.templateBlock||!pageBlocks.some(b=>b.id===A.templateBlock))A.templateBlock=pageBlocks[0]?.id||null; const current=pageBlocks.find(b=>b.id===A.templateBlock)||null; const blockTitle=b=>b.title||sectionNames[b.section]||b.block_key; const blockSummary=b=>b.subtitle||b.eyebrow||sectionNames[b.section]||'Conteúdo editável'; const navItem=b=>``; const editor=current?`
${safe(current.icon||sectionIcons[current.section]||'•')}

${safe(blockTitle(current))}

${safe(sectionNames[current.section]||current.section)} • ${pages.find(p=>p.id===current.page)?.label||current.page}

Salvo

Conteúdo principal

Textos que aparecem diretamente para o cliente.

Texto pequeno acima do título.
Use um símbolo curto que represente a seção.
Priorize uma mensagem curta e fácil de escanear.

Botão e destino

Deixe em branco quando a seção não precisar de CTA.

Pode ser uma seção interna ou uma URL.

Exibição

Controle se esse bloco fica visível na loja.

Exibir este blocoQuando desligado, o conteúdo permanece salvo, mas some da loja.

Pré-visualização do conteúdo

Uma leitura rápida de como a hierarquia ficará para o cliente.

Identificador técnico${safe(current.block_key)}
`:`
▤

Nenhum bloco disponível

Não há conteúdo editável nesta página.

`; content(`

Editor de Template

Edite o conteúdo da loja por seção, sem precisar mexer no código.

Publicação imediata ao salvar
${pages.map(p=>``).join('')}
${editor}
`); document.querySelectorAll('[data-vntpl-page]').forEach(btn=>btn.onclick=()=>{A.templatePage=btn.dataset.vntplPage;A.templateBlock=null;templatePage();}); document.querySelectorAll('[data-vntpl-block]').forEach(btn=>btn.onclick=()=>{A.templateBlock=btn.dataset.vntplBlock;templatePage();}); const form=document.getElementById('vnTplForm'); if(!form)return; const stateEl=document.getElementById('vnTplState'); const preview=()=>{const fd=new FormData(form),q=s=>String(fd.get(s)||'').trim();const box=document.getElementById('vnTplPreview');if(!box)return;box.querySelector('.vn-tpl-preview-eyebrow').textContent=q('eyebrow');box.querySelector('.vn-tpl-preview-title').textContent=q('title')||'Título da seção';box.querySelector('.vn-tpl-preview-sub').textContent=q('subtitle');box.querySelector('.vn-tpl-preview-body').textContent=q('body');const b=box.querySelector('.vn-tpl-preview-btn');b.textContent=q('cta_label');b.style.display=q('cta_label')?'inline-flex':'none';}; preview(); form.addEventListener('input',()=>{preview();stateEl.textContent='Alterações não salvas';stateEl.className='vn-tpl-state dirty';}); document.getElementById('vnTplDiscard').onclick=()=>templatePage(); form.onsubmit=async e=>{e.preventDefault();const fd=new FormData(form),id=form.dataset.vnContent;const payload={eyebrow:String(fd.get('eyebrow')||'').trim()||null,title:String(fd.get('title')||'').trim()||null,subtitle:String(fd.get('subtitle')||'').trim()||null,body:String(fd.get('body')||'').trim()||null,cta_label:String(fd.get('cta_label')||'').trim()||null,cta_url:String(fd.get('cta_url')||'').trim()||null,icon:String(fd.get('icon')||'').trim()||null,active:fd.get('active')==='on'};stateEl.textContent='Salvando…';stateEl.className='vn-tpl-state';const rr=await db.from('site_content_blocks').update(payload).eq('id',id);if(rr.error){stateEl.textContent='Erro ao salvar';stateEl.className='vn-tpl-state dirty';return toast(rr.error.message);}stateEl.textContent='Salvo';stateEl.className='vn-tpl-state saved';toast('Conteúdo atualizado.');setTimeout(()=>templatePage(),350);}; } function integrationPage(type){ const shipping=type==='shipping';setTitle(shipping?'Integração de envios':'Gateway de pagamento'); content(`

${shipping?'Envios':'Pagamentos'}

${shipping?'Central futura para cálculo e contratação de frete.':'Central futura para cobrança e regras de parcelamento.'}

Aguardando integração
${shipping?'🚚':'💳'}

Aguardando integração

${shipping?'Nenhum provedor de frete está conectado. Nesta fase não há cotação, etiqueta ou rastreamento automático.':'Nenhum gateway está conectado. Nesta fase o checkout não realiza cobranças reais.'}

O que será configurado aqui

Estrutura reservada para a próxima fase do e-commerce.

${(shipping?['Provedor de envios e credenciais','CEP de origem e regras de embalagem','Serviços habilitados e prazo adicional','Cálculo de frete no carrinho e checkout','Geração de etiqueta e rastreamento']:['Gateway e credenciais','PIX, cartão e demais meios de pagamento','Número máximo de parcelas','Valor mínimo por parcela','Juros por faixa de parcelamento','Webhooks de pagamento e estorno']).map(x=>`
○ ${x}
`).join('')}
`); } // Analytics do storefront. Não inclui a área administrativa nos visitantes. const sidKey='vn_analytics_session',cartKey='vn_analytics_cart'; const makeId=()=>window.crypto?.randomUUID?.()||('vn-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2)); const getSid=()=>{let v=sessionStorage.getItem(sidKey);if(!v){v=makeId();sessionStorage.setItem(sidKey,v)}return v}; const getCart=()=>{let v=localStorage.getItem(cartKey);if(!v){v=makeId();localStorage.setItem(cartKey,v)}return v}; async function track(event,page,cart,order){try{await db.rpc('track_analytics_event',{p_event_type:event,p_session_id:getSid(),p_page_path:page||null,p_cart_id:cart||null,p_order_id:order||null,p_metadata:{}})}catch(e){}} const prevNavigate=window.navigate; window.navigate=function(page){document.body.classList.toggle('vn-admin-mode',page==='admin');const r=prevNavigate(page);if(page!=='admin')track('page_view',page||'home',null,null);return r}; const prevAdd=window.addToCart; window.addToCart=function(id){const before=state.cart.length;const r=prevAdd(id);if(before===0&&state.cart.length>0)track('cart_created',location.hash.replace('#','')||'home',getCart(),null);return r}; const prevCheckout=window.openCheckout; window.openCheckout=function(){if(state.cart.length)track('checkout_started','checkout',getCart(),null);return prevCheckout();}; const prevFinish=window.finishDemoOrder; window.finishDemoOrder=async function(){const cart=getCart();const before=state.orders.length;const r=await prevFinish();if(state.orders.length>before){const last=state.orders[state.orders.length-1];track('order_created','checkout',cart,null);localStorage.removeItem(cartKey)}return r}; track('page_view',location.hash.replace('#','')||'home',null,null); if(location.hash==='#admin'){document.body.classList.add('vn-admin-mode');setTimeout(()=>window.renderAdmin(),50)} })();