ESSENTIALS — HNT — ESSENTIALS — HNT — ESSENTIALS — HNT —
NEW DROP — WEAR IT SOFT — NEW DROP — WEAR IT SOFT — NEW DROP — WEAR IT SOFT —
Shop nowSCROLL OVER THE CARDS TO SPIN
WEAR IT SOFT ✦ HNT ESSENTIALS ✦ WEAR IT SOFT ✦ HNT ESSENTIALS ✦
Featured

Pieces that go together.

View full shop

Shop by piece
The Shop

All essentials.

New Collection

The earth-tone drop.

Fresh pieces in brown and paired combos — built to be worn together.

Booking

Book your pieces.

© 2026 HNT Essentials. All rights reserved.
'); const ST=['Pending','Confirmed','Processing','Shipped','Delivered','Cancelled'],PS=['Unpaid','Paid','Failed','Partially refunded','Refunded'],RL=['Super Admin','Store Manager','Inventory Manager','Order Manager']; const adm=$('#adm'),E=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); let S,DBC=null,US=null,DL=null,cur='dash',ok=false,per='day',qs='',sf='',tm; const now=()=>new Date().toISOString(),day=d=>String(d).slice(0,10),uid=()=>Math.random().toString(36).slice(2,8),N=v=>+v||0; const tot=o=>o.items.reduce((a,i)=>a+i.q*i.pr,0)+N(o.ship)-N(o.disc),rfd=o=>(o.ref||[]).reduce((a,r)=>a+r.a,0),rev=o=>tot(o)-rfd(o),live=()=>S.orders.filter(o=>o.st!='Cancelled'); const stk=p=>p.vars.reduce((a,v)=>a+N(v.stock),0),pp=id=>S.prods.find(p=>p.id==id),nm=id=>(pp(id)||{n:id}).n; const hex=b=>[...new Uint8Array(b)].map(x=>x.toString(16).padStart(2,'0')).join(''),kdf=async(p,s)=>{const k=await crypto.subtle.importKey('raw',new TextEncoder().encode(p),'PBKDF2',false,['deriveBits']);return hex(await crypto.subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt:Uint8Array.from(atob(s),c=>c.charCodeAt(0)),iterations:150000},k,256))},setpw=async p=>{const s=btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));S.auth={s,h:await kdf(p,s)}}; const aud=a=>{S.audit.unshift({a,d:now()});S.audit.length=Math.min(S.audit.length,200)}; function seed(){return{prods:P.map(p=>({id:p.id,n:p.n,sku:'HNT-'+p.id.toUpperCase().replace(/-/g,'').slice(0,12),desc:'',cat:p.t,col:'',p:p.p,sale:0,cost:Math.round(p.p*.55),st:'Published',feat:0,best:0,tags:'',mat:'',fab:'',care:'',img:'',vid:'',vars:['S','M','L','XL'].map(s=>({sz:s,c:p.c,sku:'',p:0,stock:20,off:0}))})),orders:[],coupons:[],log:[],cust:{},set:{brand:'HNT Essentials',wa:'',ship:250,free:10000,low:5,days:'3-6',couriers:'TCS, Leopards, M&P',policy:'Exchange available within 7 days of delivery.',bank_name:'Sample Bank Ltd',bank_title:'Account Title',bank_acc:'0000-0000000000',jc_title:'Account Title',jc_num:'03XX-XXXXXXX',ep_title:'Account Title',ep_num:'03XX-XXXXXXX',ig_url:'https://www.instagram.com/',fb_url:'https://www.facebook.com/',tt_url:'https://www.tiktok.com/'},users:[{u:'admin',r:'Super Admin',h:''}],audit:[],auth:null,pv:0,tx:{},im:{}}} function save(){clearTimeout(tm);tm=setTimeout(()=>{try{const j=JSON.parse(JSON.stringify(S));try{if(lastLive===null)lastLive=snap;const b=JSON.stringify(pubBody());if(b!==lastLive){lastLive=b;chg=1;const o=Object.assign({v:Date.now()},pubBody());try{localStorage.setItem('hnt-live',JSON.stringify(o))}catch(e){o.im={};o.prods.forEach(p=>{if(p.img&&p.img.length>1000)p.img=''});try{localStorage.setItem('hnt-live',JSON.stringify(o))}catch(e2){}}}}catch(e){}DBC&&DBC.doc('store/main').set(j).catch(()=>{});(SRV||SBM?0:useLS?localStorage.setItem('hnt-admin',JSON.stringify(j)):idbSet('state',j).then(()=>{ok&&toast('✓ Saved')}).catch(()=>{useLS=true;try{localStorage.setItem('hnt-admin',JSON.stringify(j))}catch(e){toast('Storage full: use smaller images')}}))}catch(x){}},250);if((SRV||SBM)&&ok){clearTimeout(pt);pt=setTimeout(push,1200)}} function inbox(ib0,loc){try{const ib=ib0||JSON.parse(localStorage.getItem('hnt-inbox')||'[]');if(!ib.length)return;ib.forEach(x=>{const o={id:'HNT-'+(1001+S.orders.length),d:x.d,n:x.n,ph:x.ph,city:x.city,ad:x.ad,items:x.items,ship:0,disc:0,st:'Pending',pay:(x.pm&&x.pm!='Cash on Delivery')?x.pm:(x.m=='Pickup'?'Pickup':'COD'),ps:'Unpaid',trk:'',cour:'',notes:x.nt?[{t:x.nt,d:x.d}]:[],hist:[{s:'Pending',d:x.d}],ref:[]};o.ship=x.m=='Pickup'?0:(tot(o)>=N(S.set.free)?0:N(S.set.ship));S.orders.unshift(o);stock(o,-1)});if(loc!==0)localStorage.setItem('hnt-inbox','[]');save()}catch(x){}} function stock(o,sg){o.items.forEach(i=>{const p=pp(i.id);if(!p)return;const v=p.vars.find(v=>v.sz==i.s);if(v){v.stock=Math.max(0,N(v.stock)+sg*i.q);S.log.unshift({d:now(),p:p.id,sz:v.sz,dl:sg*i.q,r:(sg<0?'Order ':'Cancelled ')+o.id})}})} function custs(){const m={};S.orders.forEach(o=>{const k=o.ph||o.n,c=m[k]||(m[k]={k,n:o.n,ph:o.ph,city:o.city,ad:new Set(),o:[],sp:0,first:Infinity});c.o.push(o);c.ad.add(o.ad);if(o.st!='Cancelled')c.sp+=rev(o);c.first=Math.min(c.first,+new Date(o.d))});return Object.values(m)} const kf=d=>{d=new Date(d);if(per=='month')return d.toISOString().slice(0,7);if(per=='week')d.setDate(d.getDate()-((d.getDay()+6)%7));return day(d.toISOString())}; function chart(os){const n=per=='day'?14:per=='week'?8:6,ks=[];for(let i=n-1;i>=0;i--){const d=new Date();if(per=='day')d.setDate(d.getDate()-i);else if(per=='week')d.setDate(d.getDate()-i*7);else d.setMonth(d.getMonth()-i,1);ks.push(kf(d))} const v=ks.map(k=>os.filter(o=>kf(o.d)==k).reduce((a,o)=>a+rev(o),0)),mx=Math.max(1,...v);return `${v.map((x,i)=>`${ks[i].slice(per=='month'?2:5)}`).join('')}`} const T=(h,r)=>`
${h.map(x=>``).join('')}${r.map(x=>`${x.map(c=>``).join('')}`).join('')||``}
${x}
${c}
Nothing here yet.
`; const K=(l,v)=>`
${l}${v}
`,B=(a,l,id,c='')=>``; const inp=(n,l,v,t='text')=>``,sel=(n,l,v,o)=>``,chk=(n,l,v)=>``,ta=(n,l,v)=>``; function M(t,h,fn,lab='Save'){$('#am').innerHTML=`

${t}

${h}
${fn?``:''}
`;$('#am').classList.add('on');M.f=fn} const fd=()=>new FormData($('#amf')),mx=()=>$('#am').classList.remove('on'); function dl(n,s,ty){if(DL){DL.save({filename:n,data:s}).catch(()=>{});return}const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([s],{type:ty||'text/plain'}));a.download=n;a.click()} function csv(name,rows){dl(name,rows.map(r=>r.map(c=>'"'+String(c==null?'':c).replace(/"/g,'""')+'"').join(',')).join('\n'),'text/csv')} /* views */ const V={ dash(){const L=live(),R=L.reduce((a,o)=>a+rev(o),0),cs=custs(),low=[];S.prods.forEach(p=>p.vars.forEach(v=>{if(N(v.stock)<=N(S.set.low))low.push([p.n,v.sz,v.c,N(v.stock)])}));const bs={};L.forEach(o=>o.items.forEach(i=>bs[i.id]=(bs[i.id]||0)+i.q)); const sold=Object.entries(bs).sort((a,b)=>b[1]-a[1]).slice(0,5); return `
${K('Revenue',money(R))}${K('Orders',S.orders.length)}${K('Pending',S.orders.filter(o=>o.st=='Pending').length)}${K('Customers',cs.length)}${K('New (30d)',cs.filter(c=>c.first>Date.now()-2592e6).length)}${K('Avg order',money(L.length?Math.round(R/L.length):0))}

Sales ${['day','week','month'].map(x=>``).join('')}

${chart(L)}

Best sellers

${T(['Product','Sold'],sold.map(s=>[E(nm(s[0])),s[1]]))}

Stock alerts

${T(['Product','Size','Colour','Stock'],low.slice(0,12).map(l=>[E(l[0]),l[1],l[2],l[3]?l[3]:'Sold out']))}

Recent orders

${T(['Order','Customer','Total','Status'],S.orders.slice(0,6).map(o=>[o.id,E(o.n),money(tot(o)),o.st]))}`}, prod(){return `

Products

${T(['','Name','SKU','Price','Status','Stock',''],S.prods.map(p=>[``,E(p.n)+(p.feat?' ★':'')+(p.best?' 🔥':''),E(p.sku),money(N(p.sale)&&N(p.sale)p.vars.forEach((v,i)=>{val+=N(v.stock)*N(p.cost);r.push([E(p.n),v.sz,E(v.c),E(v.sku||p.sku),N(v.stock),money(N(v.stock)*N(p.cost)),N(v.stock)<=0||v.off?'Sold out':N(v.stock)<=N(S.set.low)?'Low':'OK',B('adj','Adjust',p.id+'|'+i)])})); return `

Inventory

${K('Units',S.prods.reduce((a,p)=>a+stk(p),0))}${K('Valuation (cost)',money(val))}${K('Low threshold',S.set.low)}
${T(['Product','Size','Colour','SKU','Stock','Value','State',''],r)}

Stock history

${T(['Date','Product','Size','Change','Reason / supplier'],S.log.slice(0,40).map(l=>[day(l.d),E(nm(l.p)),l.sz,l.dl>0?'+'+l.dl:l.dl,E(l.r)]))}`}, ord(){const L=S.orders.filter(o=>(!sf||o.st==sf)&&(!qs||(o.id+o.n+o.ph).toLowerCase().includes(qs.toLowerCase())));return `

Orders

${T(['Order','Date','Customer','Items','Total','Payment','Status',''],L.map(o=>[o.id,day(o.d),E(o.n),o.items.reduce((a,i)=>a+i.q,0),money(tot(o)),o.pay+' · '+o.ps,o.st,B('oview','Open',o.id)]))}`}, cus(){return `

Customers

${T(['Customer','Phone','City','Orders','Spent','Avg order','Group','Note'],custs().map(c=>{const m=S.cust[c.k]||{};return [E(c.n),E(c.ph),E(c.city),c.o.length,money(c.sp),money(Math.round(c.sp/Math.max(1,c.o.filter(o=>o.st!='Cancelled').length))),``,``]}))}`}, pay(){const L=S.orders,paid=L.filter(o=>o.ps=='Paid').reduce((a,o)=>a+tot(o),0),cod=L.filter(o=>o.pay=='COD'&&o.ps=='Unpaid'&&o.st!='Cancelled').reduce((a,o)=>a+tot(o),0),rf=[];L.forEach(o=>(o.ref||[]).forEach(r=>rf.push([day(r.d),o.id,money(r.a),E(r.r)]))); return `

Payments & Refunds

${K('Paid',money(paid))}${K('COD pending',money(cod))}${K('Refunded',money(L.reduce((a,o)=>a+rfd(o),0)))}${K('Failed',L.filter(o=>o.ps=='Failed').length)}
${T(['Order','Method','Amount','Payment status',''],L.map(o=>[o.id,o.pay,money(tot(o)),``,B('ref','Refund',o.id)]))}

Refund history

${T(['Date','Order','Amount','Reason'],rf)}`}, disc(){return `

Discounts

${T(['Code','Type','Value','Min order','Valid','Used / limit','Free ship',''],S.coupons.map(c=>[E(c.code),c.type,c.value,money(N(c.min)),E((c.from||'…')+' → '+(c.to||'…')),(c.used||0)+' / '+(c.lim||'∞'),c.fs?'Yes':'No',B('cedit','Edit',c.id)+B('cdel','Delete',c.id,'dg')]))}

Coupons are stored here; checkout does not apply them automatically yet.

`}, ship(){const s=S.set;return `

Shipping & Delivery

${inp('ship','Shipping charge (Rs.)',s.ship,'number')}${inp('free','Free shipping above (Rs.)',s.free,'number')}${inp('days','Estimated delivery (days)',s.days)}${inp('couriers','Couriers (comma separated)',s.couriers)}

Dispatch & RTO

${T(['Order','Customer','City','Courier','Tracking','Status','RTO'],S.orders.filter(o=>['Processing','Shipped','Delivered'].includes(o.st)).map(o=>[o.id,E(o.n),E(o.city),E(o.cour),E(o.trk),o.st,``]))}`}, rep(){const L=live(),R=L.reduce((a,o)=>a+rev(o),0),cat={},sz={},col={},prf={};L.forEach(o=>o.items.forEach(i=>{const p=pp(i.id)||{},a=i.q*i.pr;cat[p.cat]=(cat[p.cat]||0)+a;sz[i.s]=(sz[i.s]||0)+i.q;col[p.col||'—']=(col[p.col||'—']||0)+i.q;prf[i.id]=(prf[i.id]||0)+i.q}));const cs=custs(),cost=L.reduce((a,o)=>a+o.items.reduce((b,i)=>b+i.q*N((pp(i.id)||{}).cost),0),0),ps=S.prods.map(p=>[p.n,prf[p.id]||0]).sort((a,b)=>b[1]-a[1]); const kv=o=>Object.entries(o).map(x=>[E(x[0]),x[1]]); return `

Reports

${K('Revenue',money(R))}${K('Cost of goods',money(cost))}${K('Gross profit',money(R-cost))}${K('Cancel rate',(S.orders.length?Math.round(S.orders.filter(o=>o.st=='Cancelled').length/S.orders.length*100):0)+'%')}${K('Repeat customers',(cs.length?Math.round(cs.filter(c=>c.o.length>1).length/cs.length*100):0)+'%')}

Best sellers

${T(['Product','Units'],ps.slice(0,5).map(x=>[E(x[0]),x[1]]))}

Worst sellers

${T(['Product','Units'],ps.slice(-5).reverse().map(x=>[E(x[0]),x[1]]))}

By category (revenue)

${T(['Category','Rs.'],kv(cat))}

By size (units)

${T(['Size','Units'],kv(sz))}

By colour (units)

${T(['Colour','Units'],kv(col))}`}, web(){const ks=[...document.querySelectorAll('[data-e]')].map(e=>e.dataset.e),sp={mq1:['Hero row 1 text','ESSENTIALS — HNT'],mq2:['Hero row 2 text','NEW DROP — WEAR IT SOFT'],band:['Scrolling banner text','WEAR IT SOFT ✦ HNT ESSENTIALS']},val=k=>S.tx[k]!=null?S.tx[k]:((document.querySelector('[data-e="'+k+'"]')||{}).textContent||''); return `

Website Editor

Click “Edit on the page” to change text directly on your site (dashed outlines) and to replace the logo or a hero card by clicking it. You can also change the wording below.

${Object.keys(sp).map(k=>inp(k,sp[k][0],S.tx[k]!=null?S.tx[k]:sp[k][1])).join('')}${ks.map(k=>inp(k,k.replace(/_/g,' '),val(k))).join('')}
`}, set(){const s=S.set;return `

Store Settings

${inp('brand','Brand name',s.brand)}${inp('wa','Booking WhatsApp number (country code, no +)',s.wa)}

Payment details shown to customers

${inp('bank_name','Bank name',s.bank_name)}${inp('bank_title','Bank account title',s.bank_title)}${inp('bank_acc','Bank account number / IBAN',s.bank_acc)}${inp('jc_title','JazzCash account title',s.jc_title)}${inp('jc_num','JazzCash number',s.jc_num)}${inp('ep_title','Easypaisa account title',s.ep_title)}${inp('ep_num','Easypaisa number',s.ep_num)}

Social media links (shown in the website footer)

${inp('fb_url','Facebook link. Leave empty to hide',s.fb_url)}${inp('ig_url','Instagram link (or @username). Leave empty to hide',s.ig_url)}${inp('tt_url','TikTok link (or @username). Leave empty to hide',s.tt_url)}${inp('low','Low-stock threshold',s.low,'number')}${ta('policy','Exchange policy (shown on the website)',s.policy)}

Live saving (online database)

Makes every edit appear on your live site instantly, with no download and no re-upload. Works on Netlify. 1) Create a free project at supabase.com. 2) Type your admin email below, click “Copy setup SQL”, and run it in Supabase → SQL Editor. 3) Supabase → Authentication → Users → Add user (that email + a password). 4) Paste the Project URL and anon key below and Save. 5) Download the updated website ONE time and upload it to Netlify.

${inp('sb_url','Supabase Project URL',(S.sb||{}).url||'')}${inp('sb_key','Supabase anon public key',(S.sb||{}).key||'')}${inp('sb_mail','Your admin email (used in the SQL)',(S.sb||{}).mail||'')}

Save & publish

Edits are saved automatically in this browser and survive closing it. To show them to every visitor, download the updated website and upload it to your host in place of index.html.

Backup

Orders, customers, costs and stock stay private and are never written into your public website file. Keep a backup.

Products, prices, status, WhatsApp number and the admin password go into the website file when you download the updated website.

`}, usr(){return `

Users & Security

${T(['User','Role','Password',''],S.users.map(u=>[E(u.u),u.r,u.u=='admin'?(S.auth||SRV?'Set':'Not set'):'—',(u.u=='admin'?B('upw','Change password',u.u):B('udel','Remove',u.u,'dg'))]))}

Roles are labels for now. Browser-only login is not real security; use your host's access control for production.

Audit log

${T(['Date','Action'],S.audit.slice(0,40).map(a=>[a.d.slice(0,16).replace('T',' '),E(a.a)]))}`}}; const NAV=[['dash','Dashboard'],['prod','Products'],['inv','Inventory'],['ord','Orders'],['cus','Customers'],['pay','Payments & Refunds'],['disc','Discounts'],['ship','Shipping'],['rep','Reports'],['web','Website Editor'],['set','Store Settings'],['usr','Users & Audit']]; function R(){if(!ok)return;const y=$('#amain');adm.innerHTML=`
${SRV||SBM||window.claude?'':'
Live saving is OFF. '+why+' Until it is on, edits stay in this browser only and you must download the updated website and upload it.
'}
${V[cur]()}
`} function login(msg){const st=!window.claude||SRV||SBM,su=SBM?false:SRV?NSETUP:!S.auth;adm.innerHTML=`

${st&&su?'Create admin password':'Admin sign in'}

`+(st?`
${SBM?'':''}${su?'

Minimum 8 characters.

':''}

${SBM?'Live saving: ON ✓ (online database)':SRV?'Live saving: ON ✓':'Live saving: OFF — '+why}

`:US?``:`

Admin is restricted to the store owner. Sign in to claude.ai as the owner and reopen this page.

`)+`

${msg||''}

← Back to store
`} let useLS=false,lastLive=null,chg=0; const idb=()=>new Promise((ok,no)=>{const r=indexedDB.open('hnt_db',1);r.onupgradeneeded=()=>r.result.createObjectStore('kv');r.onsuccess=()=>ok(r.result);r.onerror=()=>no(r.error)}); const idbGet=async k=>{const d=await idb();return new Promise((ok,no)=>{const q=d.transaction('kv').objectStore('kv').get(k);q.onsuccess=()=>ok(q.result);q.onerror=()=>no(q.error)})}; const idbSet=async(k,v)=>{const d=await idb();return new Promise((ok,no)=>{const x=d.transaction('kv','readwrite');x.objectStore('kv').put(v,k);x.oncomplete=ok;x.onerror=()=>no(x.error)})}; const toast=m=>{let e=document.getElementById('atoast');if(!e){e=document.createElement('div');e.id='atoast';e.style.cssText='position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1d1612;color:#fff;padding:10px 18px;border-radius:99px;z-index:300;font-size:14px;transition:opacity .3s';document.body.appendChild(e)}e.textContent=m;e.style.opacity=1;clearTimeout(e._t);e._t=setTimeout(()=>{e.style.opacity=0},1800)}; let SRV=false,NSETUP=false,PW='',AK=0,svs='',pt,pinged=0,why='',SBM=false,IDS=[],TOK='',RT='';try{RT=localStorage.getItem('hnt-rt')||''}catch(e){} const sbc=()=>{let l={};try{l=JSON.parse(localStorage.getItem('hnt-sb')||'{}')}catch(e){}return(S&&S.sb&&S.sb.url)?S.sb:(window.PUB&&PUB.sb&&PUB.sb.url)?PUB.sb:l}; async function sbauth(b,g){const c=sbc(),r=await fetch(c.url+'/auth/v1/token?grant_type='+g,{method:'POST',headers:{apikey:c.key,'Content-Type':'application/json'},body:JSON.stringify(b)}),j=await r.json().catch(()=>({}));if(!r.ok||!j.access_token)throw new Error(j.error_description||j.msg||'Login failed');TOK=j.access_token;RT=j.refresh_token;try{localStorage.setItem('hnt-rt',RT)}catch(e){}} async function sb(path,o){o=o||{};const c=sbc(),go=()=>fetch(c.url+'/rest/v1/'+path,{method:o.m||'GET',headers:Object.assign({apikey:c.key,Authorization:'Bearer '+(TOK||c.key),'Content-Type':'application/json'},o.h||{}),body:o.b?JSON.stringify(o.b):undefined});let r=await go();if(r.status==401&&RT){await sbauth({refresh_token:RT},'refresh_token');r=await go()}if(!r.ok)throw new Error('database error '+r.status);return await r.json().catch(()=>null)} async function sbLoad(){const a=await sb('private?id=eq.1&select=data');if(a&&a[0]&&a[0].data){S=a[0].data;rebase()}const ib=(await sb('inbox?select=id,data&order=id'))||[];IDS=ib.map(x=>x.id);inbox(ib.map(x=>x.data),0);if(!(a&&a[0])||ib.length)push()} async function probe(){SRV=false;SBM=false;why='';{const c=sbc();if(c&&c.url){SBM=true;return}}try{const r=await fetch('save.php',{method:'POST',headers:{'Content-Type':'application/json'},body:'{"a":"ping"}'}),tx=await r.text();let j=null;try{j=JSON.parse(tx)}catch(e){} if(j&&j.ok){SRV=true;NSETUP=!!j.setup;return} why=r.status==404?'save.php was not found next to index.html (error 404). Upload save.php into the same folder as index.html.':(r.status==405||r.status==501)?'This server does not run PHP (error '+r.status+'). You need cPanel web hosting with PHP.':r.status>=400?'The server refused save.php (error '+r.status+').':/<\?php/.test(tx)?'save.php is there but PHP is not running (the server shows the file instead of running it).':'The server answered, but not from save.php. Check that save.php is in the same folder as index.html.'}catch(e){why=location.protocol=='file:'?'You opened the site as a file on your computer. Live saving only works on your real website address.':'Could not reach save.php (network error).'}} async function api(a,b){const r=await fetch('save.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(Object.assign({a},b||{}))});return await r.json()} const stat=()=>{const e=document.getElementById('sv');if(e)e.textContent=svs}; async function push(){svs='Saving…';stat();try{const pub=Object.assign({v:Date.now()},pubBody());S.pv=pub.v;if(SBM){const U={m:'POST',h:{Prefer:'resolution=merge-duplicates,return=minimal'}};await sb('site',Object.assign({b:{id:1,data:pub}},U));await sb('private',Object.assign({b:{id:1,data:S}},U));if(IDS.length){await sb('inbox?id=in.('+IDS.join(',')+')',{m:'DELETE'});IDS=[]}}else{const r=await api('save',{p:PW,pub,priv:S,ack:AK});if(!r.ok)throw new Error(r.err||'the server refused the save');AK=0}svs='✓ Saved to your website '+new Date().toLocaleTimeString().slice(0,5)}catch(x){svs='⚠ Save failed — '+((x&&x.message)||'check your connection')}stat()} const pick=()=>new Promise(r=>{const i=document.createElement('input');i.type='file';i.onchange=()=>i.files[0]?i.files[0].text().then(r):r('');i.click()}); const pubBody=()=>({prods:S.prods.map(p=>({id:p.id,n:p.n,sku:p.sku,desc:p.desc,cat:p.cat,col:p.col,p:N(p.p),sale:N(p.sale),st:p.st,feat:p.feat,best:p.best,tags:p.tags,mat:p.mat,fab:p.fab,care:p.care,img:p.img,vid:p.vid,vars:p.vars.map(v=>({sz:v.sz,c:v.c,p:N(v.p),off:v.off}))})),set:{brand:S.set.brand,wa:S.set.wa,ship:S.set.ship,free:S.set.free,days:S.set.days,couriers:S.set.couriers,policy:S.set.policy,bank_name:S.set.bank_name,bank_title:S.set.bank_title,bank_acc:S.set.bank_acc,jc_title:S.set.jc_title,jc_num:S.set.jc_num,ep_title:S.set.ep_title,ep_num:S.set.ep_num,ig_url:S.set.ig_url,fb_url:S.set.fb_url,tt_url:S.set.tt_url},auth:S.auth||null,tx:S.tx||{},im:S.im||{},sb:S.sb||null}); let snap='';const dirty=()=>!SRV&&!SBM&&JSON.stringify(pubBody())!==snap; function rebase(){S.tx=S.tx||{};S.im=S.im||{};const D=window.PUB;if(!D)return;if(D.reset&&S.rsv!==D.v){S.auth=null;S.rsv=D.v}if((S.pv||0)>=D.v)return;const m={};S.prods.forEach(p=>m[p.id]=p);S.prods=S.prods.filter(p=>D.prods.some(o=>o.id==p.id));D.prods.forEach(o=>{const p=m[o.id],vs=o.vars.map((v,i)=>Object.assign({stock:0,sku:''},p&&p.vars[i],v));if(p)Object.assign(p,o,{vars:vs});else S.prods.push(Object.assign({cost:0},o,{vars:vs}))});Object.assign(S.set,D.set);S.tx=D.tx||S.tx;S.im=D.im||S.im;S.sb=D.sb||S.sb;S.auth=D.auth||S.auth;S.pv=D.v} async function boot(){try{if(window.claude&&claude.use){DBC=await claude.use('db');US=await claude.use('user');DL=await claude.use('downloads')}}catch(x){} try{if(DBC){const s=await DBC.doc('store/main').get();if(s.exists)S=JSON.parse(JSON.stringify(s.data()))}}catch(x){}if(!S){try{S=await idbGet('state')}catch(x){useLS=true}}if(!S){try{S=JSON.parse(localStorage.getItem('hnt-admin')||'null')}catch(x){}}try{navigator.storage&&navigator.storage.persist&&navigator.storage.persist()}catch(x){}S=S||seed();rebase();(function(){const d=seed().set;for(const k in d)if(S.set[k]==null)S.set[k]=d[k]})();snap=JSON.stringify(window.PUB?(({v,...r})=>Object.assign({},r,{tx:r.tx||{},im:r.im||{}}))(window.PUB):pubBody())} const ready=boot(); window.ADM={async open(){adm.style.display='block';document.body.style.overflow='hidden';await ready;if(!pinged){pinged=1;await probe();if(!SRV&&!SBM)inbox();if(SBM&&RT){try{await sbauth({refresh_token:RT},'refresh_token');await sbLoad();ok=true}catch(e){}}}ok?R():login()},close(){const was=adm.style.display=='block';adm.style.display='none';document.body.style.overflow='';if(was&&chg&&!window.__em){chg=0;location.reload()}}}; const ord=id=>S.orders.find(o=>o.id==id); function oview(o){M(o.id+' · '+E(o.n),`

${E(o.ph)} · ${E(o.city)}
${E(o.ad)}

${T(['Item','Size','Qty','Price'],o.items.map(i=>[E(nm(i.id)),i.s,i.q,money(i.pr)]))}

Shipping ${money(N(o.ship))} · Discount ${money(N(o.disc))} · Total ${money(tot(o))}${rfd(o)?' · Refunded '+money(rfd(o)):''}

${sel('st','Status',o.st,ST)}
${inp('cour','Courier',o.cour)}${inp('trk','Tracking number',o.trk)}
${inp('nt','Add note','')}

History: ${o.hist.map(h=>h.s+' ('+day(h.d)+')').join(' → ')}

${o.notes.map(n=>`

📝 ${E(n.t)}

`).join('')}
${B('inv1','Invoice',o.id)}${B('inv2','Packing slip',o.id)}${B('inv3','Shipping label',o.id)}${B('ref','Refund',o.id)}
`,f=>{const v=fd();if(v.get('st')!=o.st){if(v.get('st')=='Cancelled')stock(o,1);if(o.st=='Cancelled'&&v.get('st')!='Cancelled')stock(o,-1);o.st=v.get('st');o.hist.push({s:o.st,d:now()});aud(o.id+' → '+o.st)}o.cour=v.get('cour');o.trk=v.get('trk');if(v.get('nt'))o.notes.push({t:v.get('nt'),d:now()});save();mx();R()})} function print(o,t){const L=t==3?`

SHIP TO

${E(o.n)}
${E(o.ph)}
${E(o.ad)}, ${E(o.city)}

Courier: ${E(o.cour)||'—'} · Tracking: ${E(o.trk)||'—'}${o.pay=='COD'?'
COD amount: '+money(tot(o))+'':''}

`:`

${t==1?'INVOICE':'PACKING SLIP'} ${o.id}

${day(o.d)} · ${E(o.n)} · ${E(o.ph)}
${E(o.ad)}, ${E(o.city)}

${t==1?'':''}${o.items.map(i=>`${t==1?'':''}`).join('')}
ItemSizeQtyPrice
${E(nm(i.id))}${i.s}${i.q}'+money(i.pr*i.q)+'
${t==1?`

Shipping ${money(N(o.ship))} · Discount ${money(N(o.disc))}
Total ${money(tot(o))} (${o.pay}, ${o.ps})

`:''}`;$('#pr').innerHTML=`

${E(S.set.brand)}

${L}
`;window.print()} const pfm=p=>`
${inp('n','Name',p.n)}${inp('sku','SKU',p.sku)}
${ta('desc','Description',p.desc)}
${sel('cat','Category',p.cat,['hoodie','sweatshirt','trouser','combo'])}${inp('col','Collection',p.col)}${inp('p','Regular price',p.p,'number')}${inp('sale','Sale price (0 = none)',p.sale,'number')}${inp('cost','Cost price',p.cost,'number')}${sel('st','Status',p.st,['Draft','Published','Archived'])}
${chk('feat','Featured',p.feat)} ${chk('best','Best seller',p.best)}
${inp('tags','Tags',p.tags)}${inp('mat','Material',p.mat)}${inp('fab','Fabric',p.fab)}${inp('care','Care instructions',p.care)}${inp('img','Image URL (optional)',p.img)}${inp('vid','Video URL (optional)',p.vid)}

Variants (size · colour · SKU · price override · stock)

${p.vars.map((v,i)=>`
`).join('')}`; function pcol(p){const f=fd(),g=k=>f.get(k);Object.assign(p,{n:g('n'),sku:g('sku'),desc:g('desc'),cat:g('cat'),col:g('col'),p:N(g('p')),sale:N(g('sale')),cost:N(g('cost')),st:g('st'),feat:f.has('feat')?1:0,best:f.has('best')?1:0,tags:g('tags'),mat:g('mat'),fab:g('fab'),care:g('care'),img:g('img'),vid:g('vid')});const z=f.getAll('sz'),c=f.getAll('vc'),s=f.getAll('vs'),pr=f.getAll('vp'),k=f.getAll('vk'),o=f.getAll('vo');p.vars=z.map((x,i)=>({sz:x,c:c[i],sku:s[i],p:N(pr[i]),stock:N(k[i]),off:+o[i]}));return p} let ep=null; function done(){window.__em=false;document.body.classList.remove('em');const b=document.getElementById('etb');if(b)b.remove();document.querySelectorAll('[data-e]').forEach(e=>e.removeAttribute('contenteditable'));location.hash='#/admin'} function pickImg(el){const i=document.createElement('input');i.type='file';i.accept='image/*';i.onchange=()=>{const f=i.files[0];if(!f)return;const im=new Image();im.onload=()=>{const c=document.createElement('canvas'),cx=c.getContext('2d');let url; if(el.closest('.dk')){c.width=720;c.height=1080;const k=Math.max(720/im.width,1080/im.height),w=im.width*k,h=im.height*k;cx.drawImage(im,(720-w)/2,(1080-h)/2,w,h);url=c.toDataURL('image/jpeg',.72);const ch=[...document.getElementById('strip').children],slot=ch.indexOf(el.closest('.dk'))%12,H=(S.im.hero||[]).slice(0,12);while(H.length<12)H.push('');H[slot]=url;S.im.hero=H;window.HERO=H;ch.forEach((c2,j)=>{if(j%12==slot)c2.querySelector('img').src=url})} else{c.height=160;c.width=Math.round(im.width*160/im.height);cx.drawImage(im,0,0,c.width,160);url=c.toDataURL('image/png');const w=el.id=='lgf'?'logoW':'logo';S.im[w]=url;I[w]=url;document.getElementById('lgo').src=I.logo;document.getElementById('lgf').src=I.logoW} aud('Image replaced');save()};im.src=URL.createObjectURL(f)};i.click()} document.addEventListener('click',e=>{if(!window.__em)return;const t=e.target;if(t.closest('[data-done]')){done();return}if(t.closest('[data-e]')){e.preventDefault();e.stopPropagation();return}const im=t.closest('#lgo,#lgf,.dk img');if(im){e.preventDefault();e.stopPropagation();pickImg(im)}},true); document.addEventListener('input',e=>{if(!window.__em)return;const el=e.target.closest&&e.target.closest('[data-e]');if(el){const k=el.dataset.e;if(el.textContent!==TXDEF[k])S.tx[k]=el.textContent;else delete S.tx[k];save()}}); document.addEventListener('keydown',e=>{if(window.__em&&e.key=='Enter'&&e.target.closest&&e.target.closest('[data-e]')){e.preventDefault();e.target.blur()}}); const A={ sbsave(){const f=new FormData($('#bf2'));S.sb={url:f.get('sb_url').trim().replace(/\/+$/,''),key:f.get('sb_key').trim(),mail:f.get('sb_mail').trim()};try{localStorage.setItem('hnt-sb',JSON.stringify(S.sb))}catch(e){}aud('Online database connected');save();M('Connection saved','

Now click Download updated website ONE time and upload that file to Netlify. After that, every edit goes live instantly.

Next time you open the admin you will sign in with your Supabase admin email and password.

',null)}, async sbtest(){const f=new FormData($('#bf2')),u=f.get('sb_url').trim().replace(/\/+$/,''),k=f.get('sb_key').trim();try{const r=await fetch(u+'/rest/v1/site?select=id&limit=1',{headers:{apikey:k}});toast(r.ok?'✓ Connected - tables found':r.status==404?'Connected, but the setup SQL has not been run':'Failed (error '+r.status+')')}catch(e){toast('Could not reach that URL')}}, sbsql(){const m=((new FormData($('#bf2'))).get('sb_mail')||'YOUR@EMAIL.COM').trim().replace(/'/g,''),w="lower(auth.jwt()->>'email')=lower('"+m+"')",q=`create table if not exists site(id int primary key, data jsonb not null); create table if not exists private(id int primary key, data jsonb not null); create table if not exists inbox(id bigint generated always as identity primary key, data jsonb not null, created_at timestamptz default now()); alter table site enable row level security; alter table private enable row level security; alter table inbox enable row level security; create policy "site read" on site for select using (true); create policy "site admin" on site for all to authenticated using (${w}) with check (${w}); create policy "private admin" on private for all to authenticated using (${w}) with check (${w}); create policy "inbox add" on inbox for insert to anon, authenticated with check (true); create policy "inbox read" on inbox for select to authenticated using (${w}); create policy "inbox delete" on inbox for delete to authenticated using (${w});`;M('Setup SQL','

Copy everything, paste it in Supabase → SQL Editor → Run.

',()=>{const x=document.getElementById('sqlt');x.select();try{navigator.clipboard.writeText(q)}catch(e){document.execCommand('copy')}toast('✓ Copied')},'Copy')}, async recheck(){await probe();if(SRV){ok=false;PW='';login('Live saving found. Sign in with your server password.')}else{toast('Still OFF');R()}}, edit(){window.__em=true;adm.style.display='none';document.body.style.overflow='';document.body.classList.add('em');const b=document.createElement('div');b.id='etb';b.innerHTML='Editing websiteClick dashed text to edit it · click the logo or a hero card to replace the imageHomeShopNewBooking';document.body.appendChild(b);document.querySelectorAll('[data-e]').forEach(e=>{try{e.contentEditable='plaintext-only'}catch(x){e.contentEditable='true'}});if(location.hash!='#/home')location.hash='#/home'}, wsave(){const f=new FormData($('#wf')),sp={mq1:'ESSENTIALS — HNT',mq2:'NEW DROP — WEAR IT SOFT',band:'WEAR IT SOFT ✦ HNT ESSENTIALS'};f.forEach((v,k)=>{const d=k in sp?sp[k]:TXDEF[k];if(v!==d)S.tx[k]=v;else delete S.tx[k]});applyTx(S.tx);aud('Website text saved');save();toast('✓ Text saved')}, wreset(){if(confirm('Remove ALL website text and image edits?')){S.tx={};S.im={};aud('Website edits reset');save();setTimeout(()=>location.reload(),400)}}, async pub(){try{const body=pubBody(),pub=Object.assign({v:Date.now()},body);let src=window.__SRC;if(!src){try{const r=await fetch(location.href.split('#')[0],{cache:'no-store'});src=await r.text();if(!/id="hnt-data"/.test(src))throw 0}catch(x){alert('Choose your current index.html file next so it can be updated.');src=await pick()}}if(!src)return;const blk='