{"id":21,"date":"2026-07-11T12:13:52","date_gmt":"2026-07-11T12:13:52","guid":{"rendered":"https:\/\/gst.sarvalokbhraman.com\/?page_id=21"},"modified":"2026-07-13T11:42:18","modified_gmt":"2026-07-13T11:42:18","slug":"slb_gst","status":"publish","type":"page","link":"https:\/\/gst.sarvalokbhraman.com\/","title":{"rendered":"SLB_GST"},"content":{"rendered":"<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin=\"\">\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Space+Grotesk:wght@500;600;700&#038;family=Inter:wght@400;500;600&#038;family=IBM+Plex+Mono:wght@400;500;600&#038;display=swap\" rel=\"stylesheet\">\n<style>\n:root{<br \/>\n  --ink:#16233F;<br \/>\n  --ink-soft:#233559;<br \/>\n  --ink-softer:#324875;<br \/>\n  --paper:#F3F5F4;<br \/>\n  --surface:#FFFFFF;<br \/>\n  --amber:#E8A33D;<br \/>\n  --amber-dark:#C67F1E;<br \/>\n  --success:#1E7A46;<br \/>\n  --success-bg:#E5F3EA;<br \/>\n  --danger:#B7412F;<br \/>\n  --danger-bg:#FBEAE6;<br \/>\n  --warn:#8A5A0B;<br \/>\n  --warn-bg:#FBF0DC;<br \/>\n  --text:#1C2333;<br \/>\n  --text-mute:#68707F;<br \/>\n  --text-faint:#9AA1AC;<br \/>\n  --border:#E2E5E4;<br \/>\n  --border-soft:#EDEFEE;<br \/>\n  --radius:10px;<br \/>\n  --shadow:0 1px 2px rgba(22,35,63,0.06), 0 4px 14px rgba(22,35,63,0.05);<br \/>\n  font-size:15px;<br \/>\n}<br \/>\n.slb-app-root *{box-sizing:border-box;}<br \/>\n.slb-app-root{<br \/>\n  font-family:'Inter',system-ui,sans-serif;<br \/>\n  background:var(--paper);<br \/>\n  color:var(--text);<br \/>\n  -webkit-font-smoothing:antialiased;<br \/>\n  margin:0;padding:0;<br \/>\n}<br \/>\nh1,h2,h3,h4{font-family:'Space Grotesk',sans-serif;font-weight:600;margin:0;color:var(--ink);}<br \/>\n.mono{font-family:'IBM Plex Mono',monospace;}<br \/>\nbutton{font-family:inherit;cursor:pointer;}<br \/>\ninput,select,textarea{font-family:inherit;font-size:14px;}<br \/>\na{color:inherit;}<br \/>\n#app{display:flex;min-height:100vh;}<br \/>\n\/* Sidebar *\/<br \/>\n#sidebar{<br \/>\n  width:224px;flex-shrink:0;background:var(--ink);color:#DDE3F0;<br \/>\n  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;<br \/>\n}<br \/>\n.brand{<br \/>\n  padding:22px 20px 18px;display:flex;align-items:center;gap:10px;<br \/>\n  border-bottom:1px solid rgba(255,255,255,0.08);<br \/>\n}<br \/>\n.brand-mark{<br \/>\n  width:34px;height:34px;border-radius:8px;background:var(--amber);<br \/>\n  display:flex;align-items:center;justify-content:center;flex-shrink:0;<br \/>\n}<br \/>\n.brand-mark svg{width:19px;height:19px;}<br \/>\n.brand-name{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:17px;color:#fff;line-height:1.1;}<br \/>\n.brand-sub{font-size:10.5px;color:#9FB0D4;letter-spacing:.04em;text-transform:uppercase;margin-top:2px;}<br \/>\nnav.navlist{padding:14px 12px;display:flex;flex-direction:column;gap:2px;flex:1;}<br \/>\n.navitem{<br \/>\n  display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;<br \/>\n  color:#B9C4DE;font-size:13.5px;font-weight:500;background:transparent;border:none;text-align:left;width:100%;<br \/>\n  transition:background .12s, color .12s;<br \/>\n}<br \/>\n.navitem svg{width:17px;height:17px;flex-shrink:0;opacity:.85;}<br \/>\n.navitem:hover{background:rgba(255,255,255,0.06);color:#fff;}<br \/>\n.navitem.active{background:rgba(232,163,61,0.16);color:#fff;}<br \/>\n.navitem.active svg{opacity:1;color:var(--amber);}<br \/>\n.nav-divider{height:1px;background:rgba(255,255,255,0.08);margin:10px 6px;}<br \/>\n.sidebar-foot{padding:14px 20px 18px;border-top:1px solid rgba(255,255,255,0.08);font-size:11.5px;color:#7C8AAE;}<br \/>\n\/* Main *\/<br \/>\n#main{flex:1;min-width:0;display:flex;flex-direction:column;}<br \/>\n.topbar{<br \/>\n  padding:16px 28px;display:flex;align-items:center;justify-content:space-between;<br \/>\n  border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:5;<br \/>\n}<br \/>\n.topbar h1{font-size:19px;}<br \/>\n.topbar .sub{font-size:12.5px;color:var(--text-mute);margin-top:2px;font-weight:400;}<br \/>\n.content{padding:24px 28px 60px;flex:1;}<br \/>\n.btn{<br \/>\n  display:inline-flex;align-items:center;gap:6px;padding:9px 15px;border-radius:8px;<br \/>\n  border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13.5px;font-weight:500;<br \/>\n  transition:border-color .12s, background .12s;<br \/>\n}<br \/>\n.btn svg{width:15px;height:15px;}<br \/>\n.btn:hover{border-color:#C7CCD2;background:#FAFAFA;}<br \/>\n.btn-primary{background:var(--ink);color:#fff;border-color:var(--ink);}<br \/>\n.btn-primary:hover{background:var(--ink-soft);border-color:var(--ink-soft);}<br \/>\n.btn-amber{background:var(--amber);color:#3B2606;border-color:var(--amber);}<br \/>\n.btn-amber:hover{background:var(--amber-dark);border-color:var(--amber-dark);}<br \/>\n.btn-danger-ghost{color:var(--danger);border-color:var(--border);}<br \/>\n.btn-danger-ghost:hover{background:var(--danger-bg);border-color:var(--danger);}<br \/>\n.btn-sm{padding:6px 10px;font-size:12.5px;}<br \/>\n.btn-icon{padding:7px;}<br \/>\n\/* Cards *\/<br \/>\n.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;}<br \/>\n.grid{display:grid;gap:14px;}<br \/>\n.grid-4{grid-template-columns:repeat(4,1fr);}<br \/>\n.grid-3{grid-template-columns:repeat(3,1fr);}<br \/>\n.grid-2{grid-template-columns:repeat(2,1fr);}<br \/>\n@media (max-width:1000px){.grid-4{grid-template-columns:repeat(2,1fr);}}<br \/>\n.stat-card{padding:16px 18px;}<br \/>\n.stat-label{font-size:12px;color:var(--text-mute);font-weight:500;display:flex;align-items:center;gap:6px;}<br \/>\n.stat-value{font-family:'IBM Plex Mono',monospace;font-size:24px;font-weight:600;color:var(--ink);margin-top:8px;}<br \/>\n.stat-note{font-size:11.5px;color:var(--text-faint);margin-top:4px;}<br \/>\ntable{width:100%;border-collapse:collapse;font-size:13.5px;}<br \/>\nth{<br \/>\n  text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);<br \/>\n  font-weight:600;padding:10px 12px;border-bottom:1px solid var(--border);background:#FAFBFA;<br \/>\n}<br \/>\ntd{padding:11px 12px;border-bottom:1px solid var(--border-soft);vertical-align:middle;}<br \/>\ntr:last-child td{border-bottom:none;}<br \/>\ntbody tr:hover{background:#FAFBFA;}<br \/>\n.table-wrap{border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface);}<br \/>\n.right{text-align:right;}<br \/>\n.center{text-align:center;}<br \/>\n.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:600;}<br \/>\n.badge-paid{background:var(--success-bg);color:var(--success);}<br \/>\n.badge-unpaid{background:var(--danger-bg);color:var(--danger);}<br \/>\n.badge-partial{background:var(--warn-bg);color:var(--warn);}<br \/>\n.badge-bos{background:#EFEAF7;color:#5A3E9E;}<br \/>\n.badge-tax{background:#E7EEF9;color:#26497A;}<br \/>\n.empty-state{padding:56px 20px;text-align:center;color:var(--text-mute);}<br \/>\n.empty-state svg{width:38px;height:38px;color:var(--text-faint);margin-bottom:10px;}<br \/>\n.empty-state h3{color:var(--text);font-size:15px;margin-bottom:4px;}<br \/>\n.empty-state p{font-size:13px;margin:0 0 16px;}<br \/>\n\/* Drawer \/ forms *\/<br \/>\n#overlay{<br \/>\n  position:fixed;inset:0;background:rgba(22,26,38,0.32);display:none;z-index:40;<br \/>\n}<br \/>\n#drawer{<br \/>\n  position:fixed;top:0;right:0;height:100vh;width:560px;max-width:94vw;background:var(--surface);<br \/>\n  box-shadow:-8px 0 32px rgba(0,0,0,0.12);z-index:41;display:flex;flex-direction:column;<br \/>\n  transform:translateX(100%);transition:transform .2s ease;<br \/>\n}<br \/>\n#drawer.open, #overlay.open{display:block;}<br \/>\n#drawer.open{transform:translateX(0);}<br \/>\n.drawer-head{padding:18px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}<br \/>\n.drawer-body{padding:20px 24px;overflow-y:auto;flex:1;}<br \/>\n.drawer-foot{padding:16px 24px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px;}<br \/>\n.field{margin-bottom:14px;}<br \/>\n.field label{display:block;font-size:12.5px;font-weight:500;color:var(--text-mute);margin-bottom:5px;}<br \/>\n.field input, .field select, .field textarea{<br \/>\n  width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:8px;background:#fff;color:var(--text);<br \/>\n}<br \/>\n.field input:focus, .field select:focus, .field textarea:focus{outline:none;border-color:var(--ink-softer);box-shadow:0 0 0 3px rgba(50,72,117,0.1);}<br \/>\n.field textarea{resize:vertical;min-height:60px;}<br \/>\n.field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;}<br \/>\n.field-hint{font-size:11.5px;color:var(--text-faint);margin-top:4px;}<br \/>\n\/* Invoice type toggle *\/<br \/>\n.type-toggle{display:flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;}<br \/>\n.type-toggle button{<br \/>\n  flex:1;padding:9px 10px;font-size:12.5px;font-weight:600;background:#fff;border:none;color:var(--text-mute);<br \/>\n  border-right:1px solid var(--border);transition:background .12s,color .12s;<br \/>\n}<br \/>\n.type-toggle button:last-child{border-right:none;}<br \/>\n.type-toggle button.active{background:var(--ink);color:#fff;}<br \/>\n.type-toggle button.active.bos-active{background:#5A3E9E;color:#fff;}<br \/>\n\/* Invoice line item builder *\/<br \/>\n.line-table th, .line-table td{padding:8px;font-size:13px;}<br \/>\n.line-table input, .line-table select{padding:6px 7px;font-size:12.5px;}<br \/>\n.line-actions{display:flex;justify-content:flex-end;}<br \/>\n.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:18px;}<br \/>\n.tab{padding:9px 14px;font-size:13px;font-weight:500;color:var(--text-mute);border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none;}<br \/>\n.tab.active{color:var(--ink);border-bottom-color:var(--amber);}<br \/>\n.toast{<br \/>\n  position:fixed;bottom:22px;right:22px;background:var(--ink);color:#fff;padding:12px 18px;border-radius:9px;<br \/>\n  font-size:13px;box-shadow:var(--shadow);z-index:80;display:flex;align-items:center;gap:8px;<br \/>\n  opacity:0;transform:translateY(8px);transition:opacity .18s, transform .18s;pointer-events:none;<br \/>\n}<br \/>\n.toast.show{opacity:1;transform:translateY(0);}<br \/>\n\/* Invoice preview \/ print styling \u2014 boarding pass motif *\/<br \/>\n.invoice-preview{background:#fff;border:1px solid var(--border);border-radius:12px;overflow:hidden;max-width:820px;}<br \/>\n.ip-head{background:var(--ink);color:#fff;padding:22px 26px;display:flex;justify-content:space-between;align-items:flex-start;}<br \/>\n.ip-brand{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:18px;}<br \/>\n.ip-brand-sub{font-size:11.5px;color:#B9C4DE;margin-top:2px;}<br \/>\n.ip-invno{font-family:'IBM Plex Mono',monospace;text-align:right;font-size:13px;}<br \/>\n.ip-invno .big{font-size:16px;font-weight:600;color:var(--amber);}<br \/>\n.ip-doctype{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:#B9C4DE;margin-bottom:2px;}<br \/>\n.ip-route{<br \/>\n  display:grid;grid-template-columns:1fr 1fr;align-items:center;padding:20px 26px;gap:14px;<br \/>\n}<br \/>\n.ip-party .lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-mute);font-weight:600;}<br \/>\n.ip-party .name{font-size:15px;font-weight:600;color:var(--ink);margin-top:4px;}<br \/>\n.ip-party .meta{font-size:12px;color:var(--text-mute);margin-top:3px;line-height:1.5;}<br \/>\n.ip-plane{color:var(--amber);}<br \/>\n.ip-plane svg{width:22px;height:22px;}<br \/>\n.ip-plane img{width:32px;height:32px;object-fit:contain;display:block;}<br \/>\n.perforation{<br \/>\n  position:relative;height:0;border-top:1px dashed var(--border);margin:0 26px;<br \/>\n}<br \/>\n.perforation::before,.perforation::after{<br \/>\n  content:\"\";position:absolute;top:-9px;width:18px;height:18px;border-radius:50%;background:var(--paper);border:1px solid var(--border);<br \/>\n}<br \/>\n.perforation::before{left:-35px;}<br \/>\n.perforation::after{right:-35px;}<br \/>\n.ip-body{padding:22px 26px;}<br \/>\n.ip-totals{width:280px;margin-left:auto;margin-top:10px;font-size:13px;}<br \/>\n.ip-totals .row{display:flex;justify-content:space-between;padding:5px 0;color:var(--text-mute);}<br \/>\n.ip-totals .row.grand{color:var(--ink);font-weight:700;font-size:16px;border-top:1px solid var(--border);margin-top:6px;padding-top:10px;}<br \/>\n.ip-bos-note{margin:0 26px;padding:10px 12px;background:#F4F1FB;border:1px solid #E1D8F5;border-radius:8px;font-size:12px;color:#5A3E9E;font-weight:500;}<br \/>\n.ip-foot{padding:18px 26px 24px;border-top:1px solid var(--border-soft);display:grid;grid-template-columns:1fr 1fr;gap:20px;font-size:12px;color:var(--text-mute);}<br \/>\n.ip-foot h4{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);margin-bottom:6px;}<br \/>\n.ip-disclaimer{padding:10px 26px 0;text-align:right;border-top:1px solid var(--border-soft);margin-top:6px;}<br \/>\n.ip-thankyou{padding:14px 26px 0;text-align:center;font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:13.5px;color:var(--ink);letter-spacing:.02em;}<br \/>\n.ip-signature{padding:2px 26px 22px;display:flex;justify-content:flex-end;}<br \/>\n@page{<br \/>\n  size: A4;<br \/>\n  margin: 8mm;<br \/>\n}<br \/>\n@media print{<br \/>\n  .slb-app-root{margin:0 !important;padding:0 !important;}<br \/>\n  #app, #authScreen, #overlay, #drawer, #toast{display:none !important;}<br \/>\n  #printArea{display:block !important;position:static !important;width:100%;padding:0;margin:0;}<br \/>\n  .invoice-preview{border:none;border-radius:0;max-width:100%;page-break-inside:avoid;}<br \/>\n  .ip-head{padding:14px 18px;}<br \/>\n  .ip-route{padding:14px 18px;gap:10px;}<br \/>\n  .ip-body{padding:14px 18px;}<br \/>\n  .ip-foot{padding:12px 18px 14px;}<br \/>\n  .ip-disclaimer{padding:6px 18px 0;margin-top:4px;}<br \/>\n  .ip-thankyou{padding:10px 18px 0;}<br \/>\n  .ip-signature{padding:2px 18px 12px;}<br \/>\n  .perforation{margin:0 18px;}<br \/>\n  .ip-bos-note{margin:0 18px;}<br \/>\n  table{font-size:12px;}<br \/>\n  th,td{padding:7px 8px;}<br \/>\n  .no-print{display:none !important;}<br \/>\n}<br \/>\n.search-bar{display:flex;gap:10px;margin-bottom:16px;}<br \/>\n.search-bar input{flex:1;padding:9px 12px;border:1px solid var(--border);border-radius:8px;}<br \/>\n.filter-pills{display:flex;gap:6px;}<br \/>\n.pill{padding:6px 12px;border-radius:20px;border:1px solid var(--border);font-size:12.5px;color:var(--text-mute);background:#fff;}<br \/>\n.pill.active{background:var(--ink);color:#fff;border-color:var(--ink);}<br \/>\n.section-title{font-size:13px;font-weight:600;color:var(--ink);text-transform:uppercase;letter-spacing:.03em;margin:22px 0 10px;}<br \/>\n.section-title:first-child{margin-top:0;}<br \/>\n.help-text{font-size:12px;color:var(--text-mute);margin-top:2px;}<br \/>\n\/* Login \/ setup screen *\/<br \/>\n#authScreen{<br \/>\n  min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--ink);<br \/>\n  background-image:radial-gradient(circle at 20% 20%, rgba(232,163,61,0.10), transparent 40%),radial-gradient(circle at 80% 80%, rgba(50,72,117,0.35), transparent 45%);<br \/>\n  padding:20px;<br \/>\n}<br \/>\n.auth-card{<br \/>\n  width:100%;max-width:380px;background:var(--surface);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,0.35);<br \/>\n  padding:32px 30px 28px;<br \/>\n}<br \/>\n.auth-brand{display:flex;align-items:center;gap:10px;margin-bottom:22px;}<br \/>\n.auth-brand img{width:38px;height:38px;border-radius:9px;background:#fff;object-fit:contain;}<br \/>\n.auth-brand .t1{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:16px;color:var(--ink);}<br \/>\n.auth-brand .t2{font-size:11.5px;color:var(--text-mute);}<br \/>\n.auth-title{font-size:17px;margin-bottom:4px;}<br \/>\n.auth-sub{font-size:12.5px;color:var(--text-mute);margin-bottom:20px;}<br \/>\n.auth-error{background:var(--danger-bg);color:var(--danger);font-size:12.5px;padding:9px 12px;border-radius:8px;margin-bottom:14px;display:none;}<br \/>\n.auth-error.show{display:block;}<br \/>\n<\/style>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin=\"\">\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Space+Grotesk:wght@500;600;700&#038;family=Inter:wght@400;500;600&#038;family=IBM+Plex+Mono:wght@400;500;600&#038;display=swap\" rel=\"stylesheet\">\n<style>\n:root{<br \/>\n  --ink:#16233F;<br \/>\n  --ink-soft:#233559;<br \/>\n  --ink-softer:#324875;<br \/>\n  --paper:#F3F5F4;<br \/>\n  --surface:#FFFFFF;<br \/>\n  --amber:#E8A33D;<br \/>\n  --amber-dark:#C67F1E;<br \/>\n  --success:#1E7A46;<br \/>\n  --success-bg:#E5F3EA;<br \/>\n  --danger:#B7412F;<br \/>\n  --danger-bg:#FBEAE6;<br \/>\n  --warn:#8A5A0B;<br \/>\n  --warn-bg:#FBF0DC;<br \/>\n  --text:#1C2333;<br \/>\n  --text-mute:#68707F;<br \/>\n  --text-faint:#9AA1AC;<br \/>\n  --border:#E2E5E4;<br \/>\n  --border-soft:#EDEFEE;<br \/>\n  --radius:10px;<br \/>\n  --shadow:0 1px 2px rgba(22,35,63,0.06), 0 4px 14px rgba(22,35,63,0.05);<br \/>\n  font-size:15px;<br \/>\n}<br \/>\n.slb-app-root *{box-sizing:border-box;}<br \/>\n.slb-app-root{<br \/>\n  font-family:'Inter',system-ui,sans-serif;<br \/>\n  background:var(--paper);<br \/>\n  color:var(--text);<br \/>\n  -webkit-font-smoothing:antialiased;<br \/>\n  margin:0;padding:0;<br \/>\n}<br \/>\nh1,h2,h3,h4{font-family:'Space Grotesk',sans-serif;font-weight:600;margin:0;color:var(--ink);}<br \/>\n.mono{font-family:'IBM Plex Mono',monospace;}<br \/>\nbutton{font-family:inherit;cursor:pointer;}<br \/>\ninput,select,textarea{font-family:inherit;font-size:14px;}<br \/>\na{color:inherit;}<\/p>\n<p>#app{display:flex;min-height:100vh;}<\/p>\n<p>\/* Sidebar *\/<br \/>\n#sidebar{<br \/>\n  width:224px;flex-shrink:0;background:var(--ink);color:#DDE3F0;<br \/>\n  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;<br \/>\n}<br \/>\n.brand{<br \/>\n  padding:22px 20px 18px;display:flex;align-items:center;gap:10px;<br \/>\n  border-bottom:1px solid rgba(255,255,255,0.08);<br \/>\n}<br \/>\n.brand-mark{<br \/>\n  width:34px;height:34px;border-radius:8px;background:var(--amber);<br \/>\n  display:flex;align-items:center;justify-content:center;flex-shrink:0;<br \/>\n}<br \/>\n.brand-mark svg{width:19px;height:19px;}<br \/>\n.brand-name{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:17px;color:#fff;line-height:1.1;}<br \/>\n.brand-sub{font-size:10.5px;color:#9FB0D4;letter-spacing:.04em;text-transform:uppercase;margin-top:2px;}<br \/>\nnav.navlist{padding:14px 12px;display:flex;flex-direction:column;gap:2px;flex:1;}<br \/>\n.navitem{<br \/>\n  display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;<br \/>\n  color:#B9C4DE;font-size:13.5px;font-weight:500;background:transparent;border:none;text-align:left;width:100%;<br \/>\n  transition:background .12s, color .12s;<br \/>\n}<br \/>\n.navitem svg{width:17px;height:17px;flex-shrink:0;opacity:.85;}<br \/>\n.navitem:hover{background:rgba(255,255,255,0.06);color:#fff;}<br \/>\n.navitem.active{background:rgba(232,163,61,0.16);color:#fff;}<br \/>\n.navitem.active svg{opacity:1;color:var(--amber);}<br \/>\n.nav-divider{height:1px;background:rgba(255,255,255,0.08);margin:10px 6px;}<br \/>\n.sidebar-foot{padding:14px 20px 18px;border-top:1px solid rgba(255,255,255,0.08);font-size:11.5px;color:#7C8AAE;}<\/p>\n<p>\/* Main *\/<br \/>\n#main{flex:1;min-width:0;display:flex;flex-direction:column;}<br \/>\n.topbar{<br \/>\n  padding:16px 28px;display:flex;align-items:center;justify-content:space-between;<br \/>\n  border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:5;<br \/>\n}<br \/>\n.topbar h1{font-size:19px;}<br \/>\n.topbar .sub{font-size:12.5px;color:var(--text-mute);margin-top:2px;font-weight:400;}<br \/>\n.content{padding:24px 28px 60px;flex:1;}<\/p>\n<p>.btn{<br \/>\n  display:inline-flex;align-items:center;gap:6px;padding:9px 15px;border-radius:8px;<br \/>\n  border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13.5px;font-weight:500;<br \/>\n  transition:border-color .12s, background .12s;<br \/>\n}<br \/>\n.btn svg{width:15px;height:15px;}<br \/>\n.btn:hover{border-color:#C7CCD2;background:#FAFAFA;}<br \/>\n.btn-primary{background:var(--ink);color:#fff;border-color:var(--ink);}<br \/>\n.btn-primary:hover{background:var(--ink-soft);border-color:var(--ink-soft);}<br \/>\n.btn-amber{background:var(--amber);color:#3B2606;border-color:var(--amber);}<br \/>\n.btn-amber:hover{background:var(--amber-dark);border-color:var(--amber-dark);}<br \/>\n.btn-danger-ghost{color:var(--danger);border-color:var(--border);}<br \/>\n.btn-danger-ghost:hover{background:var(--danger-bg);border-color:var(--danger);}<br \/>\n.btn-sm{padding:6px 10px;font-size:12.5px;}<br \/>\n.btn-icon{padding:7px;}<\/p>\n<p>\/* Cards *\/<br \/>\n.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;}<br \/>\n.grid{display:grid;gap:14px;}<br \/>\n.grid-4{grid-template-columns:repeat(4,1fr);}<br \/>\n.grid-3{grid-template-columns:repeat(3,1fr);}<br \/>\n.grid-2{grid-template-columns:repeat(2,1fr);}<br \/>\n@media (max-width:1000px){.grid-4{grid-template-columns:repeat(2,1fr);}}<\/p>\n<p>.stat-card{padding:16px 18px;}<br \/>\n.stat-label{font-size:12px;color:var(--text-mute);font-weight:500;display:flex;align-items:center;gap:6px;}<br \/>\n.stat-value{font-family:'IBM Plex Mono',monospace;font-size:24px;font-weight:600;color:var(--ink);margin-top:8px;}<br \/>\n.stat-note{font-size:11.5px;color:var(--text-faint);margin-top:4px;}<\/p>\n<p>table{width:100%;border-collapse:collapse;font-size:13.5px;}<br \/>\nth{<br \/>\n  text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);<br \/>\n  font-weight:600;padding:10px 12px;border-bottom:1px solid var(--border);background:#FAFBFA;<br \/>\n}<br \/>\ntd{padding:11px 12px;border-bottom:1px solid var(--border-soft);vertical-align:middle;}<br \/>\ntr:last-child td{border-bottom:none;}<br \/>\ntbody tr:hover{background:#FAFBFA;}<br \/>\n.table-wrap{border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface);}<br \/>\n.right{text-align:right;}<br \/>\n.center{text-align:center;}<\/p>\n<p>.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:600;}<br \/>\n.badge-paid{background:var(--success-bg);color:var(--success);}<br \/>\n.badge-unpaid{background:var(--danger-bg);color:var(--danger);}<br \/>\n.badge-partial{background:var(--warn-bg);color:var(--warn);}<br \/>\n.badge-bos{background:#EFEAF7;color:#5A3E9E;}<br \/>\n.badge-tax{background:#E7EEF9;color:#26497A;}<\/p>\n<p>.empty-state{padding:56px 20px;text-align:center;color:var(--text-mute);}<br \/>\n.empty-state svg{width:38px;height:38px;color:var(--text-faint);margin-bottom:10px;}<br \/>\n.empty-state h3{color:var(--text);font-size:15px;margin-bottom:4px;}<br \/>\n.empty-state p{font-size:13px;margin:0 0 16px;}<\/p>\n<p>\/* Drawer \/ forms *\/<br \/>\n#overlay{<br \/>\n  position:fixed;inset:0;background:rgba(22,26,38,0.32);display:none;z-index:40;<br \/>\n}<br \/>\n#drawer{<br \/>\n  position:fixed;top:0;right:0;height:100vh;width:560px;max-width:94vw;background:var(--surface);<br \/>\n  box-shadow:-8px 0 32px rgba(0,0,0,0.12);z-index:41;display:flex;flex-direction:column;<br \/>\n  transform:translateX(100%);transition:transform .2s ease;<br \/>\n}<br \/>\n#drawer.open, #overlay.open{display:block;}<br \/>\n#drawer.open{transform:translateX(0);}<br \/>\n.drawer-head{padding:18px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}<br \/>\n.drawer-body{padding:20px 24px;overflow-y:auto;flex:1;}<br \/>\n.drawer-foot{padding:16px 24px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px;}<\/p>\n<p>.field{margin-bottom:14px;}<br \/>\n.field label{display:block;font-size:12.5px;font-weight:500;color:var(--text-mute);margin-bottom:5px;}<br \/>\n.field input, .field select, .field textarea{<br \/>\n  width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:8px;background:#fff;color:var(--text);<br \/>\n}<br \/>\n.field input:focus, .field select:focus, .field textarea:focus{outline:none;border-color:var(--ink-softer);box-shadow:0 0 0 3px rgba(50,72,117,0.1);}<br \/>\n.field textarea{resize:vertical;min-height:60px;}<br \/>\n.field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;}<br \/>\n.field-hint{font-size:11.5px;color:var(--text-faint);margin-top:4px;}<\/p>\n<p>\/* Invoice type toggle *\/<br \/>\n.type-toggle{display:flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;}<br \/>\n.type-toggle button{<br \/>\n  flex:1;padding:9px 10px;font-size:12.5px;font-weight:600;background:#fff;border:none;color:var(--text-mute);<br \/>\n  border-right:1px solid var(--border);transition:background .12s,color .12s;<br \/>\n}<br \/>\n.type-toggle button:last-child{border-right:none;}<br \/>\n.type-toggle button.active{background:var(--ink);color:#fff;}<br \/>\n.type-toggle button.active.bos-active{background:#5A3E9E;color:#fff;}<\/p>\n<p>\/* Invoice line item builder *\/<br \/>\n.line-table th, .line-table td{padding:8px;font-size:13px;}<br \/>\n.line-table input, .line-table select{padding:6px 7px;font-size:12.5px;}<br \/>\n.line-actions{display:flex;justify-content:flex-end;}<\/p>\n<p>.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:18px;}<br \/>\n.tab{padding:9px 14px;font-size:13px;font-weight:500;color:var(--text-mute);border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none;}<br \/>\n.tab.active{color:var(--ink);border-bottom-color:var(--amber);}<\/p>\n<p>.toast{<br \/>\n  position:fixed;bottom:22px;right:22px;background:var(--ink);color:#fff;padding:12px 18px;border-radius:9px;<br \/>\n  font-size:13px;box-shadow:var(--shadow);z-index:80;display:flex;align-items:center;gap:8px;<br \/>\n  opacity:0;transform:translateY(8px);transition:opacity .18s, transform .18s;pointer-events:none;<br \/>\n}<br \/>\n.toast.show{opacity:1;transform:translateY(0);}<\/p>\n<p>\/* Invoice preview \/ print styling \u2014 boarding pass motif *\/<br \/>\n.invoice-preview{background:#fff;border:1px solid var(--border);border-radius:12px;overflow:hidden;max-width:820px;}<br \/>\n.ip-head{background:var(--ink);color:#fff;padding:22px 26px;display:flex;justify-content:space-between;align-items:flex-start;}<br \/>\n.ip-brand{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:18px;}<br \/>\n.ip-brand-sub{font-size:11.5px;color:#B9C4DE;margin-top:2px;}<br \/>\n.ip-invno{font-family:'IBM Plex Mono',monospace;text-align:right;font-size:13px;}<br \/>\n.ip-invno .big{font-size:16px;font-weight:600;color:var(--amber);}<br \/>\n.ip-doctype{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:#B9C4DE;margin-bottom:2px;}<br \/>\n.ip-route{<br \/>\n  display:grid;grid-template-columns:1fr 1fr;align-items:center;padding:20px 26px;gap:14px;<br \/>\n}<br \/>\n.ip-party .lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-mute);font-weight:600;}<br \/>\n.ip-party .name{font-size:15px;font-weight:600;color:var(--ink);margin-top:4px;}<br \/>\n.ip-party .meta{font-size:12px;color:var(--text-mute);margin-top:3px;line-height:1.5;}<br \/>\n.ip-plane{color:var(--amber);}<br \/>\n.ip-plane svg{width:22px;height:22px;}<br \/>\n.ip-plane img{width:32px;height:32px;object-fit:contain;display:block;}<br \/>\n.perforation{<br \/>\n  position:relative;height:0;border-top:1px dashed var(--border);margin:0 26px;<br \/>\n}<br \/>\n.perforation::before,.perforation::after{<br \/>\n  content:\"\";position:absolute;top:-9px;width:18px;height:18px;border-radius:50%;background:var(--paper);border:1px solid var(--border);<br \/>\n}<br \/>\n.perforation::before{left:-35px;}<br \/>\n.perforation::after{right:-35px;}<br \/>\n.ip-body{padding:22px 26px;}<br \/>\n.ip-totals{width:280px;margin-left:auto;margin-top:10px;font-size:13px;}<br \/>\n.ip-totals .row{display:flex;justify-content:space-between;padding:5px 0;color:var(--text-mute);}<br \/>\n.ip-totals .row.grand{color:var(--ink);font-weight:700;font-size:16px;border-top:1px solid var(--border);margin-top:6px;padding-top:10px;}<br \/>\n.ip-bos-note{margin:0 26px;padding:10px 12px;background:#F4F1FB;border:1px solid #E1D8F5;border-radius:8px;font-size:12px;color:#5A3E9E;font-weight:500;}<br \/>\n.ip-foot{padding:18px 26px 24px;border-top:1px solid var(--border-soft);display:grid;grid-template-columns:1fr 1fr;gap:20px;font-size:12px;color:var(--text-mute);}<br \/>\n.ip-foot h4{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);margin-bottom:6px;}<br \/>\n.ip-disclaimer{padding:10px 26px 0;text-align:right;border-top:1px solid var(--border-soft);margin-top:6px;}<br \/>\n.ip-thankyou{padding:14px 26px 0;text-align:center;font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:13.5px;color:var(--ink);letter-spacing:.02em;}<br \/>\n.ip-signature{padding:2px 26px 22px;display:flex;justify-content:flex-end;}<\/p>\n<p>@page{<br \/>\n  size: A4;<br \/>\n  margin: 8mm;<br \/>\n}<br \/>\n@media print{<br \/>\n  .slb-app-root{margin:0 !important;padding:0 !important;}<br \/>\n  #app, #authScreen, #overlay, #drawer, #toast{display:none !important;}<br \/>\n  #printArea{display:block !important;position:static !important;width:100%;padding:0;margin:0;}<br \/>\n  .invoice-preview{border:none;border-radius:0;max-width:100%;page-break-inside:avoid;}<br \/>\n  .ip-head{padding:14px 18px;}<br \/>\n  .ip-route{padding:14px 18px;gap:10px;}<br \/>\n  .ip-body{padding:14px 18px;}<br \/>\n  .ip-foot{padding:12px 18px 14px;}<br \/>\n  .ip-disclaimer{padding:6px 18px 0;margin-top:4px;}<br \/>\n  .ip-thankyou{padding:10px 18px 0;}<br \/>\n  .ip-signature{padding:2px 18px 12px;}<br \/>\n  .perforation{margin:0 18px;}<br \/>\n  .ip-bos-note{margin:0 18px;}<br \/>\n  table{font-size:12px;}<br \/>\n  th,td{padding:7px 8px;}<br \/>\n  .no-print{display:none !important;}<br \/>\n}<\/p>\n<p>.search-bar{display:flex;gap:10px;margin-bottom:16px;}<br \/>\n.search-bar input{flex:1;padding:9px 12px;border:1px solid var(--border);border-radius:8px;}<br \/>\n.filter-pills{display:flex;gap:6px;}<br \/>\n.pill{padding:6px 12px;border-radius:20px;border:1px solid var(--border);font-size:12.5px;color:var(--text-mute);background:#fff;}<br \/>\n.pill.active{background:var(--ink);color:#fff;border-color:var(--ink);}<\/p>\n<p>.section-title{font-size:13px;font-weight:600;color:var(--ink);text-transform:uppercase;letter-spacing:.03em;margin:22px 0 10px;}<br \/>\n.section-title:first-child{margin-top:0;}<br \/>\n.help-text{font-size:12px;color:var(--text-mute);margin-top:2px;}<\/p>\n<p>\/* Login \/ setup screen *\/<br \/>\n#authScreen{<br \/>\n  min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--ink);<br \/>\n  background-image:radial-gradient(circle at 20% 20%, rgba(232,163,61,0.10), transparent 40%),radial-gradient(circle at 80% 80%, rgba(50,72,117,0.35), transparent 45%);<br \/>\n  padding:20px;<br \/>\n}<br \/>\n.auth-card{<br \/>\n  width:100%;max-width:380px;background:var(--surface);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,0.35);<br \/>\n  padding:32px 30px 28px;<br \/>\n}<br \/>\n.auth-brand{display:flex;align-items:center;gap:10px;margin-bottom:22px;}<br \/>\n.auth-brand img{width:38px;height:38px;border-radius:9px;background:#fff;object-fit:contain;}<br \/>\n.auth-brand .t1{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:16px;color:var(--ink);}<br \/>\n.auth-brand .t2{font-size:11.5px;color:var(--text-mute);}<br \/>\n.auth-title{font-size:17px;margin-bottom:4px;}<br \/>\n.auth-sub{font-size:12.5px;color:var(--text-mute);margin-bottom:20px;}<br \/>\n.auth-error{background:var(--danger-bg);color:var(--danger);font-size:12.5px;padding:9px 12px;border-radius:8px;margin-bottom:14px;display:none;}<br \/>\n.auth-error.show{display:block;}<br \/>\n<\/style>\n<div class=\"slb-app-root\">\n<div id=\"authScreen\"><\/div>\n<div id=\"app\" style=\"display:none\"><\/div>\n<div id=\"overlay\" onclick=\"closeDrawer()\"><\/div>\n<div id=\"drawer\"><\/div>\n<div id=\"printArea\" style=\"display:none\"><\/div>\n<div id=\"toast\" class=\"toast\"><\/div>\n<\/div>\n<p><script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.2\/firebase-app-compat.js\"><\/script><br \/>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.2\/firebase-firestore-compat.js\"><\/script><br \/>\n<script><br \/>\n  const FIREBASE_CONFIG = {<br \/>\n    apiKey: \"AIzaSyDYCLnE54y06iMNCu8GD_3VWvULc-uW6rQ\",<br \/>\n    authDomain: \"slb-books.firebaseapp.com\",<br \/>\n    projectId: \"slb-books\",<br \/>\n    storageBucket: \"slb-books.firebasestorage.app\",<br \/>\n    messagingSenderId: \"871472305647\",<br \/>\n    appId: \"1:871472305647:web:af7b289e4067dddfdc921a\"<br \/>\n  };<br \/>\n  firebase.initializeApp(FIREBASE_CONFIG);<br \/>\n  const FIRESTORE_DB = firebase.firestore();<br \/>\n  const FIRESTORE_COLLECTION = 'slbbooks_data';<br \/>\n<\/script><\/p>\n<p><script><br \/>\n\/* ---------- Icons ---------- *\/<br \/>\nconst ICONS = {<br \/>\n  dashboard:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"9\" rx=\"1.5\"\/><rect x=\"14\" y=\"3\" width=\"7\" height=\"5\" rx=\"1.5\"\/><rect x=\"14\" y=\"12\" width=\"7\" height=\"9\" rx=\"1.5\"\/><rect x=\"3\" y=\"16\" width=\"7\" height=\"5\" rx=\"1.5\"\/><\/svg>',<br \/>\n  invoice:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 3h9l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z\"\/><path d=\"M14 3v5h5\" \/><path d=\"M8 13h8M8 17h5\"\/><\/svg>',<br \/>\n  users:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"9\" cy=\"8\" r=\"3.2\"\/><path d=\"M2.5 20c0-3.5 3-6 6.5-6s6.5 2.5 6.5 6\"\/><circle cx=\"17\" cy=\"8.5\" r=\"2.6\"\/><path d=\"M15.5 14.3c2.8.5 4.5 2.6 4.5 5.7\"\/><\/svg>',<br \/>\n  package:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M21 8l-9-5-9 5 9 5 9-5z\"\/><path d=\"M3 8v8l9 5 9-5V8\"\/><path d=\"M12 13v8\"\/><\/svg>',<br \/>\n  chart:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M4 20V10M12 20V4M20 20v-7\"\/><\/svg>',<br \/>\n  settings:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"12\" cy=\"12\" r=\"3.2\"\/><path d=\"M19.4 13.5a7.6 7.6 0 0 0 0-3l2-1.5-2-3.4-2.3.8a7.7 7.7 0 0 0-2.6-1.5L14 2h-4l-.5 2.4a7.7 7.7 0 0 0-2.6 1.5l-2.3-.8-2 3.4 2 1.5a7.6 7.6 0 0 0 0 3l-2 1.5 2 3.4 2.3-.8a7.7 7.7 0 0 0 2.6 1.5L10 22h4l.5-2.4a7.7 7.7 0 0 0 2.6-1.5l2.3.8 2-3.4-2-1.5z\"\/><\/svg>',<br \/>\n  plus:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 5v14M5 12h14\"\/><\/svg>',<br \/>\n  edit:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M12 20h9\"\/><path d=\"M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\"\/><\/svg>',<br \/>\n  trash:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 6h18\"\/><path d=\"M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2\"\/><path d=\"M19 6l-.9 14a1 1 0 0 1-1 1H6.9a1 1 0 0 1-1-1L5 6\"\/><\/svg>',<br \/>\n  print:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 9V3h12v6\"\/><rect x=\"4\" y=\"9\" width=\"16\" height=\"8\" rx=\"1.2\"\/><path d=\"M6 17v4h12v-4\"\/><\/svg>',<br \/>\n  download:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M12 3v13\"\/><path d=\"M6 11l6 6 6-6\"\/><path d=\"M4 21h16\"\/><\/svg>',<br \/>\n  x:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 6 6 18M6 6l12 12\"\/><\/svg>',<br \/>\n  check:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M20 6 9 17l-5-5\"\/><\/svg>',<br \/>\n  search:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"11\" cy=\"11\" r=\"7\"\/><path d=\"m21 21-4.3-4.3\"\/><\/svg>',<br \/>\n  plane:'<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M21 15.5v-2l-8-5V4a1.5 1.5 0 0 0-3 0v4.5l-8 5v2l8-2.5V17l-2.5 2v1.5L11 20l3.5.5V19L12 17v-4.5z\"\/><\/svg>',<br \/>\n  money:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"2\" y=\"6\" width=\"20\" height=\"12\" rx=\"2\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><path d=\"M6 10v.01M18 14v.01\"\/><\/svg>',<br \/>\n  clock:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"12\" cy=\"12\" r=\"9\"\/><path d=\"M12 7v5l3 3\"\/><\/svg>',<br \/>\n  receipt:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 2h12v20l-3-2-3 2-3-2-3 2z\"\/><path d=\"M9 8h6M9 12h6\"\/><\/svg>',<br \/>\n  ticket:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 4v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-4z\"\/><path d=\"M13 5v14\" stroke-dasharray=\"2 2\"\/><\/svg>',<br \/>\n  building:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"\/><path d=\"M9 8h1M14 8h1M9 12h1M14 12h1M9 16h1M14 16h1\"\/><\/svg>',<br \/>\n  empty:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><path d=\"M4 7h16l-1.5 12a1 1 0 0 1-1 1H6.5a1 1 0 0 1-1-1L4 7z\"\/><path d=\"M8 7V5a4 4 0 0 1 8 0v2\"\/><\/svg>',<br \/>\n  lock:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"4\" y=\"10\" width=\"16\" height=\"10\" rx=\"2\"\/><path d=\"M8 10V7a4 4 0 0 1 8 0v3\"\/><\/svg>',<br \/>\n  logout:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"\/><path d=\"M16 17l5-5-5-5\"\/><path d=\"M21 12H9\"\/><\/svg>',<br \/>\n  expense:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"2\" y=\"6\" width=\"20\" height=\"14\" rx=\"2\"\/><path d=\"M2 10h20\"\/><path d=\"M14 15h4\"\/><\/svg>',<br \/>\n  trendUp:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 17l6-6 4 4 8-8\"\/><path d=\"M15 7h6v6\"\/><\/svg>',<br \/>\n  trendDown:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 7l6 6 4-4 8 8\"\/><path d=\"M15 17h6v-6\"\/><\/svg>'<br \/>\n};<\/p>\n<p>\/* ---------- Constants ---------- *\/<br \/>\nconst STATES = [<br \/>\n {code:'01',name:'Jammu and Kashmir'},{code:'02',name:'Himachal Pradesh'},{code:'03',name:'Punjab'},<br \/>\n {code:'04',name:'Chandigarh'},{code:'05',name:'Uttarakhand'},{code:'06',name:'Haryana'},{code:'07',name:'Delhi'},<br \/>\n {code:'08',name:'Rajasthan'},{code:'09',name:'Uttar Pradesh'},{code:'10',name:'Bihar'},{code:'11',name:'Sikkim'},<br \/>\n {code:'12',name:'Arunachal Pradesh'},{code:'13',name:'Nagaland'},{code:'14',name:'Manipur'},{code:'15',name:'Mizoram'},<br \/>\n {code:'16',name:'Tripura'},{code:'17',name:'Meghalaya'},{code:'18',name:'Assam'},{code:'19',name:'West Bengal'},<br \/>\n {code:'20',name:'Jharkhand'},{code:'21',name:'Odisha'},{code:'22',name:'Chhattisgarh'},{code:'23',name:'Madhya Pradesh'},<br \/>\n {code:'24',name:'Gujarat'},{code:'26',name:'Dadra and Nagar Haveli and Daman and Diu'},{code:'27',name:'Maharashtra'},<br \/>\n {code:'29',name:'Karnataka'},{code:'30',name:'Goa'},{code:'31',name:'Lakshadweep'},{code:'32',name:'Kerala'},<br \/>\n {code:'33',name:'Tamil Nadu'},{code:'34',name:'Puducherry'},{code:'35',name:'Andaman and Nicobar Islands'},<br \/>\n {code:'36',name:'Telangana'},{code:'37',name:'Andhra Pradesh'},{code:'38',name:'Ladakh'},{code:'97',name:'Other Territory'}<br \/>\n];<br \/>\nconst GST_RATES = [0,5,12,18,28];<br \/>\nconst SERVICE_PRESETS = [<br \/>\n {name:'Domestic Air Ticket', hsn:'9964', rate:5, unit:'ticket'},<br \/>\n {name:'International Air Ticket', hsn:'9964', rate:5, unit:'ticket'},<br \/>\n {name:'Hotel Booking (Domestic)', hsn:'996311', rate:12, unit:'night'},<br \/>\n {name:'Hotel Booking (International)', hsn:'996311', rate:5, unit:'night'},<br \/>\n {name:'Domestic Tour Package', hsn:'9985', rate:5, unit:'package'},<br \/>\n {name:'International Tour Package', hsn:'9985', rate:5, unit:'package'},<br \/>\n {name:'Visa Assistance', hsn:'998596', rate:18, unit:'application'},<br \/>\n {name:'Travel Insurance', hsn:'997133', rate:18, unit:'policy'},<br \/>\n {name:'Railway Ticket Booking', hsn:'996421', rate:5, unit:'ticket'},<br \/>\n {name:'Bus Ticket Booking', hsn:'996421', rate:5, unit:'ticket'},<br \/>\n {name:'Car \/ Cab Rental', hsn:'996601', rate:5, unit:'trip'},<br \/>\n {name:'Forex Service', hsn:'997157', rate:18, unit:'transaction'},<br \/>\n {name:'Passport Assistance', hsn:'998596', rate:18, unit:'application'},<br \/>\n {name:'Tour Guide Service', hsn:'998552', rate:18, unit:'day'}<br \/>\n];<br \/>\nconst PAY_MODES = ['Cash','Bank Transfer','UPI','Cheque','Card','Other'];<br \/>\nconst EXPENSE_CATEGORIES = [<br \/>\n  'Vendor Payments (Hotels\/Airlines\/Package Cost)',<br \/>\n  'Office Rent',<br \/>\n  'Salaries & Staff',<br \/>\n  'Marketing & Advertising',<br \/>\n  'Travel & Conveyance',<br \/>\n  'Utilities (Electricity\/Internet)',<br \/>\n  'Mobile Phone Bill',<br \/>\n  'Credit Card Bill',<br \/>\n  'Software & Subscriptions',<br \/>\n  'Bank & Payment Gateway Charges',<br \/>\n  'Commission Paid',<br \/>\n  'Printing & Stationery',<br \/>\n  'Professional Fees (CA\/Legal)',<br \/>\n  'Other'<br \/>\n];<br \/>\nconst DEFAULT_BUSINESS_NAME = 'Sarvalok Bhraman';<br \/>\nconst DEFAULT_BUSINESS = {<br \/>\n  name: 'Sarvalok Bhraman',<br \/>\n  logo: '', \/\/ set below to DEFAULT_LOGO once DEFAULT_LOGO is defined<br \/>\n  gstin: '07AFEFS0908J1ZY',<br \/>\n  pan: 'AFEFS0908J',<br \/>\n  address: 'E-514 F\/F Street No-7A Sangam Vihar',<br \/>\n  city: 'Delhi',<br \/>\n  pincode: '110062',<br \/>\n  state: 'Delhi',<br \/>\n  stateCode: '07',<br \/>\n  phone: '8567950007',<br \/>\n  email: 'enquiry@sarvalokbhraman.com',<br \/>\n  invoicePrefix: 'SLB',<br \/>\n  nextInvoiceNumber: 1,<br \/>\n  bosInvoicePrefix: 'SLB-BOS',<br \/>\n  nextBosInvoiceNumber: 1,<br \/>\n  terms: '',<br \/>\n  bankName: '',<br \/>\n  bankAccount: '',<br \/>\n  bankIFSC: '',<br \/>\n  upiId: ''<br \/>\n};<br \/>\nconst DEFAULT_LOGO = 'data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVQAAAE5CAYAAADP+NgxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAETRSURBVHhe7d15fFxV+T\/wz+fcmSTdFyj73nRhpwVUNrGylTZlU\/Cr6Fdxww2SgiiuyBdFEbAJRRQ31J8riyJNCshSyo6UFATa0oUdCrSlK22TmXs+vz8my9yTtE3SpM1knvfr1VczzzmTubn3PnPu2S5gjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGLM9MAwYY3rGqEeX714al7xvg8\/MWXL8Dq+F5abwuTBgjOl+5fVrRpSw7A\/e+VtLotRVO9+9dEBYxxQ+S6jG9LBDnvYDSt7LXO4zGycgmyV89iPDBw+qCuuZwmcJ1ZietmHtQXCp8wk6kISURjZzxpiHl+0WVjWFzRKqMT0s6\/kXZBtbxisIAPKHOpZ+NlHRFDxLqMb0oLEPLD9LcWYf0LUOAOdaqSnITyyfubA08QZT0CyhGtNjRFdScgOldmfTyPtD04N3OCGMm8JlCdWYHrL\/Q2smKs4OAdkmoZIkof5E6nNhmSlcllCN6SGMeDm93\/QtvUQhHrfrHXP6h0WmMFlCNaYHjJm1ch\/F8UiFBaHY7zZ02Kjjw7ApTJZQjekBUYofh+LBbOd2vxlJgkjD8dywzBQmS6jGdDcpAnkmwSgsClEgiSPDuClMllCN6Wb73L9yD8nvDW3xhj\/Hx3vv8ajvF4ZN4bGEakw3K037IyE\/uL3R\/TZIysep\/g1L9w6LTOGxhGpMN4ui9GGQNj26H6DgolT\/o8O4KTyWUI3pTpKDtNeWm6Z5SDmXtnX9fYAlVGO60wMPODk3PAxvlkQxLgvDpvBYQjWmOy0b4SDEHRyOyiHl43hdGDaFxxKqMd3N+0FhaLPkSWpVGDaFxxKqMd2ofOABBHxpp\/pQAUh6O4yZwmMJ1ZhutPi1et+VR7UpdnEYM4XHEqox3ah8z\/EOQDqMbxadUk4rwrApPJZQjemG8pnLB+\/\/4Or3j5m16oiwrNmB9604Oj1kbQ3lD+zQpP5mAmOpfK+HXhkWFpnC0vGDbkyR2nXOG\/2HbuxXRUb\/B+F135j96oIP71DXXH7g7BV7wrmPebrz6bMjO31dSVI6LcT+l3FDw40LT9zlv2GVsU+u29U1YOd5xw58OiwzvUfnDrwxReiQx9YcmxVmI5txAKRU+sX5dz81dsxJB\/Wj0h9w8N+Cc8cjm2HiUSedJ5FPIpX+2fyjBv29OXjgwxtGMsr8MvbxoRD\/PP\/YoVOTbzO9xdYcfGP6vptvjg7cfWKtsg2ngI6SJDLjougyxdl96aLPK45Bslu6zyR5RNFqxPGvshlMT++yw2q3es1v4mzmbACEc8tBd8H8Y4e2JFzTe1hCNWYzdr5JZQNGrVkAn83fvEQCYkIO6J5EmiAJUQRP3slsPIvOXQV5BwCCxCh9+7xjhpwVvs1sf91\/MhjTh7zyMrJim31NSSDVI8kUuR2o4D1dHJ8K4kr4OK\/hQwjaBbNmpfLfYnqHnjkhjOkrdgMpNYbhbYQE0vkzBiiBQAkmfMjmrfZCllCN2YwD9l\/xbTjsE8a3J4rLAHZquwCzbVhCNaYdB85eccoBD698y4PfRjbba64TAfDww3Gj0jYG0vvYATEmzwGz3i5HSel1ymaPJTkQUq5Ps7dommVAuqUZH5+z6Pgd\/xNWMdtP7zlRjNmODrl76QA\/eOBlcTZzJqSRkHJPJe2NJIGEotR7ymSuXvChHf8vrGK2j955whizrUg84NE1nxHwWcTZD0CKem0ibUuSGl1J6byGlUuPWnzT01ncco4NVm1HhXLiGNPtxj6w4qgo7ap8rJMoPxRomrK0NdT0qNOt\/T6dIi86+Rhnxc88fvfiykkNYQ2zbWzDg25M77D\/I+\/uTe8+AfjzIJXn7qC7kACVy57NbxQRg24dpBJK\/TqUVJtu37ulrzaKvGL9pmFw6nsvHjbonbDY9LytO4DGFJBd57zRf1h28OnMZs4T+GHFsetSIm3OpWQMIEbk3iZdvTyfzDY23hel3Ecc9PUtXl+S5KJlgr+NiD4MxaMhaWuWsUryTJUsjuXPjde88+ziSaOttboNbf6AG9MXXCY39pRVh7kYn5f0ecBHFNjhFmHrbXxuyWkUrQb5jIQZ2Qz\/rfVvvZifuMY8uubbUTbzwy1eX5KUSs3wy5Z+ksN2GEiW7uUdTnfEJym\/u7JZ0nVls5VcF4DEb2hj9m8vnDzizbCG6RldOFjGFI6Rs1fsWercOYS+5aVhlDafSJuTJwABYhQ1gm6lpMcA3JPVxn+vW\/n6W0tPO2J98o2txjy4+rwI2d9u8fqSpFT0q\/nHDPtSS+zGOem9R48cUKJ4TJRKfQSOn6fPDu3SrAMXeXnVqdT9eMH7hzwWFpvu17kDZEwBKZ+5sLRk6M5\/QzZ7+ib7KJsSqACA9HAuQ\/AtkY944d6Y0TOpVPzqghVvr0MHb5\/HPrjq4w7xn7a0cEaQp0t\/fd6xQ6aFZQCAyy5zOx\/9pX5DgJ2igf2\/Bep0ZrJDQKYlEbmxr7b\/pzy53atSS6Hs9zNrceviSTuuCeuY7rPZg2FMIdv1jjf6Dx3afw7h908UNCdR5zzABlKvA+5huOhp0f\/nvcaGV1\/1I5ZhArOJ93XQ6AdWnJpyqN1iQiUbveO5Lxwz7NawrF2XKbXPB1ce2K80VQnFp0AaAPnBbPpA2HRylQcdvItujDduvHHRCTs\/E9Yw3WMTB8CYwrfDw8sG7cKS53w2s2dTc04e2EBwEZxbLESPxtCchlX+uVcrhqzqrvXx5Q8t\/0Ca7iF6bXpHKElw0bosokkLjxv8cFjcESMfeOuokvSAL4J+HLLxPoAGYbMtVwrAE0yna54\/atDfwlKz9dr5pRvTN+w6R\/2HNax7XopBuOcFPBb77IyGeNjCVyZwY1i\/u4x6YM3+6bSvRxyXhWUtJMm55T7b8L4XJuz6cljcKZcpNfqYt493AwZ8lD57BKRRgAaxKbnmd3UI8GBUO3\/p62fjnIO21y5afZYlVNN33axo\/x1W7jH\/hOGvNneTbgtjZq3cx5W4ZxhnB4dlLSTBuaffy64++pUJ+3Zbct\/j76\/267\/r4OMjxw8B\/hgKh8LHA1vmy5IQo3\/MXzrkYziHtqqqm1lCNaab7fXQqmEDHRYijncMy1pIQip1x7xjhp4RFnWXPe56dfjAAcMOB+ITIR1F4DBQAwj3l3nHDfnf7uriMK0222lujOm691ZksorjTU6rQm5ASkL0VhjvTq9P3OvdBccNumfBcUO\/iQZ3phfPJtx3FaXmWzLtGdZCNaa73fRS2djyYY85xYeFRc1ExgS\/Oe+4YdeGZT2MeO65NA6y\/tOeYC1UY7rby79vJN3bYbhFbtpWDJ+dGxZtA7Jk2nMsoRrT3S6\/3MthWf6qqwSS9FqtbGZJWGQKmyVUY3qAy2YXh7EWuSlTb89v2KVH+1DNtmcJ1Zge4GO+EcbyMZV6F6d2bSWW6b0soRrTEyIt0qamvpKAjxeC8GGRKWyWUI3pAT5Ovb2piUkCPMRHbOpS32MJ1Zge8DbitxRF7d7SE1zv1fhsGDeFzxKqMT1g9cIl75FuQxiXJDlm1iP9YlhmCp8lVGN6wvlHZARlw6lTzM2aWj5w6evv5cdN32AJ1ZgeIRJudZtOUhIin51nOz31SZZQjekRlCLX5llOkhez\/pEwbvoGS6jG9BAKy8HkSD7pILhH82Om77CEakxPkOjlNyJcfUqqwTe8mwyavsISqjHdbM9\/L9vtgMfW3MM4e2qbHd1Ilkapy0fNenuTO1GZwmXb9xnTjfZ\/YNU5SLvvIM4ctJlHVst7PYQo9cMFxw29Jyw0hau9g22M6aQxDy8bFEVl30Um80lAu27x2pKEKPW0i0p+9txR\/f8UFpvCtPmDbozZotGz3j42VVJ2gXz2LHhF7T9xtC3lkuprcKmfzz964E\/DclN4OnTgjTHtmDUrdUDZkecp03ghoQO7cj0p97C+1SD\/NP\/5JRfh\/CMyYR1TODp9AhhjgNGzlo1Np9Kf8fIXQr6so63SdkkSkBX4d+\/TUxdOGLw8rGIKQ9dPAmOK1JhH1x7sstkf0qECPnbtXkbNS05JtEydIilJ7Sbf5vqp9H2NGzKVi0\/ccV5YxfR+Nm3KmE5SNrsToBPhfTKZSgIkkY1wqZcRpf4kl\/o5XNQg57zA38OlnwLpJSX3QiVzMwJ8fEK6f8kfRs9as+lHUJteyxKqMZ1EB08ogiRIEhGLXAWXmiuXqvFwn8wAJ655Y835mYaN1SKXEZzPlLu40WfPElNnianfy0WrJCixgYr3YJwdlYnXR4l\/1BSEtrcexpjNGvvIugoq81cKa0QuFDBLMZ7IbNww98WTd1mWv9y0fObC0pKhu\/wGsX9i3r1DbsDlzLVMZy4sHT1wxzF07mgHnkfqCGWzIEA4t2Lj0pXlL54zcnX+v2t6P0uoxnTSqAfe3D8VlZ2srO5bsGz4QpzDze4cNfbx9w73DWveXXj8ri+FZQAw+o41O3JIZrQjz6aLPivEfv6a5btg0uiGsK4xxpiOuHFOunzmmhGjZi0\/EZfJuuOMMcYYY0y3kXWlFSm7rTCmm42dvWrc2EdW\/U8YR9MgFWYpFcZN32CfpMZ0szEPrfy6Iy6Qi2Yjxj9jxi\/R+wEpVzKekZuU8fHUhccMXhC+zxQ+S6jGdKc5Su+\/YfUNUPazucuLGwA15q41DgDdfEaYMO\/oobbJdB9kt\/zGdKO9164aAGksBRJwhAZQGkppCOVTkn9y3lFDVobvM32DJVRjulEJ411BjEpsLN20rFSkd+Aj4XOmTN9hCdWYbsR0tCuhtuvwc9v0NSBKvRIWmb7DEqrZLF0Gp5vPjnTZ8anc33CS9b23S2IEdzDU\/rQpCq9uXLfuuTBu+o52D7wpXn76abvBx+dA\/CjJY8LyZvJ6G8Q\/QH+Lq5w5KywvRuUzF5amB4\/4HeP4422eJSVJUfTX+ccO+yQAu+XvoyyhGgCAv+b0PZHyPyLxqbBsSyQtBnCpq6q9LSwrJuUPrhlR4uLH4f1+YZkAT5e6ZN6xQ34Wlpm+w275DXxNxf8x7V\/tSjJFbsylnOStqqmY42smjgzLi8gIAHuFQQAg2JD1G58I46ZvsYRa5Hx1xZ8Ifi+Mdw0Ph1KP+pqJB4QlxSBFjMltOh3I7Zn6vC+Jng2LTN\/S9uCbouGrK24jeW4Y3xokd4JSD\/nqKYeEZX3azYrI+PRNDUiB0eLF799hbRg2fYsl1CLlp035Gcmzwni7ygYBO40Chu4WlrSL5HBAd\/prTx4elvVVe494eRDJk9sMRgGAo6c0y+af9n1tD77p8\/x1k86ios0PIA3eGTjsNGDkB4BBI5Jlr9QD8+4FFj2cjAckPeiqao8P433Rfo+s3alU2aUMb\/klie7NxjjzoSUTdl6cKDN9jiXUIuOnn7Yb4ngR6fqHZS2OPQ8Yf2YYbWvFy8Bd1wIrNj1XXfLfdFV1Pw3jfc3oB9\/9ZCT\/B5JtE2qUenB+ZsiJmMBsosz0OXbLX2zi+PJNJtNBOwIfu6ZjyRQAdtgH+Ni1wP4nhCUtCH5bN544JIz3KRKdi77QJpkCucdIgw9aMi0ObU8A02f56aeOJt3nw3iLMccDO48Oo5uXKgHe\/z9A6YCwJIccoo1l3w7DfY0j2l0EIWmVMpmZYdz0TZZQi4mPvhKGWvQbDBxxdhjtmME7A4d\/NIy2oHB+GOtLRj2w6lD4OAwDkhhFy7O7D58bFpm+yRJqMRFOC0Mtxp8JlLTfE9Ahh00B0v3CaA45RNUVx4bhviJVmvoy5NsdjxD5yOLRzp5eWiQsoRYJTTt9KMl9w3iL\/T8cRjonVQLse2QYbSFgUhjrKyh\/OsINY3Kj++sh97tE3PRpllCLRRTvEoZaRGmg\/7Aw2nk7bjpfgzouDPUFB8x6u1zZ7OAwDgAuilasdQPnhHHTd1lCLRLKYtP3892RTIFND0wBgLh3GOoTSvufD6IkDIuQgMdeP9ptCMtM32UJtWhwEx2cABreCyNdEzeGkRYk9wxjfYEUT6DkwhVSpGt0jY3X5MdM32cJtXiUhoEWje8BmY1htPNWLQ0jCbr57CiMFbKxs1YcRB\/vE8YBAM5tfA47Ph2GTd9mCbVI0GnTzUcAeOnJMNJ5r20hf5x9iw9DhcyVlPyvwCFh6xSS93H8B5vMX3wsoRYJ0W3+vv6FB8JI57z1ArDyjTDaQtI6sg\/tVH\/ZrJTgj4f3bVvdUYSMcG0YNn2fJdRiQa0KQwkvPQm8sRWPO3r4pjASeiYMFLIxJx52LOJs22VlkgA3L+uHLwuLTN9nCbVIuOsNtWSp97pi0fjoqEWHOzuZDFCELVR7XekqXn9NsomZFNXCUaGSfg7cM4vzo4cDoDcT4kj9bXQAAAAAElFTkSuQmCC';<br \/>\nDEFAULT_BUSINESS.logo = DEFAULT_LOGO;<\/p>\n<p>\/* ---------- State ---------- *\/<br \/>\nlet DB = { business:null, customers:[], items:[], invoices:[], expenses:[] };<br \/>\nlet VIEW = 'dashboard';<br \/>\nlet VPARAM = null;<\/p>\n<p>\/* ---------- Storage ---------- *\/<br \/>\nasync function loadAll(){<br \/>\n  DB.business = await safeGet('business', null);<br \/>\n  if(!DB.business){<br \/>\n    DB.business = {...DEFAULT_BUSINESS};<br \/>\n    await saveBusiness();<br \/>\n  }<br \/>\n  DB.customers = await safeGet('customers', []);<br \/>\n  DB.items = await safeGet('items', []);<br \/>\n  DB.invoices = await safeGet('invoices', []);<br \/>\n  DB.expenses = await safeGet('expenses', []);<br \/>\n  if(!DB.items.length){<br \/>\n    DB.items = SERVICE_PRESETS.map(p=>({id:uid(),name:p.name,hsn:p.hsn,gstRate:p.rate,unit:p.unit,rate:0}));<br \/>\n    await saveItems();<br \/>\n  }<br \/>\n}<br \/>\nasync function safeGet(key, fallback){<br \/>\n  try{<br \/>\n    const doc = await FIRESTORE_DB.collection(FIRESTORE_COLLECTION).doc(key).get();<br \/>\n    return doc.exists ? JSON.parse(doc.data().value) : fallback;<br \/>\n  }catch(e){<br \/>\n    console.error('Firestore read failed for key \"'+key+'\":', e);<br \/>\n    return fallback;<br \/>\n  }<br \/>\n}<br \/>\nasync function saveKey(key, value){<br \/>\n  try{<br \/>\n    await FIRESTORE_DB.collection(FIRESTORE_COLLECTION).doc(key).set({<br \/>\n      value: JSON.stringify(value),<br \/>\n      updatedAt: new Date().toISOString()<br \/>\n    });<br \/>\n    return true;<br \/>\n  }catch(e){<br \/>\n    console.error('Firestore write failed for key \"'+key+'\":', e);<br \/>\n    toast('Could not save \u2014 check Firebase setup \/ connection');<br \/>\n    return false;<br \/>\n  }<br \/>\n}<br \/>\nasync function saveBusiness(){ await saveKey('business', DB.business); }<br \/>\nasync function saveCustomers(){ await saveKey('customers', DB.customers); }<br \/>\nasync function saveItems(){ await saveKey('items', DB.items); }<br \/>\nasync function saveInvoices(){ await saveKey('invoices', DB.invoices); }<br \/>\nasync function saveExpenses(){ await saveKey('expenses', DB.expenses); }<\/p>\n<p>\/* ---------- Helpers ---------- *\/<br \/>\nfunction uid(){ return Date.now().toString(36)+Math.random().toString(36).slice(2,8); }<br \/>\nfunction fmtMoney(n){ n = Number(n)||0; return '\u20b9' + n.toLocaleString('en-IN',{minimumFractionDigits:2,maximumFractionDigits:2}); }<br \/>\nfunction fmtNum(n){ n=Number(n)||0; return n.toLocaleString('en-IN',{maximumFractionDigits:2}); }<br \/>\nfunction fmtDate(d){ if(!d) return '-'; const dt=new Date(d); return dt.toLocaleDateString('en-IN',{day:'2-digit',month:'short',year:'numeric'}); }<br \/>\nfunction todayStr(){ return new Date().toISOString().slice(0,10); }<br \/>\nfunction monthKey(d){ return new Date(d).toISOString().slice(0,7); }<br \/>\nfunction stateName(code){ const s=STATES.find(x=>x.code===code); return s?s.name:''; }<br \/>\nfunction esc(s){ if(s===undefined||s===null) return ''; return String(s).replace(\/[&<>\"']\/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c])); }<br \/>\nfunction toast(msg){<br \/>\n  const t=document.getElementById('toast');<br \/>\n  t.innerHTML = ICONS.check + '<span>'+esc(msg)+'<\/span>';<br \/>\n  t.classList.add('show');<br \/>\n  clearTimeout(window.__toastT);<br \/>\n  window.__toastT = setTimeout(()=>t.classList.remove('show'), 2200);<br \/>\n}<br \/>\nfunction customerById(id){ return DB.customers.find(c=>c.id===id); }<br \/>\nfunction itemById(id){ return DB.items.find(i=>i.id===id); }<br \/>\nfunction invoiceById(id){ return DB.invoices.find(i=>i.id===id); }<br \/>\nfunction expenseById(id){ return DB.expenses.find(e=>e.id===id); }<br \/>\nfunction totalExpensesFor(list){ return list.reduce((s,e)=>s+(Number(e.amount)||0),0); }<\/p>\n<p>function computeLine(line){<br \/>\n  const base = (Number(line.qty)||0) * (Number(line.rate)||0);<br \/>\n  const discount = base * ((Number(line.discountPct)||0)\/100);<br \/>\n  const baseAfterDiscount = Math.max(0, base - discount);<br \/>\n  const serviceCharge = Number(line.serviceCharge)||0;<br \/>\n  \/\/ GST applies only on the service charge, never on the base\/ticket amount.<br \/>\n  const taxable = serviceCharge;<br \/>\n  const intra = isIntraState(line.__intra);<br \/>\n  const effRate = line.__nonGst ? 0 : (Number(line.gstRate)||0);<br \/>\n  const taxAmt = taxable * (effRate\/100);<br \/>\n  let cgst=0, sgst=0, igst=0;<br \/>\n  if(intra){ cgst = taxAmt\/2; sgst = taxAmt\/2; } else { igst = taxAmt; }<br \/>\n  return { base, discount, baseAfterDiscount, serviceCharge, taxable, cgst, sgst, igst, total: baseAfterDiscount+serviceCharge+taxAmt };<br \/>\n}<br \/>\nfunction isIntraState(flag){ return !!flag; }<\/p>\n<p>function computeInvoiceTotals(inv){<br \/>\n  const intra = DB.business && inv.customerStateCode && DB.business.stateCode === inv.customerStateCode;<br \/>\n  const nonGst = inv.invoiceType === 'bos';<br \/>\n  let baseTotal=0, taxable=0, cgst=0, sgst=0, igst=0;<br \/>\n  const lines = (inv.lines||[]).map(l=>{<br \/>\n    const c = computeLine({...l, __intra: intra, __nonGst: nonGst});<br \/>\n    baseTotal += c.baseAfterDiscount; taxable += c.taxable; cgst += c.cgst; sgst += c.sgst; igst += c.igst;<br \/>\n    return {...l, ...c};<br \/>\n  });<br \/>\n  const grand = baseTotal+taxable+cgst+sgst+igst;<br \/>\n  const rounded = Math.round(grand);<br \/>\n  const roundOff = rounded - grand;<br \/>\n  return { lines, intra, nonGst, baseTotal, taxable, cgst, sgst, igst, grand, rounded, roundOff };<br \/>\n}<br \/>\nfunction invoicePaid(inv){ return (inv.payments||[]).reduce((s,p)=>s+Number(p.amount||0),0); }<br \/>\nfunction invoiceStatus(inv){<br \/>\n  const t = computeInvoiceTotals(inv).rounded;<br \/>\n  const paid = invoicePaid(inv);<br \/>\n  if(paid<=0) return 'Unpaid';\n  if(paid >= t - 0.5) return 'Paid';<br \/>\n  return 'Partial';<br \/>\n}<br \/>\nfunction statusBadge(status){<br \/>\n  const cls = status==='Paid'?'badge-paid':status==='Partial'?'badge-partial':'badge-unpaid';<br \/>\n  return `<span class=\"badge ${cls}\">${status}<\/span>`;<br \/>\n}<br \/>\nfunction invoiceTypeBadge(inv){<br \/>\n  const isBos = inv.invoiceType === 'bos';<br \/>\n  return `<span class=\"badge ${isBos?'badge-bos':'badge-tax'}\">${isBos?'Bill of Supply':'Tax Invoice'}<\/span>`;<br \/>\n}<\/p>\n<p>\/* ---------- App shell \/ routing ---------- *\/<br \/>\nconst NAV = [<br \/>\n  {id:'dashboard', label:'Dashboard', icon:'dashboard'},<br \/>\n  {id:'invoices', label:'Invoices', icon:'invoice'},<br \/>\n  {id:'customers', label:'Customers', icon:'users'},<br \/>\n  {id:'items', label:'Services', icon:'package'},<br \/>\n  {id:'expenses', label:'Expenses', icon:'expense'},<br \/>\n  {id:'reports', label:'GST Reports', icon:'chart'},<br \/>\n  {id:'settings', label:'Business Profile', icon:'settings'}<br \/>\n];<\/p>\n<p>function renderShell(){<br \/>\n  const app = document.getElementById('app');<br \/>\n  app.innerHTML = `<\/p>\n<div id=\"sidebar\">\n<div class=\"brand\">\n<div class=\"brand-mark\">${DB.business&&DB.business.logo? `<img decoding=\"async\" src=\"${DB.business.logo}\" alt=\"Logo\" style=\"width:100%;height:100%;object-fit:contain;border-radius:8px;background:#fff;\">` : `<img decoding=\"async\" src=\"${DEFAULT_LOGO}\" alt=\"Logo\" style=\"width:100%;height:100%;object-fit:contain;border-radius:8px;background:#fff;\">`}<\/div>\n<div>\n<div class=\"brand-name\">${DB.business&&DB.business.name? esc(DB.business.name) : 'Sarvalok Bhraman'}<\/div>\n<div class=\"brand-sub\">GST Billing<\/div>\n<\/p>\n<\/div>\n<\/div>\n<nav class=\"navlist\" id=\"navlist\"><\/nav>\n<div class=\"sidebar-foot\">\n        ${DB.business?esc(DB.business.name):'Set up your business'} <br \/>${DB.business&&DB.business.gstin?esc(DB.business.gstin):''}<\/p>\n<div style=\"margin-top:10px;\">\n          <button class=\"btn btn-sm\" style=\"width:100%;background:transparent;border-color:rgba(255,255,255,0.18);color:#DDE3F0;\" onclick=\"logout()\">${ICONS.logout} Logout<\/button>\n        <\/div>\n<\/p>\n<\/div>\n<\/div>\n<div id=\"main\">\n<div class=\"topbar\">\n<div>\n<h1 id=\"topTitle\">Dashboard<\/h1>\n<div class=\"sub\" id=\"topSub\">Overview of your travel business billing<\/div>\n<\/p>\n<\/div>\n<div id=\"topActions\"><\/div>\n<\/p>\n<\/div>\n<div class=\"content\" id=\"content\"><\/div>\n<\/p>\n<\/div>\n<p>  `;<br \/>\n  const navlist = document.getElementById('navlist');<br \/>\n  navlist.innerHTML = NAV.map(n=>`<br \/>\n    <button class=\"navitem ${VIEW===n.id?'active':''}\" data-nav=\"${n.id}\">${ICONS[n.icon]}<span>${n.label}<\/span><\/button><br \/>\n  `).join('');<br \/>\n  navlist.querySelectorAll('[data-nav]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.nav)));<br \/>\n}<\/p>\n<p>function navigate(view, param){<br \/>\n  VIEW = view; VPARAM = param||null;<br \/>\n  renderShell();<br \/>\n  route();<br \/>\n}<\/p>\n<p>function route(){<br \/>\n  const titles = {<br \/>\n    dashboard:['Dashboard','Overview of your travel business billing'],<br \/>\n    invoices:['Invoices','Create and manage GST invoices'],<br \/>\n    customers:['Customers','Manage client and corporate accounts'],<br \/>\n    items:['Services','Your travel service catalog with HSN\/SAC and GST rates'],<br \/>\n    expenses:['Expenses','Track your business costs and see your net profit'],<br \/>\n    reports:['GST Reports','Tax summaries for return filing'],<br \/>\n    settings:['Business Profile','Your company details used on every invoice']<br \/>\n  };<br \/>\n  document.getElementById('topTitle').textContent = titles[VIEW][0];<br \/>\n  document.getElementById('topSub').textContent = titles[VIEW][1];<br \/>\n  if(!DB.business && VIEW!=='settings'){<br \/>\n    renderNoBusiness();<br \/>\n    return;<br \/>\n  }<br \/>\n  if(VIEW==='dashboard') renderDashboard();<br \/>\n  else if(VIEW==='invoices') renderInvoices();<br \/>\n  else if(VIEW==='customers') renderCustomers();<br \/>\n  else if(VIEW==='items') renderItems();<br \/>\n  else if(VIEW==='expenses') renderExpenses();<br \/>\n  else if(VIEW==='reports') renderReports();<br \/>\n  else if(VIEW==='settings') renderSettings();<br \/>\n}<\/p>\n<p>function renderNoBusiness(){<br \/>\n  document.getElementById('topActions').innerHTML='';<br \/>\n  document.getElementById('content').innerHTML = `<\/p>\n<div class=\"empty-state card\">\n      ${ICONS.building}<\/p>\n<h3>Set up your business profile first<\/h3>\n<p>Your GSTIN, address and bank details appear on every invoice.<\/p>\n<p>      <button class=\"btn btn-primary\" onclick=\"navigate('settings')\">${ICONS.building} Set up business<\/button>\n    <\/div>\n<p>`;<br \/>\n}<\/p>\n<p>\/* ---------- Drawer ---------- *\/<br \/>\nfunction openDrawer(html, wide){<br \/>\n  document.getElementById('drawer').innerHTML = html;<br \/>\n  document.getElementById('drawer').style.width = wide? '760px':'560px';<br \/>\n  document.getElementById('overlay').classList.add('open');<br \/>\n  document.getElementById('drawer').classList.add('open');<br \/>\n}<br \/>\nfunction closeDrawer(){<br \/>\n  document.getElementById('overlay').classList.remove('open');<br \/>\n  document.getElementById('drawer').classList.remove('open');<br \/>\n}<\/p>\n<p>\/* ---------- Settings \/ Business Profile ---------- *\/<br \/>\nfunction renderSettings(){<br \/>\n  document.getElementById('topActions').innerHTML='';<br \/>\n  const b = DB.business || {name:DEFAULT_BUSINESS_NAME,gstin:'',pan:'',address:'',city:'',state:'',stateCode:'',pincode:'',phone:'',email:'',invoicePrefix:'INV',nextInvoiceNumber:1,bosInvoicePrefix:'INV-BOS',nextBosInvoiceNumber:1,terms:'',bankName:'',bankAccount:'',bankIFSC:'',upiId:'',logo:DEFAULT_LOGO};<br \/>\n  window.__logoDraft = b.logo || '';<br \/>\n  document.getElementById('content').innerHTML = `<\/p>\n<div class=\"card\" style=\"max-width:720px;\">\n<div class=\"section-title\" style=\"margin-top:0;\">Company details<\/div>\n<div class=\"field\">\n        <label>Logo<\/label><\/p>\n<div style=\"display:flex;align-items:center;gap:14px;\">\n<div id=\"logoPreviewWrap\" style=\"width:64px;height:64px;border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#fff;flex-shrink:0;\">\n            <img decoding=\"async\" id=\"logoPreview\" src=\"${esc(b.logo||'')}\" style=\"max-width:100%;max-height:100%;object-fit:contain;${b.logo?'':'display:none;'}\">\n          <\/div>\n<div>\n            <input type=\"file\" id=\"f_logo\" accept=\"image\/*\" onchange=\"handleLogoUpload(event)\"><\/p>\n<div class=\"field-hint\">PNG or SVG with a transparent background works best.<\/div>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Business name<\/label><input id=\"f_name\" value=\"${esc(b.name)}\" placeholder=\"Wanderlust Travels Pvt Ltd\"><\/div>\n<div class=\"field\"><label>GSTIN<\/label><input id=\"f_gstin\" value=\"${esc(b.gstin)}\" placeholder=\"07ABCDE1234F1Z5\" maxlength=\"15\" style=\"text-transform:uppercase\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>PAN<\/label><input id=\"f_pan\" value=\"${esc(b.pan)}\" placeholder=\"ABCDE1234F\" style=\"text-transform:uppercase\"><\/div>\n<div class=\"field\"><label>State (place of business)<\/label><br \/>\n          <select id=\"f_state\">${STATES.map(s=>`<option value=\"${s.code}\" ${b.stateCode===s.code?'selected':''}>${s.name} (${s.code})<\/option>`).join('')}<\/select>\n        <\/div>\n<\/p>\n<\/div>\n<div class=\"field\"><label>Address<\/label><textarea id=\"f_address\" placeholder=\"Office address\">${esc(b.address)}<\/textarea><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>City<\/label><input id=\"f_city\" value=\"${esc(b.city)}\"><\/div>\n<div class=\"field\"><label>PIN code<\/label><input id=\"f_pincode\" value=\"${esc(b.pincode)}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Phone<\/label><input id=\"f_phone\" value=\"${esc(b.phone)}\"><\/div>\n<div class=\"field\"><label>Email<\/label><input id=\"f_email\" value=\"${esc(b.email)}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"section-title\">Invoicing<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Invoice number prefix<\/label><input id=\"f_prefix\" value=\"${esc(b.invoicePrefix||'SLB')}\"><\/div>\n<div class=\"field\"><label>Next invoice number<\/label><input id=\"f_nextnum\" type=\"number\" min=\"1\" value=\"${b.nextInvoiceNumber||computeNextInvoiceNumber('tax')}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-hint\" style=\"margin-top:-8px;margin-bottom:14px;\">Normally the next number is worked out automatically from your last saved invoice. Change this only if you need to jump the sequence forward (e.g. to match an old invoice book) \u2014 it won't be allowed to go backwards below your last saved invoice number.<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Bill of Supply (non-GST) prefix<\/label><input id=\"f_bosprefix\" value=\"${esc(b.bosInvoicePrefix||(b.invoicePrefix||'SLB')+'-BOS')}\"><\/div>\n<div class=\"field\"><label>Next Bill of Supply number<\/label><input id=\"f_nextbosnum\" type=\"number\" min=\"1\" value=\"${b.nextBosInvoiceNumber||computeNextInvoiceNumber('bos')}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-hint\" style=\"margin-top:-8px;margin-bottom:14px;\">Bill of Supply (non-GST) invoices run in their own separate numbering series, e.g. ${esc(b.bosInvoicePrefix||(b.invoicePrefix||'SLB')+'-BOS')}-0001, so they never clash with your Tax Invoice numbers.<\/div>\n<div class=\"field\"><label>Default terms & notes (shown on invoice)<\/label><textarea id=\"f_terms\" placeholder=\"Payment due within 7 days. Prices inclusive of applicable GST.\">${esc(b.terms||'')}<\/textarea><\/div>\n<div class=\"section-title\">Bank details<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Bank name<\/label><input id=\"f_bankname\" value=\"${esc(b.bankName||'')}\"><\/div>\n<div class=\"field\"><label>Account number<\/label><input id=\"f_bankacc\" value=\"${esc(b.bankAccount||'')}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>IFSC<\/label><input id=\"f_ifsc\" value=\"${esc(b.bankIFSC||'')}\" style=\"text-transform:uppercase\"><\/div>\n<div class=\"field\"><label>UPI ID<\/label><input id=\"f_upi\" value=\"${esc(b.upiId||'')}\"><\/div>\n<\/p>\n<\/div>\n<p>      <button class=\"btn btn-primary\" onclick=\"saveBusinessForm()\">${ICONS.check} Save business profile<\/button>\n    <\/div>\n<div class=\"section-title\">Login security<\/div>\n<div class=\"card\" style=\"max-width:720px;\">\n<div class=\"field-hint\" style=\"margin-bottom:14px;\">Change the username and\/or password used to open this app. You'll need your current password to confirm.<\/div>\n<div id=\"pwChangeMsg\" class=\"auth-error\"><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>New username<\/label><input id=\"pw_username\" value=\"${esc(AUTH.username||'')}\"><\/div>\n<div class=\"field\"><label>Current password<\/label><input id=\"pw_current\" type=\"password\" placeholder=\"Required to confirm changes\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>New password<\/label><input id=\"pw_new\" type=\"password\" placeholder=\"Leave blank to keep current password\"><\/div>\n<div class=\"field\"><label>Confirm new password<\/label><input id=\"pw_confirm\" type=\"password\"><\/div>\n<\/p>\n<\/div>\n<p>      <button class=\"btn btn-amber\" onclick=\"changeCredentials()\">${ICONS.lock} Update login details<\/button>\n    <\/div>\n<p>  `;<br \/>\n}<br \/>\nfunction handleLogoUpload(evt){<br \/>\n  const file = evt.target.files[0];<br \/>\n  if(!file) return;<br \/>\n  const reader = new FileReader();<br \/>\n  reader.onload = ()=>{<br \/>\n    window.__logoDraft = reader.result;<br \/>\n    const img = document.getElementById('logoPreview');<br \/>\n    img.src = reader.result;<br \/>\n    img.style.display = 'block';<br \/>\n  };<br \/>\n  reader.readAsDataURL(file);<br \/>\n}<br \/>\nasync function saveBusinessForm(){<br \/>\n  const name = document.getElementById('f_name').value.trim();<br \/>\n  if(!name){ toast('Business name is required'); return; }<br \/>\n  const stateCode = document.getElementById('f_state').value;<br \/>\n  DB.business = {<br \/>\n    name, logo: window.__logoDraft || '',<br \/>\n    gstin: document.getElementById('f_gstin').value.trim().toUpperCase(),<br \/>\n    pan: document.getElementById('f_pan').value.trim().toUpperCase(),<br \/>\n    address: document.getElementById('f_address').value.trim(),<br \/>\n    city: document.getElementById('f_city').value.trim(),<br \/>\n    pincode: document.getElementById('f_pincode').value.trim(),<br \/>\n    state: stateName(stateCode), stateCode,<br \/>\n    phone: document.getElementById('f_phone').value.trim(),<br \/>\n    email: document.getElementById('f_email').value.trim(),<br \/>\n    invoicePrefix: document.getElementById('f_prefix').value.trim() || 'INV',<br \/>\n    nextInvoiceNumber: Number(document.getElementById('f_nextnum').value)||1,<br \/>\n    bosInvoicePrefix: document.getElementById('f_bosprefix').value.trim() || ((document.getElementById('f_prefix').value.trim()||'INV')+'-BOS'),<br \/>\n    nextBosInvoiceNumber: Number(document.getElementById('f_nextbosnum').value)||1,<br \/>\n    terms: document.getElementById('f_terms').value.trim(),<br \/>\n    bankName: document.getElementById('f_bankname').value.trim(),<br \/>\n    bankAccount: document.getElementById('f_bankacc').value.trim(),<br \/>\n    bankIFSC: document.getElementById('f_ifsc').value.trim().toUpperCase(),<br \/>\n    upiId: document.getElementById('f_upi').value.trim()<br \/>\n  };<br \/>\n  await saveBusiness();<br \/>\n  if(DB.business.logo) AUTH_LOGO = DB.business.logo;<br \/>\n  if(DB.business.name) AUTH_BIZNAME = DB.business.name;<br \/>\n  toast('Business profile saved');<br \/>\n  renderShell(); route();<br \/>\n}<\/p>\n<p>\/* ---------- Customers ---------- *\/<br \/>\nfunction renderCustomers(){<br \/>\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openCustomerForm()\">${ICONS.plus} Add customer<\/button>`;<br \/>\n  const list = DB.customers;<br \/>\n  let body;<br \/>\n  if(!list.length){<br \/>\n    body = `<\/p>\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No customers yet<\/h3>\n<p>Add a client or corporate account to start billing.<\/p>\n<p>      <button class=\"btn btn-primary\" onclick=\"openCustomerForm()\">${ICONS.plus} Add customer<\/button><\/div>\n<p>`;<br \/>\n  }else{<br \/>\n    body = `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Name<\/th>\n<th>GSTIN<\/th>\n<th>State<\/th>\n<th>Contact<\/th>\n<th class=\"right\">Invoices<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody>${list.map(c=>{<br \/>\n        const invCount = DB.invoices.filter(i=>i.customerId===c.id).length;<br \/>\n        return `<\/p>\n<tr>\n<td><strong>${esc(c.name)}<\/strong><\/td>\n<td class=\"mono\">${esc(c.gstin)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n<td>${esc(stateName(c.stateCode))}<\/td>\n<td>${esc(c.phone)}${c.phone&&c.email?' \u00b7 ':''}${esc(c.email)}<\/td>\n<td class=\"right\">${invCount}<\/td>\n<td>\n<div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n              <button class=\"btn btn-sm btn-icon\" onclick=\"openCustomerForm('${c.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button><br \/>\n              <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteCustomer('${c.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n            <\/div>\n<\/td>\n<\/tr>\n<p>`;<br \/>\n      }).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>`;<br \/>\n  }<br \/>\n  document.getElementById('content').innerHTML = body;<br \/>\n}<\/p>\n<p>function openCustomerForm(id){<br \/>\n  const c = id ? customerById(id) : {name:'',gstin:'',stateCode:DB.business?DB.business.stateCode:'',address:'',phone:'',email:''};<br \/>\n  openDrawer(`<\/p>\n<div class=\"drawer-head\">\n<h3>${id?'Edit customer':'Add customer'}<\/h3>\n<p><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n<div class=\"drawer-body\">\n<div class=\"field\"><label>Customer \/ company name<\/label><input id=\"c_name\" value=\"${esc(c.name)}\" placeholder=\"Sharma Family \/ Acme Corp\"><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>GSTIN (leave blank if unregistered)<\/label><input id=\"c_gstin\" value=\"${esc(c.gstin)}\" maxlength=\"15\" style=\"text-transform:uppercase\"><\/div>\n<div class=\"field\"><label>State<\/label><br \/>\n          <select id=\"c_state\">${STATES.map(s=>`<option value=\"${s.code}\" ${c.stateCode===s.code?'selected':''}>${s.name} (${s.code})<\/option>`).join('')}<\/select>\n        <\/div>\n<\/p>\n<\/div>\n<div class=\"field\"><label>Address<\/label><textarea id=\"c_address\">${esc(c.address)}<\/textarea><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Phone<\/label><input id=\"c_phone\" value=\"${esc(c.phone)}\"><\/div>\n<div class=\"field\"><label>Email<\/label><input id=\"c_email\" value=\"${esc(c.email)}\"><\/div>\n<\/p>\n<\/div>\n<\/div>\n<div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button><br \/>\n      <button class=\"btn btn-primary\" onclick=\"saveCustomerForm('${id||''}')\">${ICONS.check} Save customer<\/button>\n    <\/div>\n<p>  `);<br \/>\n}<br \/>\nasync function saveCustomerForm(id){<br \/>\n  const name = document.getElementById('c_name').value.trim();<br \/>\n  if(!name){ toast('Customer name is required'); return; }<br \/>\n  const stateCode = document.getElementById('c_state').value;<br \/>\n  const data = {<br \/>\n    name, gstin: document.getElementById('c_gstin').value.trim().toUpperCase(),<br \/>\n    stateCode, address: document.getElementById('c_address').value.trim(),<br \/>\n    phone: document.getElementById('c_phone').value.trim(),<br \/>\n    email: document.getElementById('c_email').value.trim()<br \/>\n  };<br \/>\n  if(id){<br \/>\n    const idx = DB.customers.findIndex(x=>x.id===id);<br \/>\n    DB.customers[idx] = {...DB.customers[idx], ...data};<br \/>\n  }else{<br \/>\n    DB.customers.push({id:uid(), ...data});<br \/>\n  }<br \/>\n  await saveCustomers();<br \/>\n  closeDrawer();<br \/>\n  toast('Customer saved');<br \/>\n  route();<br \/>\n}<br \/>\nasync function deleteCustomer(id){<br \/>\n  const used = DB.invoices.some(i=>i.customerId===id);<br \/>\n  if(used){ toast('Cannot delete \u2014 customer has invoices'); return; }<br \/>\n  if(!confirm('Delete this customer?')) return;<br \/>\n  DB.customers = DB.customers.filter(c=>c.id!==id);<br \/>\n  await saveCustomers();<br \/>\n  route();<br \/>\n}<\/p>\n<p>\/* ---------- Services \/ Items ---------- *\/<br \/>\nfunction renderItems(){<br \/>\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openItemForm()\">${ICONS.plus} Add service<\/button>`;<br \/>\n  const list = DB.items;<br \/>\n  let body;<br \/>\n  if(!list.length){<br \/>\n    body = `<\/p>\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No services yet<\/h3>\n<p>Add the travel services you bill for, with default HSN\/SAC and GST rate.<\/p>\n<p>      <button class=\"btn btn-primary\" onclick=\"openItemForm()\">${ICONS.plus} Add service<\/button><\/div>\n<p>`;<br \/>\n  }else{<br \/>\n    body = `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Service<\/th>\n<th>HSN \/ SAC<\/th>\n<th>Unit<\/th>\n<th class=\"right\">Default rate<\/th>\n<th class=\"right\">GST %<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody>${list.map(i=>`<\/p>\n<tr>\n<td><strong>${esc(i.name)}<\/strong><\/td>\n<td class=\"mono\">${esc(i.hsn)}<\/td>\n<td>${esc(i.unit)}<\/td>\n<td class=\"right mono\">${fmtMoney(i.rate)}<\/td>\n<td class=\"right mono\">${i.gstRate}%<\/td>\n<td>\n<div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n            <button class=\"btn btn-sm btn-icon\" onclick=\"openItemForm('${i.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button><br \/>\n            <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteItem('${i.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n          <\/div>\n<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>`;<br \/>\n  }<br \/>\n  document.getElementById('content').innerHTML = body;<br \/>\n}<br \/>\nfunction openItemForm(id){<br \/>\n  const i = id ? itemById(id) : {name:'',hsn:'',unit:'unit',rate:0,gstRate:5};<br \/>\n  openDrawer(`<\/p>\n<div class=\"drawer-head\">\n<h3>${id?'Edit service':'Add service'}<\/h3>\n<p><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n<div class=\"drawer-body\">\n<div class=\"field\"><label>Service name<\/label><input id=\"i_name\" value=\"${esc(i.name)}\" placeholder=\"Domestic Tour Package\"><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>HSN \/ SAC code<\/label><input id=\"i_hsn\" value=\"${esc(i.hsn)}\" placeholder=\"9985\"><\/div>\n<div class=\"field\"><label>Unit<\/label><input id=\"i_unit\" value=\"${esc(i.unit)}\" placeholder=\"package, night, ticket...\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Default rate (\u20b9)<\/label><input id=\"i_rate\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${i.rate||0}\"><\/div>\n<div class=\"field\"><label>GST rate<\/label><select id=\"i_gst\">${GST_RATES.map(r=>`<option value=\"${r}\" ${i.gstRate===r?'selected':''}>${r}%<\/option>`).join('')}<\/select><\/div>\n<\/p>\n<\/div>\n<div class=\"field-hint\">Presets follow common SAC codes for tour operators, air\/rail\/bus ticketing, hotels, visa and insurance services. Adjust to match your actual tax position.<\/div>\n<\/p>\n<\/div>\n<div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button><br \/>\n      <button class=\"btn btn-primary\" onclick=\"saveItemForm('${id||''}')\">${ICONS.check} Save service<\/button>\n    <\/div>\n<p>  `);<br \/>\n}<br \/>\nasync function saveItemForm(id){<br \/>\n  const name = document.getElementById('i_name').value.trim();<br \/>\n  if(!name){ toast('Service name is required'); return; }<br \/>\n  const data = {<br \/>\n    name, hsn: document.getElementById('i_hsn').value.trim(),<br \/>\n    unit: document.getElementById('i_unit').value.trim()||'unit',<br \/>\n    rate: Number(document.getElementById('i_rate').value)||0,<br \/>\n    gstRate: Number(document.getElementById('i_gst').value)<br \/>\n  };<br \/>\n  if(id){<br \/>\n    const idx = DB.items.findIndex(x=>x.id===id);<br \/>\n    DB.items[idx] = {...DB.items[idx], ...data};<br \/>\n  }else{<br \/>\n    DB.items.push({id:uid(), ...data});<br \/>\n  }<br \/>\n  await saveItems();<br \/>\n  closeDrawer();<br \/>\n  toast('Service saved');<br \/>\n  route();<br \/>\n}<br \/>\nasync function deleteItem(id){<br \/>\n  if(!confirm('Delete this service?')) return;<br \/>\n  DB.items = DB.items.filter(i=>i.id!==id);<br \/>\n  await saveItems();<br \/>\n  route();<br \/>\n}<\/p>\n<p>\/* ---------- Expenses ---------- *\/<br \/>\nlet EXP_MONTH_FILTER = 'all';<br \/>\nlet EXP_CAT_FILTER = 'all';<br \/>\nfunction expenseMonthOptions(){<br \/>\n  const set = new Set(DB.expenses.map(e=>monthKey(e.date)));<br \/>\n  return [...set].sort().reverse();<br \/>\n}<br \/>\nfunction renderExpenses(){<br \/>\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openExpenseForm()\">${ICONS.plus} Add expense<\/button>`;<br \/>\n  let list = [...DB.expenses].sort((a,b)=> new Date(b.date)-new Date(a.date));<br \/>\n  if(EXP_MONTH_FILTER!=='all') list = list.filter(e=>monthKey(e.date)===EXP_MONTH_FILTER);<br \/>\n  if(EXP_CAT_FILTER!=='all') list = list.filter(e=>e.category===EXP_CAT_FILTER);<br \/>\n  const months = expenseMonthOptions();<br \/>\n  const total = totalExpensesFor(list);<\/p>\n<p>  let body = `<\/p>\n<div class=\"card stat-card\" style=\"margin-bottom:16px;max-width:280px;\">\n<div class=\"stat-label\">${ICONS.expense} Total (filtered)<\/div>\n<div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(total)}<\/div>\n<div class=\"stat-note\">${list.length} expense${list.length===1?'':'s'}<\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\" style=\"margin-bottom:16px;max-width:600px;\">\n<div class=\"field\" style=\"margin-bottom:0;\">\n        <label>Month<\/label><br \/>\n        <select onchange=\"EXP_MONTH_FILTER=this.value;route()\"><option value=\"all\" ${EXP_MONTH_FILTER==='all'?'selected':''}>All months<\/option>${months.map(m=>`<option value=\"${m}\" ${EXP_MONTH_FILTER===m?'selected':''}>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/option>`).join('')}<br \/>\n        <\/select>\n      <\/div>\n<div class=\"field\" style=\"margin-bottom:0;\">\n        <label>Category<\/label><br \/>\n        <select onchange=\"EXP_CAT_FILTER=this.value;route()\"><option value=\"all\" ${EXP_CAT_FILTER==='all'?'selected':''}>All categories<\/option>${EXPENSE_CATEGORIES.map(c=>`<option value=\"${esc(c)}\" ${EXP_CAT_FILTER===c?'selected':''}>${esc(c)}<\/option>`).join('')}<br \/>\n        <\/select>\n      <\/div>\n<\/p>\n<\/div>\n<p>  `;<br \/>\n  if(!list.length){<br \/>\n    body += `<\/p>\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No expenses${EXP_MONTH_FILTER!=='all'||EXP_CAT_FILTER!=='all'?' match this filter':' yet'}<\/h3>\n<p>Log vendor payments, rent, salaries and other business costs here.<\/p>\n<p>      <button class=\"btn btn-primary\" onclick=\"openExpenseForm()\">${ICONS.plus} Add expense<\/button><\/div>\n<p>`;<br \/>\n  }else{<br \/>\n    body += `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Date<\/th>\n<th>Category<\/th>\n<th>Vendor \/ Paid to<\/th>\n<th>Description<\/th>\n<th>Mode<\/th>\n<th class=\"right\">Amount<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody>${list.map(e=>`<\/p>\n<tr>\n<td>${fmtDate(e.date)}<\/td>\n<td>${esc(e.category)}<\/td>\n<td>${esc(e.vendor)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n<td>${esc(e.description)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n<td>${esc(e.paymentMode)}<\/td>\n<td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(e.amount)}<\/td>\n<td>\n<div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n            <button class=\"btn btn-sm btn-icon\" onclick=\"openExpenseForm('${e.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button><br \/>\n            <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteExpense('${e.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n          <\/div>\n<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>`;<br \/>\n  }<br \/>\n  document.getElementById('content').innerHTML = body;<br \/>\n}<br \/>\nfunction openExpenseForm(id){<br \/>\n  const e = id ? expenseById(id) : {date:todayStr(), category:EXPENSE_CATEGORIES[0], vendor:'', description:'', amount:0, gstPaid:0, paymentMode:'Bank Transfer', notes:''};<br \/>\n  openDrawer(`<\/p>\n<div class=\"drawer-head\">\n<h3>${id?'Edit expense':'Add expense'}<\/h3>\n<p><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n<div class=\"drawer-body\">\n<div class=\"field-row\">\n<div class=\"field\"><label>Date<\/label><input id=\"e_date\" type=\"date\" value=\"${e.date}\"><\/div>\n<div class=\"field\"><label>Category<\/label><select id=\"e_category\">${EXPENSE_CATEGORIES.map(c=>`<option ${e.category===c?'selected':''}>${esc(c)}<\/option>`).join('')}<\/select><\/div>\n<\/p>\n<\/div>\n<div class=\"field\"><label>Vendor \/ Paid to<\/label><input id=\"e_vendor\" value=\"${esc(e.vendor)}\" placeholder=\"e.g. Hotel Taj, Landlord, Staff name\"><\/div>\n<div class=\"field\"><label>Description<\/label><input id=\"e_description\" value=\"${esc(e.description)}\" placeholder=\"What was this expense for?\"><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Amount (\u20b9)<\/label><input id=\"e_amount\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${e.amount||0}\"><\/div>\n<div class=\"field\"><label>GST paid on this (\u20b9, optional)<\/label><input id=\"e_gstpaid\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${e.gstPaid||0}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field\"><label>Payment mode<\/label><select id=\"e_mode\">${PAY_MODES.map(m=>`<option ${e.paymentMode===m?'selected':''}>${m}<\/option>`).join('')}<\/select><\/div>\n<div class=\"field\"><label>Notes (optional)<\/label><textarea id=\"e_notes\">${esc(e.notes||'')}<\/textarea><\/div>\n<div class=\"field-hint\">GST paid is for your own records (e.g. input tax credit tracking) \u2014 it isn't linked to your GST return filing automatically.<\/div>\n<\/p>\n<\/div>\n<div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button><br \/>\n      <button class=\"btn btn-primary\" onclick=\"saveExpenseForm('${id||''}')\">${ICONS.check} Save expense<\/button>\n    <\/div>\n<p>  `);<br \/>\n}<br \/>\nasync function saveExpenseForm(id){<br \/>\n  const amount = Number(document.getElementById('e_amount').value)||0;<br \/>\n  if(amount<=0){ toast('Enter a valid amount'); return; }\n  const data = {\n    date: document.getElementById('e_date').value || todayStr(),\n    category: document.getElementById('e_category').value,\n    vendor: document.getElementById('e_vendor').value.trim(),\n    description: document.getElementById('e_description').value.trim(),\n    amount,\n    gstPaid: Number(document.getElementById('e_gstpaid').value)||0,\n    paymentMode: document.getElementById('e_mode').value,\n    notes: document.getElementById('e_notes').value.trim()\n  };\n  if(id){\n    const idx = DB.expenses.findIndex(x=>x.id===id);<br \/>\n    DB.expenses[idx] = {...DB.expenses[idx], ...data};<br \/>\n  }else{<br \/>\n    DB.expenses.push({id:uid(), ...data});<br \/>\n  }<br \/>\n  await saveExpenses();<br \/>\n  closeDrawer();<br \/>\n  toast('Expense saved');<br \/>\n  route();<br \/>\n}<br \/>\nasync function deleteExpense(id){<br \/>\n  if(!confirm('Delete this expense?')) return;<br \/>\n  DB.expenses = DB.expenses.filter(e=>e.id!==id);<br \/>\n  await saveExpenses();<br \/>\n  route();<br \/>\n}<\/p>\n<p>\/* ---------- Dashboard ---------- *\/<br \/>\nfunction renderDashboard(){<br \/>\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button>`;<br \/>\n  const invs = DB.invoices;<br \/>\n  const thisMonth = monthKey(new Date());<br \/>\n  let totalInvoiced=0, totalReceived=0, totalOutstanding=0, gstThisMonth=0, invoicedThisMonth=0;<br \/>\n  invs.forEach(inv=>{<br \/>\n    const t = computeInvoiceTotals(inv);<br \/>\n    const paid = invoicePaid(inv);<br \/>\n    totalInvoiced += t.rounded;<br \/>\n    totalReceived += paid;<br \/>\n    totalOutstanding += Math.max(0, t.rounded-paid);<br \/>\n    if(monthKey(inv.date)===thisMonth){ gstThisMonth += (t.cgst+t.sgst+t.igst); invoicedThisMonth += t.rounded; }<br \/>\n  });<br \/>\n  const totalExpensesAllTime = totalExpensesFor(DB.expenses);<br \/>\n  const expensesThisMonth = totalExpensesFor(DB.expenses.filter(e=>monthKey(e.date)===thisMonth));<br \/>\n  const netProfitThisMonth = invoicedThisMonth - expensesThisMonth;<br \/>\n  const netProfitAllTime = totalInvoiced - totalExpensesAllTime;<br \/>\n  const recent = [...invs].sort((a,b)=> new Date(b.date)-new Date(a.date)).slice(0,6);<br \/>\n  document.getElementById('content').innerHTML = `<\/p>\n<div class=\"grid grid-4\">\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.invoice} Total invoiced<\/div>\n<div class=\"stat-value\">${fmtMoney(totalInvoiced)}<\/div>\n<div class=\"stat-note\">${invs.length} invoice${invs.length===1?'':'s'} all time<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.money} Received<\/div>\n<div class=\"stat-value\" style=\"color:var(--success)\">${fmtMoney(totalReceived)}<\/div>\n<div class=\"stat-note\">Across all invoices<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.clock} Outstanding<\/div>\n<div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(totalOutstanding)}<\/div>\n<div class=\"stat-note\">Yet to be collected<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.expense} Total expenses<\/div>\n<div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(totalExpensesAllTime)}<\/div>\n<div class=\"stat-note\">${DB.expenses.length} expense${DB.expenses.length===1?'':'s'} all time<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.receipt} GST collected \u2014 this month<\/div>\n<div class=\"stat-value\">${fmtMoney(gstThisMonth)}<\/div>\n<div class=\"stat-note\">${fmtMoney(invoicedThisMonth)} invoiced in ${new Date().toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/div>\n<\/div>\n<\/div>\n<div class=\"section-title\">This month's business<\/div>\n<div class=\"grid grid-3\">\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.trendUp} Revenue (invoiced)<\/div>\n<div class=\"stat-value\">${fmtMoney(invoicedThisMonth)}<\/div>\n<div class=\"stat-note\">${new Date().toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${ICONS.expense} Expenses<\/div>\n<div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(expensesThisMonth)}<\/div>\n<div class=\"stat-note\">${DB.expenses.filter(e=>monthKey(e.date)===thisMonth).length} expense${DB.expenses.filter(e=>monthKey(e.date)===thisMonth).length===1?'':'s'} logged<\/div>\n<\/div>\n<div class=\"card stat-card\">\n<div class=\"stat-label\">${netProfitThisMonth>=0?ICONS.trendUp:ICONS.trendDown} Net profit<\/div>\n<div class=\"stat-value\" style=\"color:${netProfitThisMonth>=0?'var(--success)':'var(--danger)'}\">${fmtMoney(netProfitThisMonth)}<\/div>\n<div class=\"stat-note\">All time net: ${fmtMoney(netProfitAllTime)}<\/div>\n<\/div>\n<\/div>\n<div class=\"section-title\">Recent invoices<\/div>\n<p>    ${recent.length ? `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Invoice<\/th>\n<th>Customer<\/th>\n<th>Date<\/th>\n<th class=\"right\">Amount<\/th>\n<th>Type<\/th>\n<th>Status<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody>${recent.map(inv=>{<br \/>\n        const t=computeInvoiceTotals(inv); const cust=customerById(inv.customerId);<br \/>\n        return `<\/p>\n<tr style=\"cursor:pointer\" onclick=\"navigate('invoices','${inv.id}')\">\n<td class=\"mono\">${esc(inv.number)}<\/td>\n<td>${esc(cust?cust.name:'\u2014')}<\/td>\n<td>${fmtDate(inv.date)}<\/td>\n<td class=\"right mono\">${fmtMoney(t.rounded)}<\/td>\n<td>${invoiceTypeBadge(inv)}<\/td>\n<td>${statusBadge(invoiceStatus(inv))}<\/td>\n<td class=\"right\"><button class=\"btn btn-sm\" onclick=\"event.stopPropagation();navigate('invoices','${inv.id}')\">Open<\/button><\/td>\n<\/tr>\n<p>`;<br \/>\n      }).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>`<br \/>\n      : `<\/p>\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No invoices yet<\/h3>\n<p>Create your first invoice to see it here.<\/p>\n<p><button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button><\/div>\n<p>`}<br \/>\n  `;<br \/>\n}<\/p>\n<p>\/* ---------- Invoices: list \/ router ---------- *\/<br \/>\nfunction renderInvoices(){<br \/>\n  if(VPARAM==='new'){ renderInvoiceEditor(null); return; }<br \/>\n  if(VPARAM){ renderInvoiceDetail(VPARAM); return; }<br \/>\n  renderInvoiceList();<br \/>\n}<br \/>\nlet INV_FILTER = 'all';<br \/>\nfunction renderInvoiceList(){<br \/>\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button>`;<br \/>\n  let list = [...DB.invoices].sort((a,b)=> new Date(b.date)-new Date(a.date));<br \/>\n  if(INV_FILTER!=='all') list = list.filter(inv=>invoiceStatus(inv)===INV_FILTER);<br \/>\n  const pills = ['all','Unpaid','Partial','Paid'];<br \/>\n  let body = `<\/p>\n<div class=\"filter-pills\" style=\"margin-bottom:16px;\">${pills.map(p=>`<button class=\"pill ${INV_FILTER===p?'active':''}\" onclick=\"INV_FILTER='${p}';route()\">${p==='all'?'All':p}<\/button>`).join('')}<\/div>\n<p>`;<br \/>\n  if(!list.length){<br \/>\n    body += `<\/p>\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No invoices${INV_FILTER!=='all'?' with status '+INV_FILTER:''}<\/h3>\n<p>Create an invoice for a tour package, flight, hotel or visa booking.<\/p>\n<p>      <button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button><\/div>\n<p>`;<br \/>\n  }else{<br \/>\n    body += `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Invoice<\/th>\n<th>Customer<\/th>\n<th>Date<\/th>\n<th>Place of supply<\/th>\n<th class=\"right\">Amount<\/th>\n<th>Type<\/th>\n<th>Status<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody>${list.map(inv=>{<br \/>\n        const t = computeInvoiceTotals(inv); const cust = customerById(inv.customerId);<br \/>\n        return `<\/p>\n<tr style=\"cursor:pointer\" onclick=\"navigate('invoices','${inv.id}')\">\n<td class=\"mono\">${esc(inv.number)}<\/td>\n<td>${esc(cust?cust.name:'\u2014')}<\/td>\n<td>${fmtDate(inv.date)}<\/td>\n<td>${esc(stateName(inv.customerStateCode))}<\/td>\n<td class=\"right mono\">${fmtMoney(t.rounded)}<\/td>\n<td>${invoiceTypeBadge(inv)}<\/td>\n<td>${statusBadge(invoiceStatus(inv))}<\/td>\n<td class=\"right\"><button class=\"btn btn-sm\" onclick=\"event.stopPropagation();navigate('invoices','${inv.id}')\">Open<\/button><\/td>\n<\/tr>\n<p>`;<br \/>\n      }).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>`;<br \/>\n  }<br \/>\n  document.getElementById('content').innerHTML = body;<br \/>\n}<\/p>\n<p>\/* ---------- Invoice Editor ---------- *\/<br \/>\nlet DRAFT = null;<br \/>\nfunction newLine(){ return {id:uid(), itemId:'', description:'', hsn:'', qty:1, unit:'unit', rate:0, discountPct:0, serviceCharge:0, gstRate:5}; }<br \/>\nfunction pad4(n){ return String(n).padStart(4,'0'); }<\/p>\n<p>function computeNextInvoiceNumber(type){<br \/>\n  const b = DB.business;<br \/>\n  const isBos = type === 'bos';<br \/>\n  const basePrefix = isBos ? (b.bosInvoicePrefix || ((b.invoicePrefix||'INV')+'-BOS')) : (b.invoicePrefix||'INV');<br \/>\n  const prefix = basePrefix + '-';<br \/>\n  let maxNum = 0;<br \/>\n  DB.invoices.forEach(inv=>{<br \/>\n    if(((inv.invoiceType==='bos') === isBos) && inv.number && inv.number.startsWith(prefix)){<br \/>\n      const suffix = inv.number.slice(prefix.length);<br \/>\n      const n = parseInt(suffix, 10);<br \/>\n      if(!isNaN(n) && n > maxNum) maxNum = n;<br \/>\n    }<br \/>\n  });<br \/>\n  const manual = Number(b && (isBos ? b.nextBosInvoiceNumber : b.nextInvoiceNumber)) || 0;<br \/>\n  return Math.max(maxNum + 1, manual);<br \/>\n}<br \/>\nfunction invoicePrefixForType(type){<br \/>\n  const b = DB.business;<br \/>\n  return type==='bos' ? (b.bosInvoicePrefix || ((b.invoicePrefix||'INV')+'-BOS')) : (b.invoicePrefix||'INV');<br \/>\n}<\/p>\n<p>function customerNameForDraft(){<br \/>\n  if(DRAFT.customerId){<br \/>\n    const c = customerById(DRAFT.customerId);<br \/>\n    return c ? c.name : (DRAFT.customerName||'');<br \/>\n  }<br \/>\n  return DRAFT.customerName||'';<br \/>\n}<\/p>\n<p>function renderInvoiceEditor(existingId){<br \/>\n  document.getElementById('topActions').innerHTML='';<br \/>\n  if(existingId){<br \/>\n    const inv = invoiceById(existingId);<br \/>\n    DRAFT = JSON.parse(JSON.stringify(inv));<br \/>\n    if(!DRAFT.invoiceType) DRAFT.invoiceType = 'tax';<br \/>\n  }else{<br \/>\n    const b = DB.business;<br \/>\n    const autoNumber = invoicePrefixForType('tax')+'-'+pad4(computeNextInvoiceNumber('tax'));<br \/>\n    DRAFT = {<br \/>\n      id: uid(), __fresh:true, __autoNumber: autoNumber,<br \/>\n      number: autoNumber,<br \/>\n      date: todayStr(), dueDate:'', customerId:'', customerName:'', customerStateCode:'',<br \/>\n      invoiceType:'tax', notes: b.terms||'', lines:[newLine()], payments:[]<br \/>\n    };<br \/>\n  }<br \/>\n  document.getElementById('content').innerHTML = `<\/p>\n<div style=\"display:grid;grid-template-columns:1fr 300px;gap:20px;align-items:start;\">\n<div>\n<div class=\"card\">\n<div class=\"section-title\" style=\"margin-top:0;\">Invoice details<\/div>\n<div class=\"field\">\n            <label>Invoice type<\/label><\/p>\n<div class=\"type-toggle\">\n              <button type=\"button\" id=\"typeBtnTax\" class=\"${DRAFT.invoiceType!=='bos'?'active':''}\" onclick=\"setDraftInvoiceType('tax')\">Tax Invoice (with GST)<\/button><br \/>\n              <button type=\"button\" id=\"typeBtnBos\" class=\"${DRAFT.invoiceType==='bos'?'active bos-active':''}\" onclick=\"setDraftInvoiceType('bos')\">Bill of Supply (Non-GST)<\/button>\n            <\/div>\n<div class=\"field-hint\" id=\"typeHint\">${DRAFT.invoiceType==='bos' ? 'No CGST\/SGST\/IGST will be charged or shown on this invoice.' : 'GST is calculated per line item as usual.'}<\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Invoice number<\/label><input id=\"d_number\" value=\"${esc(DRAFT.number)}\"><\/div>\n<div class=\"field\"><label>Customer<\/label><br \/>\n              <input id=\"d_customer\" list=\"customerListOptions\" autocomplete=\"off\" placeholder=\"Type a customer name\u2026\" value=\"${esc(customerNameForDraft())}\" oninput=\"draftCustomerChanged()\"><br \/>\n              <datalist id=\"customerListOptions\">${DB.customers.map(c=>`<option value=\"${esc(c.name)}\"><\/option>`).join('')}<\/datalist>\n            <\/div>\n<\/p>\n<\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Invoice date<\/label><input id=\"d_date\" type=\"date\" value=\"${DRAFT.date}\"><\/div>\n<div class=\"field\"><label>Due date<\/label><input id=\"d_due\" type=\"date\" value=\"${DRAFT.dueDate||''}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field-hint\">Start typing to see matching customers, or type a new name to create one when you save. <a href=\"#\" onclick=\"event.preventDefault();openCustomerForm()\">Add a customer with full details instead<\/a>.<\/div>\n<\/p>\n<\/div>\n<div class=\"section-title\">Line items<\/div>\n<div class=\"card\" style=\"padding:14px 16px;\">\n<table class=\"line-table\" id=\"lineTable\">\n<thead>\n<tr>\n<th style=\"width:20%\">Service<\/th>\n<th style=\"width:8%\">HSN\/SAC<\/th>\n<th style=\"width:6%\">Qty<\/th>\n<th style=\"width:9%\">Rate \u20b9<\/th>\n<th style=\"width:6%\">Disc %<\/th>\n<th style=\"width:11%\">Service Charge \u20b9<\/th>\n<th style=\"width:7%\">GST %<\/th>\n<th class=\"right\" style=\"width:11%\">Amount<\/th>\n<th><\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"lineBody\"><\/tbody>\n<\/table>\n<p>          <button class=\"btn btn-sm\" style=\"margin-top:10px\" onclick=\"addDraftLine()\">${ICONS.plus} Add line<\/button>\n        <\/div>\n<div class=\"section-title\">Notes \/ terms<\/div>\n<div class=\"card\"><textarea id=\"d_notes\" style=\"min-height:70px;border:none;padding:0;width:100%;\" placeholder=\"Payment terms, cancellation policy, etc.\">${esc(DRAFT.notes)}<\/textarea><\/div>\n<\/p>\n<\/div>\n<div style=\"position:sticky;top:80px;\">\n<div class=\"card\" id=\"totalsCard\"><\/div>\n<div style=\"display:flex;flex-direction:column;gap:8px;margin-top:14px;\">\n          <button class=\"btn btn-primary\" onclick=\"saveDraft()\">${ICONS.check} Save invoice<\/button><br \/>\n          <button class=\"btn\" onclick=\"navigate('invoices')\">Cancel<\/button>\n        <\/div>\n<\/p>\n<\/div>\n<\/div>\n<p>  `;<br \/>\n  renderLineBody();<br \/>\n  renderTotalsCard();<br \/>\n}<\/p>\n<p>function setDraftInvoiceType(type){<br \/>\n  DRAFT.invoiceType = type;<br \/>\n  const taxBtn = document.getElementById('typeBtnTax');<br \/>\n  const bosBtn = document.getElementById('typeBtnBos');<br \/>\n  taxBtn.className = type!=='bos' ? 'active' : '';<br \/>\n  bosBtn.className = type==='bos' ? 'active bos-active' : '';<br \/>\n  document.getElementById('typeHint').textContent = type==='bos'<br \/>\n    ? 'No CGST\/SGST\/IGST will be charged or shown on this invoice. This invoice will use the separate Bill of Supply numbering series.'<br \/>\n    : 'GST is calculated per line item as usual.';<br \/>\n  if(DRAFT.__fresh){<br \/>\n    const numberField = document.getElementById('d_number');<br \/>\n    const stillAuto = numberField && numberField.value.trim() === (DRAFT.__autoNumber||'');<br \/>\n    if(stillAuto){<br \/>\n      const newAuto = invoicePrefixForType(type)+'-'+pad4(computeNextInvoiceNumber(type));<br \/>\n      DRAFT.__autoNumber = newAuto;<br \/>\n      DRAFT.number = newAuto;<br \/>\n      if(numberField) numberField.value = newAuto;<br \/>\n    }<br \/>\n  }<br \/>\n  renderLineBody();<br \/>\n  renderTotalsCard();<br \/>\n}<\/p>\n<p>function draftCustomerChanged(){<br \/>\n  const val = document.getElementById('d_customer').value.trim();<br \/>\n  const match = DB.customers.find(c=>c.name.toLowerCase()===val.toLowerCase());<br \/>\n  if(match){<br \/>\n    DRAFT.customerId = match.id;<br \/>\n    DRAFT.customerStateCode = match.stateCode;<br \/>\n    DRAFT.customerName = '';<br \/>\n  }else{<br \/>\n    DRAFT.customerId = '';<br \/>\n    DRAFT.customerName = val;<br \/>\n    DRAFT.customerStateCode = '';<br \/>\n  }<br \/>\n  renderLineBody();<br \/>\n  renderTotalsCard();<br \/>\n}<\/p>\n<p>function renderLineBody(){<br \/>\n  const tbody = document.getElementById('lineBody');<br \/>\n  const nonGst = DRAFT.invoiceType === 'bos';<br \/>\n  tbody.innerHTML = DRAFT.lines.map((l,idx)=>`<\/p>\n<tr>\n<td>\n        <select onchange=\"pickPreset(${idx}, this.value)\"><option value=\"\">Custom\u2026<\/option>${DB.items.map(it=>`<option value=\"${it.id}\" ${l.itemId===it.id?'selected':''}>${esc(it.name)}<\/option>`).join('')}<br \/>\n        <\/select><br \/>\n        <input style=\"margin-top:5px\" placeholder=\"Description\" value=\"${esc(l.description)}\" oninput=\"updateLineField(${idx},'description',this.value,false)\">\n      <\/td>\n<td><input value=\"${esc(l.hsn)}\" oninput=\"updateLineField(${idx},'hsn',this.value,false)\"><\/td>\n<td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.qty}\" oninput=\"updateLineField(${idx},'qty',this.value,true)\"><\/td>\n<td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.rate}\" oninput=\"updateLineField(${idx},'rate',this.value,true)\"><\/td>\n<td><input type=\"number\" min=\"0\" max=\"100\" step=\"0.01\" value=\"${l.discountPct}\" oninput=\"updateLineField(${idx},'discountPct',this.value,true)\"><\/td>\n<td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.serviceCharge||0}\" oninput=\"updateLineField(${idx},'serviceCharge',this.value,true)\"><\/td>\n<td>${nonGst<br \/>\n          ? `<select disabled title=\"No GST on a Bill of Supply\"><option>\u2014<\/option><\/select>`<br \/>\n          : `<select onchange=\"updateLineField(${idx},'gstRate',this.value,true)\">${GST_RATES.map(r=>`<option value=\"${r}\" ${l.gstRate==r?'selected':''}>${r}%<\/option>`).join('')}<\/select>`}\n      <\/td>\n<td class=\"right mono\" id=\"lineAmt_${l.id}\">${fmtMoney(computeLine({...l,__intra:isSameState(),__nonGst:nonGst}).total)}<\/td>\n<td class=\"line-actions\"><button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"removeDraftLine(${idx})\" aria-label=\"Remove\">${ICONS.trash}<\/button><\/td>\n<\/tr>\n<p>  `).join('');<br \/>\n}<br \/>\nfunction isSameState(){ return DB.business && DRAFT.customerStateCode && DB.business.stateCode===DRAFT.customerStateCode; }<\/p>\n<p>function pickPreset(idx, itemId){<br \/>\n  const it = itemById(itemId);<br \/>\n  if(!it){ DRAFT.lines[idx].itemId=''; return; }<br \/>\n  DRAFT.lines[idx] = {...DRAFT.lines[idx], itemId, description:it.name, hsn:it.hsn, rate:it.rate, gstRate:it.gstRate, unit:it.unit};<br \/>\n  renderLineBody();<br \/>\n  renderTotalsCard();<br \/>\n}<br \/>\nfunction updateLineField(idx, field, value, numeric){<br \/>\n  DRAFT.lines[idx][field] = numeric ? Number(value)||0 : value;<br \/>\n  const l = DRAFT.lines[idx];<br \/>\n  const amt = document.getElementById('lineAmt_'+l.id);<br \/>\n  if(amt) amt.textContent = fmtMoney(computeLine({...l,__intra:isSameState(),__nonGst:DRAFT.invoiceType==='bos'}).total);<br \/>\n  renderTotalsCard();<br \/>\n}<br \/>\nfunction addDraftLine(){ DRAFT.lines.push(newLine()); renderLineBody(); renderTotalsCard(); }<br \/>\nfunction removeDraftLine(idx){<br \/>\n  if(DRAFT.lines.length===1){ toast('Invoice needs at least one line'); return; }<br \/>\n  DRAFT.lines.splice(idx,1); renderLineBody(); renderTotalsCard();<br \/>\n}<\/p>\n<p>function renderTotalsCard(){<br \/>\n  const t = computeInvoiceTotals(DRAFT);<br \/>\n  const intraLabel = DRAFT.customerStateCode ? (t.intra?'Intra-state (CGST + SGST)':'Inter-state (IGST)') : 'Select or type a customer';<br \/>\n  document.getElementById('totalsCard').innerHTML = `<\/p>\n<div class=\"section-title\" style=\"margin-top:0;\">Summary<\/div>\n<div class=\"ip-totals\" style=\"width:100%;margin:0;\">\n<div class=\"row\"><span>Base amount (non-taxable)<\/span><span class=\"mono\">${fmtMoney(t.baseTotal)}<\/span><\/div>\n<div class=\"row\"><span>Service charge${t.nonGst?'':' (taxable value)'}<\/span><span class=\"mono\">${fmtMoney(t.taxable)}<\/span><\/div>\n<p>      ${t.nonGst ? `<\/p>\n<div class=\"row\"><span>GST<\/span><span class=\"mono\">Not applicable<\/span><\/div>\n<p>      ` : (t.intra ? `<\/p>\n<div class=\"row\"><span>CGST<\/span><span class=\"mono\">${fmtMoney(t.cgst)}<\/span><\/div>\n<div class=\"row\"><span>SGST<\/span><span class=\"mono\">${fmtMoney(t.sgst)}<\/span><\/div>\n<p>      ` : `<\/p>\n<div class=\"row\"><span>IGST<\/span><span class=\"mono\">${fmtMoney(t.igst)}<\/span><\/div>\n<p>`)}<\/p>\n<div class=\"row\"><span>Round off<\/span><span class=\"mono\">${fmtMoney(t.roundOff)}<\/span><\/div>\n<div class=\"row grand\"><span>Total<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n<\/p>\n<\/div>\n<div class=\"field-hint\" style=\"margin-top:10px;\">${t.nonGst ? 'This is a Bill of Supply \u2014 no GST is charged.' : `GST is applied only on the service charge, not on the base amount. ${intraLabel}`}<\/div>\n<p>  `;<br \/>\n}<\/p>\n<p>async function saveDraft(){<br \/>\n  DRAFT.number = document.getElementById('d_number').value.trim();<br \/>\n  DRAFT.date = document.getElementById('d_date').value || todayStr();<br \/>\n  DRAFT.dueDate = document.getElementById('d_due').value || '';<br \/>\n  DRAFT.notes = document.getElementById('d_notes').value;<\/p>\n<p>  const typedName = document.getElementById('d_customer').value.trim();<br \/>\n  if(!DRAFT.customerId && typedName){<br \/>\n    let match = DB.customers.find(c=>c.name.toLowerCase()===typedName.toLowerCase());<br \/>\n    if(!match){<br \/>\n      match = {<br \/>\n        id: uid(), name: typedName, gstin:'',<br \/>\n        stateCode: DRAFT.customerStateCode || (DB.business?DB.business.stateCode:''),<br \/>\n        address:'', phone:'', email:''<br \/>\n      };<br \/>\n      DB.customers.push(match);<br \/>\n      await saveCustomers();<br \/>\n    }<br \/>\n    DRAFT.customerId = match.id;<br \/>\n    DRAFT.customerStateCode = match.stateCode;<br \/>\n  }<br \/>\n  delete DRAFT.customerName;<\/p>\n<p>  if(!DRAFT.customerId){ toast('Please type or select a customer'); return; }<br \/>\n  if(!DRAFT.number){ toast('Invoice number is required'); return; }<br \/>\n  const hasAmount = DRAFT.lines.some(l=> (Number(l.qty)||0)*(Number(l.rate)||0) > 0 || (Number(l.serviceCharge)||0) > 0);<br \/>\n  if(!hasAmount){ toast('Add at least one line item with an amount'); return; }<\/p>\n<p>  const isNew = DRAFT.__fresh === true;<br \/>\n  delete DRAFT.__fresh;<br \/>\n  if(isNew){<br \/>\n    DB.invoices.push(DRAFT);<br \/>\n  }else{<br \/>\n    const idx = DB.invoices.findIndex(i=>i.id===DRAFT.id);<br \/>\n    DB.invoices[idx] = DRAFT;<br \/>\n  }<br \/>\n  await saveInvoices();<br \/>\n  const savedId = DRAFT.id;<br \/>\n  DRAFT = null;<br \/>\n  toast('Invoice saved');<br \/>\n  navigate('invoices', savedId);<br \/>\n}<\/p>\n<p>\/* ---------- Invoice detail \/ preview ---------- *\/<br \/>\nfunction buildInvoicePreviewHTML(inv){<br \/>\n  const b = DB.business;<br \/>\n  const c = customerById(inv.customerId) || {name:'Customer', address:'', stateCode:'', gstin:''};<br \/>\n  const t = computeInvoiceTotals(inv);<br \/>\n  const docLabel = t.nonGst ? 'Bill of Supply' : 'Tax Invoice';<br \/>\n  return `<\/p>\n<div class=\"invoice-preview\">\n<div class=\"ip-head\">\n<div style=\"display:flex;align-items:center;gap:12px;\">\n        ${b.logo? `<\/p>\n<div style=\"width:44px;height:44px;border-radius:8px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;\"><img decoding=\"async\" src=\"${esc(b.logo)}\" style=\"max-width:100%;max-height:100%;object-fit:contain;\"><\/div>\n<p>`:''}<\/p>\n<div>\n<div class=\"ip-brand\">${esc(b.name)}<\/div>\n<div class=\"ip-brand-sub\">${t.nonGst ? '' : esc(b.gstin? 'GSTIN '+b.gstin : '')}<\/div>\n<div class=\"ip-brand-sub\">${esc([b.address,b.city,b.pincode].filter(Boolean).join(', '))}<\/div>\n<\/p>\n<\/div>\n<\/div>\n<div class=\"ip-invno\">\n<div class=\"ip-doctype\">${docLabel}<\/div>\n<div style=\"font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:#B9C4DE;margin-bottom:2px;\">Invoice No.<\/div>\n<div class=\"big\">${esc(inv.number)}<\/div>\n<div>Date ${fmtDate(inv.date)}<\/div>\n<p>        ${inv.dueDate?`<\/p>\n<div>Due ${fmtDate(inv.dueDate)}<\/div>\n<p>`:''}\n      <\/p>\n<\/div>\n<\/div>\n<div class=\"ip-route\">\n<div class=\"ip-party\">\n<div class=\"lbl\">Billed by<\/div>\n<div class=\"name\">${esc(b.name)}<\/div>\n<div class=\"meta\">${esc(b.state)} (${esc(b.stateCode)})${b.phone?' \u00b7 '+esc(b.phone):''}${b.email?' \u00b7 '+esc(b.email):''}<\/div>\n<\/p>\n<\/div>\n<div class=\"ip-party\" style=\"text-align:right;\">\n<div class=\"lbl\">Billed to<\/div>\n<div class=\"name\">${esc(c.name)}<\/div>\n<div class=\"meta\">${(!t.nonGst && c.gstin)? esc('GSTIN '+c.gstin)+'<br \/>' : ''}${esc(stateName(c.stateCode))}${c.stateCode? ' ('+esc(c.stateCode)+')':''}<\/div>\n<\/p>\n<\/div>\n<\/div>\n<div class=\"perforation\"><\/div>\n<p>    ${t.nonGst ? `<\/p>\n<div class=\"ip-bos-note\">This is a Bill of Supply. No GST is chargeable on this supply.<\/div>\n<p>` : ''}<\/p>\n<div class=\"ip-body\">\n<table>\n<thead>\n<tr>\n<th>Service<\/th>\n<th>HSN\/SAC<\/th>\n<th class=\"right\">Qty<\/th>\n<th class=\"right\">Rate<\/th>\n<th class=\"right\">Amount<\/th>\n<th class=\"right\">Service Charge<\/th>\n<p>${t.nonGst?'':'<\/p>\n<th class=\"right\">GST<\/th>\n<p>'}<\/p>\n<th class=\"right\">Line Total<\/th>\n<\/tr>\n<\/thead>\n<tbody>${t.lines.map(l=>`<\/p>\n<tr>\n<td>${esc(l.description||'Service')}<\/td>\n<td class=\"mono\">${esc(l.hsn)}<\/td>\n<td class=\"right mono\">${fmtNum(l.qty)}<\/td>\n<td class=\"right mono\">${fmtMoney(l.rate)}<\/td>\n<td class=\"right mono\">${fmtMoney(l.baseAfterDiscount)}<\/td>\n<td class=\"right mono\">${fmtMoney(l.serviceCharge)}<\/td>\n<p>          ${t.nonGst?'':`<\/p>\n<td class=\"right mono\">${l.gstRate}%<\/td>\n<p>`}<\/p>\n<td class=\"right mono\">${fmtMoney(l.total)}<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<div class=\"ip-totals\">\n<div class=\"row\"><span>Base amount (non-taxable)<\/span><span class=\"mono\">${fmtMoney(t.baseTotal)}<\/span><\/div>\n<div class=\"row\"><span>Service charge<\/span><span class=\"mono\">${fmtMoney(t.taxable)}<\/span><\/div>\n<p>        ${t.nonGst ? '' : (t.intra ? `<\/p>\n<div class=\"row\"><span>CGST<\/span><span class=\"mono\">${fmtMoney(t.cgst)}<\/span><\/div>\n<div class=\"row\"><span>SGST<\/span><span class=\"mono\">${fmtMoney(t.sgst)}<\/span><\/div>\n<p>        ` : `<\/p>\n<div class=\"row\"><span>IGST<\/span><span class=\"mono\">${fmtMoney(t.igst)}<\/span><\/div>\n<p>`)}<\/p>\n<div class=\"row\"><span>Round off<\/span><span class=\"mono\">${fmtMoney(t.roundOff)}<\/span><\/div>\n<div class=\"row grand\"><span>Total due<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n<\/p>\n<\/div>\n<\/div>\n<div class=\"ip-foot\">\n<div>\n<h4>Bank details<\/h4>\n<p>        ${b.bankName?`${esc(b.bankName)}<br \/>A\/C ${esc(b.bankAccount)}<br \/>IFSC ${esc(b.bankIFSC)}${b.upiId?'<br \/>UPI '+esc(b.upiId):''}`:'\u2014'}\n      <\/div>\n<div>\n<h4>Notes<\/h4>\n<p>        ${esc(inv.notes)||'\u2014'}\n      <\/p>\n<\/div>\n<\/div>\n<div class=\"ip-thankyou\">Thank you for your business!<\/div>\n<div class=\"ip-disclaimer\">\n<div style=\"display:flex;justify-content:flex-end;\">\n<div style=\"min-width:210px;text-align:center;font-size:11px;font-weight:700;color:var(--text-faint);\">This bill is auto generated and does<br \/>not require a physical signature.<\/div>\n<\/p>\n<\/div>\n<\/div>\n<div class=\"ip-signature\">\n<div style=\"text-align:center;min-width:210px;\">\n<div style=\"height:6px;\"><\/div>\n<div style=\"border-top:1px solid var(--text-mute);padding-top:6px;font-size:12.5px;font-weight:600;color:var(--ink);\">Authorized Signatory<\/div>\n<div style=\"font-size:10.5px;color:var(--text-faint);margin-top:3px;\">for ${esc(b.name)}<\/div>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>`;<br \/>\n}<\/p>\n<p>function renderInvoiceDetail(id){<br \/>\n  const inv = invoiceById(id);<br \/>\n  if(!inv){ navigate('invoices'); return; }<br \/>\n  const status = invoiceStatus(inv);<br \/>\n  const t = computeInvoiceTotals(inv);<br \/>\n  const paid = invoicePaid(inv);<br \/>\n  document.getElementById('topActions').innerHTML = `<\/p>\n<div style=\"display:flex;gap:8px;\">\n      <button class=\"btn\" onclick=\"navigate('invoices')\">Back<\/button><br \/>\n      <button class=\"btn\" onclick=\"editInvoice('${inv.id}')\">${ICONS.edit} Edit<\/button><br \/>\n      <button class=\"btn\" onclick=\"printInvoice('${inv.id}')\">${ICONS.print} Print \/ PDF<\/button><br \/>\n      <button class=\"btn btn-danger-ghost\" onclick=\"deleteInvoice('${inv.id}')\">${ICONS.trash} Delete<\/button>\n    <\/div>\n<p>`;<br \/>\n  document.getElementById('content').innerHTML = `<\/p>\n<div style=\"display:grid;grid-template-columns:1fr 300px;gap:20px;align-items:start;\">\n<div>${buildInvoicePreviewHTML(inv)}<\/div>\n<div>\n<div class=\"card\">\n<div class=\"section-title\" style=\"margin-top:0;\">Payment status<\/div>\n<div style=\"margin-bottom:10px;display:flex;gap:6px;flex-wrap:wrap;\">${invoiceTypeBadge(inv)}${statusBadge(status)}<\/div>\n<div class=\"ip-totals\" style=\"width:100%;margin:0;\">\n<div class=\"row\"><span>Invoice total<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n<div class=\"row\"><span>Received<\/span><span class=\"mono\">${fmtMoney(paid)}<\/span><\/div>\n<div class=\"row grand\"><span>Balance due<\/span><span class=\"mono\">${fmtMoney(Math.max(0,t.rounded-paid))}<\/span><\/div>\n<\/p>\n<\/div>\n<p>          ${status!=='Paid' ? `<button class=\"btn btn-amber\" style=\"width:100%;margin-top:12px;\" onclick=\"openPaymentForm('${inv.id}')\">${ICONS.money} Record payment<\/button>`:''}\n        <\/div>\n<p>        ${inv.payments && inv.payments.length ? `<\/p>\n<div class=\"section-title\">Payment history<\/div>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Date<\/th>\n<th>Mode<\/th>\n<th class=\"right\">Amount<\/th>\n<\/tr>\n<\/thead>\n<tbody>${inv.payments.map(p=>`<\/p>\n<tr>\n<td>${fmtDate(p.date)}<\/td>\n<td>${esc(p.mode)}<\/td>\n<td class=\"right mono\">${fmtMoney(p.amount)}<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>` : ''}\n      <\/p>\n<\/div>\n<\/div>\n<p>  `;<br \/>\n}<\/p>\n<p>function editInvoice(id){ DRAFT=null; navigate('invoices'); VIEW='invoices'; VPARAM='new'; renderShell(); renderInvoiceEditor(id); }<\/p>\n<p>async function deleteInvoice(id){<br \/>\n  if(!confirm('Delete this invoice? This cannot be undone.')) return;<br \/>\n  DB.invoices = DB.invoices.filter(i=>i.id!==id);<br \/>\n  await saveInvoices();<br \/>\n  toast('Invoice deleted');<br \/>\n  navigate('invoices');<br \/>\n}<\/p>\n<p>function openPaymentForm(invId){<br \/>\n  const inv = invoiceById(invId);<br \/>\n  const t = computeInvoiceTotals(inv);<br \/>\n  const balance = Math.max(0, t.rounded - invoicePaid(inv));<br \/>\n  openDrawer(`<\/p>\n<div class=\"drawer-head\">\n<h3>Record payment<\/h3>\n<p><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n<div class=\"drawer-body\">\n<div class=\"field-hint\" style=\"margin-bottom:12px;\">Balance due: <strong>${fmtMoney(balance)}<\/strong><\/div>\n<div class=\"field-row\">\n<div class=\"field\"><label>Amount received (\u20b9)<\/label><input id=\"p_amount\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${balance.toFixed(2)}\"><\/div>\n<div class=\"field\"><label>Date<\/label><input id=\"p_date\" type=\"date\" value=\"${todayStr()}\"><\/div>\n<\/p>\n<\/div>\n<div class=\"field\"><label>Mode<\/label><select id=\"p_mode\">${PAY_MODES.map(m=>`<option>${m}<\/option>`).join('')}<\/select><\/div>\n<div class=\"field\"><label>Note (optional)<\/label><input id=\"p_note\" placeholder=\"Reference \/ transaction ID\"><\/div>\n<\/p>\n<\/div>\n<div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button><br \/>\n      <button class=\"btn btn-primary\" onclick=\"savePayment('${invId}')\">${ICONS.check} Record payment<\/button>\n    <\/div>\n<p>  `);<br \/>\n}<br \/>\nasync function savePayment(invId){<br \/>\n  const amount = Number(document.getElementById('p_amount').value)||0;<br \/>\n  if(amount<=0){ toast('Enter a valid amount'); return; }\n  const inv = invoiceById(invId);\n  inv.payments = inv.payments || [];\n  inv.payments.push({ amount, date: document.getElementById('p_date').value||todayStr(), mode: document.getElementById('p_mode').value, note: document.getElementById('p_note').value });\n  await saveInvoices();\n  closeDrawer();\n  toast('Payment recorded');\n  navigate('invoices', invId);\n}\n\nfunction printInvoice(id){\n  const inv = invoiceById(id);\n  document.getElementById('printArea').innerHTML = buildInvoicePreviewHTML(inv);\n  window.print();\n}\n\n\/* ---------- GST Reports ---------- *\/\nfunction renderReports(){\n  document.getElementById('topActions').innerHTML = '';\n  if(!DB.invoices.length &#038;&#038; !DB.expenses.length){\n    document.getElementById('content').innerHTML = `\n\n\n\n<div class=\"empty-state card\">${ICONS.empty}<\/p>\n<h3>No data to report yet<\/h3>\n<p>Reports populate once you have invoices or expenses.<\/p>\n<\/div>\n<p>`;<br \/>\n    return;<br \/>\n  }<br \/>\n  const byMonth = {};<br \/>\n  DB.invoices.forEach(inv=>{<br \/>\n    const k = monthKey(inv.date);<br \/>\n    const t = computeInvoiceTotals(inv);<br \/>\n    if(!byMonth[k]) byMonth[k] = {taxable:0,cgst:0,sgst:0,igst:0,total:0,count:0,bosCount:0,expenses:0};<br \/>\n    byMonth[k].taxable += t.taxable; byMonth[k].cgst += t.cgst; byMonth[k].sgst += t.sgst; byMonth[k].igst += t.igst; byMonth[k].total += t.rounded; byMonth[k].count++;<br \/>\n    if(t.nonGst) byMonth[k].bosCount++;<br \/>\n  });<br \/>\n  DB.expenses.forEach(e=>{<br \/>\n    const k = monthKey(e.date);<br \/>\n    if(!byMonth[k]) byMonth[k] = {taxable:0,cgst:0,sgst:0,igst:0,total:0,count:0,bosCount:0,expenses:0};<br \/>\n    byMonth[k].expenses += Number(e.amount)||0;<br \/>\n  });<br \/>\n  const months = Object.keys(byMonth).sort().reverse();<br \/>\n  const gstMonths = months.filter(m=>byMonth[m].count>0);<\/p>\n<p>  const byExpCat = {};<br \/>\n  DB.expenses.forEach(e=>{<br \/>\n    const cat = e.category||'Other';<br \/>\n    if(!byExpCat[cat]) byExpCat[cat] = 0;<br \/>\n    byExpCat[cat] += Number(e.amount)||0;<br \/>\n  });<br \/>\n  const expCats = Object.keys(byExpCat).sort((a,b)=>byExpCat[b]-byExpCat[a]);<br \/>\n  const totalExpAll = totalExpensesFor(DB.expenses);<\/p>\n<p>  const byRate = {};<br \/>\n  DB.invoices.forEach(inv=>{<br \/>\n    const t = computeInvoiceTotals(inv);<br \/>\n    t.lines.forEach(l=>{<br \/>\n      const r = t.nonGst ? 'Non-GST' : l.gstRate;<br \/>\n      if(!byRate[r]) byRate[r] = {taxable:0,tax:0};<br \/>\n      byRate[r].taxable += l.taxable; byRate[r].tax += (l.cgst+l.sgst+l.igst);<br \/>\n    });<br \/>\n  });<br \/>\n  const rates = Object.keys(byRate).sort((a,b)=>{<br \/>\n    if(a==='Non-GST') return 1;<br \/>\n    if(b==='Non-GST') return -1;<br \/>\n    return a-b;<br \/>\n  });<\/p>\n<p>  const byCust = {};<br \/>\n  DB.invoices.forEach(inv=>{<br \/>\n    const t = computeInvoiceTotals(inv);<br \/>\n    const bal = Math.max(0, t.rounded - invoicePaid(inv));<br \/>\n    if(bal<=0) return;\n    const c = customerById(inv.customerId);\n    const key = c?c.id:'unknown';\n    if(!byCust[key]) byCust[key] = {name:c?c.name:'Unknown', balance:0, count:0};\n    byCust[key].balance += bal; byCust[key].count++;\n  });\n  const custRows = Object.values(byCust).sort((a,b)=>b.balance-a.balance);<\/p>\n<p>  document.getElementById('content').innerHTML = `<\/p>\n<div class=\"section-title\" style=\"margin-top:0;\">Monthly GST summary<\/div>\n<div class=\"field-hint\" style=\"margin-bottom:10px;\">Bill of Supply (non-GST) invoices are included in invoice counts but contribute \u20b90 tax.<\/div>\n<p>    ${gstMonths.length ? `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Month<\/th>\n<th class=\"right\">Invoices<\/th>\n<th class=\"right\">Non-GST<\/th>\n<th class=\"right\">Taxable value<\/th>\n<th class=\"right\">CGST<\/th>\n<th class=\"right\">SGST<\/th>\n<th class=\"right\">IGST<\/th>\n<th class=\"right\">Total tax<\/th>\n<\/tr>\n<\/thead>\n<tbody>${gstMonths.map(m=>{const r=byMonth[m];return `<\/p>\n<tr>\n<td>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/td>\n<td class=\"right\">${r.count}<\/td>\n<td class=\"right\">${r.bosCount||0}<\/td>\n<td class=\"right mono\">${fmtMoney(r.taxable)}<\/td>\n<td class=\"right mono\">${fmtMoney(r.cgst)}<\/td>\n<td class=\"right mono\">${fmtMoney(r.sgst)}<\/td>\n<td class=\"right mono\">${fmtMoney(r.igst)}<\/td>\n<td class=\"right mono\">${fmtMoney(r.cgst+r.sgst+r.igst)}<\/td>\n<\/tr>\n<p>`;}).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>` : `<\/p>\n<div class=\"card\" style=\"color:var(--text-mute)\">No invoices yet.<\/div>\n<p>`}<\/p>\n<div class=\"section-title\">Profit &amp; Loss by month<\/div>\n<div class=\"field-hint\" style=\"margin-bottom:10px;\">Revenue is invoiced amount (including GST); Net profit = Revenue \u2212 Expenses.<\/div>\n<p>    ${months.length ? `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Month<\/th>\n<th class=\"right\">Revenue (invoiced)<\/th>\n<th class=\"right\">Expenses<\/th>\n<th class=\"right\">Net profit<\/th>\n<\/tr>\n<\/thead>\n<tbody>${months.map(m=>{const r=byMonth[m]; const net=r.total-r.expenses; return `<\/p>\n<tr>\n<td>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/td>\n<td class=\"right mono\">${fmtMoney(r.total)}<\/td>\n<td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(r.expenses)}<\/td>\n<td class=\"right mono\" style=\"color:${net>=0?'var(--success)':'var(--danger)'}\">${fmtMoney(net)}<\/td>\n<\/tr>\n<p>`;}).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>` : `<\/p>\n<div class=\"card\" style=\"color:var(--text-mute)\">No data yet.<\/div>\n<p>`}<\/p>\n<div class=\"section-title\">Expenses by category<\/div>\n<p>    ${expCats.length ? `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Category<\/th>\n<th class=\"right\">Amount<\/th>\n<th class=\"right\">% of total<\/th>\n<\/tr>\n<\/thead>\n<tbody>${expCats.map(c=>`<\/p>\n<tr>\n<td>${esc(c)}<\/td>\n<td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(byExpCat[c])}<\/td>\n<td class=\"right mono\">${totalExpAll>0?((byExpCat[c]\/totalExpAll)*100).toFixed(1):'0.0'}%<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>` : `<\/p>\n<div class=\"card\" style=\"color:var(--text-mute)\">No expenses logged yet.<\/div>\n<p>`}<\/p>\n<div class=\"section-title\">Tax rate-wise summary<\/div>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>GST rate<\/th>\n<th class=\"right\">Taxable value<\/th>\n<th class=\"right\">Tax amount<\/th>\n<\/tr>\n<\/thead>\n<tbody>${rates.map(r=>`<\/p>\n<tr>\n<td>${r==='Non-GST'?'Non-GST (Bill of Supply)':r+'%'}<\/td>\n<td class=\"right mono\">${fmtMoney(byRate[r].taxable)}<\/td>\n<td class=\"right mono\">${fmtMoney(byRate[r].tax)}<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<div class=\"section-title\">Outstanding by customer<\/div>\n<p>    ${custRows.length ? `<\/p>\n<div class=\"table-wrap\">\n<table>\n<thead>\n<tr>\n<th>Customer<\/th>\n<th class=\"right\">Unpaid invoices<\/th>\n<th class=\"right\">Balance due<\/th>\n<\/tr>\n<\/thead>\n<tbody>${custRows.map(r=>`<\/p>\n<tr>\n<td>${esc(r.name)}<\/td>\n<td class=\"right\">${r.count}<\/td>\n<td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(r.balance)}<\/td>\n<\/tr>\n<p>`).join('')}<\/tbody>\n<\/table>\n<\/div>\n<p>` : `<\/p>\n<div class=\"card\" style=\"color:var(--text-mute)\">Nothing outstanding \u2014 all invoices are fully paid.<\/div>\n<p>`}<br \/>\n  `;<br \/>\n}<\/p>\n<p>\/* =====================================================================<br \/>\n   AUTH \u2014 simple in-app username\/password gate<br \/>\n   ===================================================================== *\/<br \/>\nlet AUTH = { username:'', unlocked:false };<\/p>\n<p>async function sha256Hex(str){<br \/>\n  const enc = new TextEncoder().encode(str);<br \/>\n  const buf = await crypto.subtle.digest('SHA-256', enc);<br \/>\n  return Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join('');<br \/>\n}<br \/>\nfunction randomSalt(){<br \/>\n  const arr = new Uint8Array(16);<br \/>\n  crypto.getRandomValues(arr);<br \/>\n  return Array.from(arr).map(b=>b.toString(16).padStart(2,'0')).join('');<br \/>\n}<br \/>\nasync function getAuthDoc(){ return await safeGet('auth', null); }<br \/>\nasync function setAuthDoc(doc){ return await saveKey('auth', doc); }<\/p>\n<p>let AUTH_LOGO = DEFAULT_LOGO;<br \/>\nlet AUTH_BIZNAME = 'Sarvalok Bhraman';<br \/>\nasync function loadAuthBranding(){<br \/>\n  try{<br \/>\n    const biz = await safeGet('business', null);<br \/>\n    if(biz){<br \/>\n      if(biz.logo) AUTH_LOGO = biz.logo;<br \/>\n      if(biz.name) AUTH_BIZNAME = biz.name;<br \/>\n    }<br \/>\n  }catch(e){ \/* fall back to defaults silently *\/ }<br \/>\n}<\/p>\n<p>function authShell(innerHTML){<br \/>\n  document.getElementById('app').style.display='none';<br \/>\n  document.getElementById('authScreen').style.display='flex';<br \/>\n  document.getElementById('authScreen').innerHTML = `<\/p>\n<div class=\"auth-card\">\n<div class=\"auth-brand\">\n        <img decoding=\"async\" src=\"${esc(AUTH_LOGO)}\" alt=\"\"><\/p>\n<div>\n<div class=\"t1\">${esc(AUTH_BIZNAME)}<\/div>\n<div class=\"t2\">GST Billing \u2014 secure sign in<\/div>\n<\/p>\n<\/div>\n<\/div>\n<p>      ${innerHTML}\n    <\/p>\n<\/div>\n<p>  `;<br \/>\n}<\/p>\n<p>function renderAuthSetup(){<br \/>\n  authShell(`<\/p>\n<div class=\"auth-title\">Create your login<\/div>\n<div class=\"auth-sub\">No login exists yet for this app. Set a username and password \u2014 you'll need them every time you open this page.<\/div>\n<div id=\"authError\" class=\"auth-error\"><\/div>\n<div class=\"field\"><label>Username<\/label><input id=\"su_username\" placeholder=\"e.g. admin\" autofocus><\/div>\n<div class=\"field\"><label>Password<\/label><input id=\"su_password\" type=\"password\" placeholder=\"Choose a password\"><\/div>\n<div class=\"field\"><label>Confirm password<\/label><input id=\"su_password2\" type=\"password\"><\/div>\n<p>    <button class=\"btn btn-primary\" style=\"width:100%;justify-content:center;\" onclick=\"submitAuthSetup()\">${ICONS.lock} Create login & continue<\/button><br \/>\n  `);<br \/>\n  document.getElementById('su_password2').addEventListener('keydown', e=>{ if(e.key==='Enter') submitAuthSetup(); });<br \/>\n}<\/p>\n<p>async function submitAuthSetup(){<br \/>\n  const username = document.getElementById('su_username').value.trim();<br \/>\n  const pw = document.getElementById('su_password').value;<br \/>\n  const pw2 = document.getElementById('su_password2').value;<br \/>\n  const err = document.getElementById('authError');<br \/>\n  err.classList.remove('show');<br \/>\n  if(!username){ err.textContent='Username is required.'; err.classList.add('show'); return; }<br \/>\n  if(pw.length < 4){ err.textContent='Password must be at least 4 characters.'; err.classList.add('show'); return; }\n  if(pw !== pw2){ err.textContent='Passwords do not match.'; err.classList.add('show'); return; }\n  const salt = randomSalt();\n  const hash = await sha256Hex(salt + pw);\n  const ok = await setAuthDoc({ username, salt, hash });\n  if(!ok){ err.textContent='Could not save login \u2014 check your connection and try again.'; err.classList.add('show'); return; }\n  AUTH = { username, unlocked:true };\n  toast('Login created');\n  unlockApp();\n}\n\nfunction renderAuthLogin(existingUsername){\n  authShell(`\n    \n\n\n\n<div class=\"auth-title\">Sign in<\/div>\n<div class=\"auth-sub\">Enter your username and password to open the billing app.<\/div>\n<div id=\"authError\" class=\"auth-error\"><\/div>\n<div class=\"field\"><label>Username<\/label><input id=\"li_username\" value=\"${esc(existingUsername||'')}\" autofocus><\/div>\n<div class=\"field\"><label>Password<\/label><input id=\"li_password\" type=\"password\"><\/div>\n<p>    <button class=\"btn btn-primary\" style=\"width:100%;justify-content:center;\" onclick=\"submitAuthLogin()\">${ICONS.lock} Sign in<\/button><br \/>\n  `);<br \/>\n  document.getElementById('li_password').addEventListener('keydown', e=>{ if(e.key==='Enter') submitAuthLogin(); });<br \/>\n}<\/p>\n<p>async function submitAuthLogin(){<br \/>\n  const username = document.getElementById('li_username').value.trim();<br \/>\n  const pw = document.getElementById('li_password').value;<br \/>\n  const err = document.getElementById('authError');<br \/>\n  err.classList.remove('show');<br \/>\n  const doc = await getAuthDoc();<br \/>\n  if(!doc){ err.textContent='No login found \u2014 please refresh and set one up.'; err.classList.add('show'); return; }<br \/>\n  if(username.toLowerCase() !== (doc.username||'').toLowerCase()){<br \/>\n    err.textContent='Incorrect username or password.'; err.classList.add('show'); return;<br \/>\n  }<br \/>\n  const hash = await sha256Hex(doc.salt + pw);<br \/>\n  if(hash !== doc.hash){<br \/>\n    err.textContent='Incorrect username or password.'; err.classList.add('show'); return;<br \/>\n  }<br \/>\n  AUTH = { username: doc.username, unlocked:true };<br \/>\n  unlockApp();<br \/>\n}<\/p>\n<p>function logout(){<br \/>\n  window.location.reload();<br \/>\n}<\/p>\n<p>async function changeCredentials(){<br \/>\n  const msg = document.getElementById('pwChangeMsg');<br \/>\n  msg.classList.remove('show');<br \/>\n  const newUsername = document.getElementById('pw_username').value.trim();<br \/>\n  const current = document.getElementById('pw_current').value;<br \/>\n  const newPw = document.getElementById('pw_new').value;<br \/>\n  const confirmPw = document.getElementById('pw_confirm').value;<br \/>\n  if(!newUsername){ msg.textContent='Username cannot be blank.'; msg.classList.add('show'); return; }<br \/>\n  if(!current){ msg.textContent='Enter your current password to confirm changes.'; msg.classList.add('show'); return; }<br \/>\n  const doc = await getAuthDoc();<br \/>\n  if(!doc){ msg.textContent='No login record found.'; msg.classList.add('show'); return; }<br \/>\n  const currentHash = await sha256Hex(doc.salt + current);<br \/>\n  if(currentHash !== doc.hash){ msg.textContent='Current password is incorrect.'; msg.classList.add('show'); return; }<br \/>\n  if(newPw || confirmPw){<br \/>\n    if(newPw.length < 4){ msg.textContent='New password must be at least 4 characters.'; msg.classList.add('show'); return; }\n    if(newPw !== confirmPw){ msg.textContent='New passwords do not match.'; msg.classList.add('show'); return; }\n  }\n  const salt = randomSalt();\n  const finalPw = newPw || current;\n  const hash = await sha256Hex(salt + finalPw);\n  const ok = await setAuthDoc({ username: newUsername, salt, hash });\n  if(!ok){ msg.textContent='Could not save \u2014 check your connection and try again.'; msg.classList.add('show'); return; }\n  AUTH.username = newUsername;\n  document.getElementById('pw_current').value='';\n  document.getElementById('pw_new').value='';\n  document.getElementById('pw_confirm').value='';\n  toast('Login details updated');\n}\n\nasync function unlockApp(){\n  document.getElementById('authScreen').style.display='none';\n  document.getElementById('app').style.display='flex';\n  await loadAll();\n  renderShell();\n  route();\n}\n\n\/* ---------- Init ---------- *\/\nasync function init(){\n  await loadAuthBranding();\n  const doc = await getAuthDoc();\n  if(!doc){\n    renderAuthSetup();\n  }else{\n    renderAuthLogin(doc.username);\n  }\n}\ninit();\n\n<\/script><\/p>\n\n\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin=\"\">\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Space+Grotesk:wght@500;600;700&amp;family=Inter:wght@400;500;600&amp;family=IBM+Plex+Mono:wght@400;500;600&amp;display=swap\" rel=\"stylesheet\">\n<style>\n:root{\n  --ink:#16233F;\n  --ink-soft:#233559;\n  --ink-softer:#324875;\n  --paper:#F3F5F4;\n  --surface:#FFFFFF;\n  --amber:#E8A33D;\n  --amber-dark:#C67F1E;\n  --success:#1E7A46;\n  --success-bg:#E5F3EA;\n  --danger:#B7412F;\n  --danger-bg:#FBEAE6;\n  --warn:#8A5A0B;\n  --warn-bg:#FBF0DC;\n  --text:#1C2333;\n  --text-mute:#68707F;\n  --text-faint:#9AA1AC;\n  --border:#E2E5E4;\n  --border-soft:#EDEFEE;\n  --radius:10px;\n  --shadow:0 1px 2px rgba(22,35,63,0.06), 0 4px 14px rgba(22,35,63,0.05);\n  font-size:15px;\n}\n.slb-app-root *{box-sizing:border-box;}\n.slb-app-root{\n  font-family:'Inter',system-ui,sans-serif;\n  background:var(--paper);\n  color:var(--text);\n  -webkit-font-smoothing:antialiased;\n  margin:0;padding:0;\n}\nh1,h2,h3,h4{font-family:'Space Grotesk',sans-serif;font-weight:600;margin:0;color:var(--ink);}\n.mono{font-family:'IBM Plex Mono',monospace;}\nbutton{font-family:inherit;cursor:pointer;}\ninput,select,textarea{font-family:inherit;font-size:14px;}\na{color:inherit;}\n\n#app{display:flex;min-height:100vh;}\n\n\/* Sidebar *\/\n#sidebar{\n  width:224px;flex-shrink:0;background:var(--ink);color:#DDE3F0;\n  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;\n}\n.brand{\n  padding:22px 20px 18px;display:flex;align-items:center;gap:10px;\n  border-bottom:1px solid rgba(255,255,255,0.08);\n}\n.brand-mark{\n  width:34px;height:34px;border-radius:8px;background:var(--amber);\n  display:flex;align-items:center;justify-content:center;flex-shrink:0;\n  overflow:hidden;\n}\n.brand-mark svg{width:19px;height:19px;}\n.brand-mark img{width:100%;height:100%;object-fit:contain;border-radius:8px;background:#fff;}\n.brand-name{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:17px;color:#fff;line-height:1.1;}\n.brand-sub{font-size:10.5px;color:#9FB0D4;letter-spacing:.04em;text-transform:uppercase;margin-top:2px;}\nnav.navlist{padding:14px 12px;display:flex;flex-direction:column;gap:2px;flex:1;}\n.navitem{\n  display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;\n  color:#B9C4DE;font-size:13.5px;font-weight:500;background:transparent;border:none;text-align:left;width:100%;\n  transition:background .12s, color .12s;\n}\n.navitem svg{width:17px;height:17px;flex-shrink:0;opacity:.85;}\n.navitem:hover{background:rgba(255,255,255,0.06);color:#fff;}\n.navitem.active{background:rgba(232,163,61,0.16);color:#fff;}\n.navitem.active svg{opacity:1;color:var(--amber);}\n.nav-divider{height:1px;background:rgba(255,255,255,0.08);margin:10px 6px;}\n.sidebar-foot{padding:14px 20px 18px;border-top:1px solid rgba(255,255,255,0.08);font-size:11.5px;color:#7C8AAE;}\n\n\/* Main *\/\n#main{flex:1;min-width:0;display:flex;flex-direction:column;}\n.topbar{\n  padding:16px 28px;display:flex;align-items:center;justify-content:space-between;\n  border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:5;\n}\n.topbar h1{font-size:19px;}\n.topbar .sub{font-size:12.5px;color:var(--text-mute);margin-top:2px;font-weight:400;}\n.content{padding:24px 28px 60px;flex:1;}\n\n.btn{\n  display:inline-flex;align-items:center;gap:6px;padding:9px 15px;border-radius:8px;\n  border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13.5px;font-weight:500;\n  transition:border-color .12s, background .12s;\n}\n.btn svg{width:15px;height:15px;}\n.btn:hover{border-color:#C7CCD2;background:#FAFAFA;}\n.btn-primary{background:var(--ink);color:#fff;border-color:var(--ink);}\n.btn-primary:hover{background:var(--ink-soft);border-color:var(--ink-soft);}\n.btn-amber{background:var(--amber);color:#3B2606;border-color:var(--amber);}\n.btn-amber:hover{background:var(--amber-dark);border-color:var(--amber-dark);}\n.btn-danger-ghost{color:var(--danger);border-color:var(--border);}\n.btn-danger-ghost:hover{background:var(--danger-bg);border-color:var(--danger);}\n.btn-sm{padding:6px 10px;font-size:12.5px;}\n.btn-icon{padding:7px;}\n\n\/* Cards *\/\n.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;}\n.grid{display:grid;gap:14px;}\n.grid-4{grid-template-columns:repeat(4,1fr);}\n.grid-3{grid-template-columns:repeat(3,1fr);}\n.grid-2{grid-template-columns:repeat(2,1fr);}\n@media (max-width:1000px){.grid-4{grid-template-columns:repeat(2,1fr);}}\n\n.stat-card{padding:16px 18px;}\n.stat-label{font-size:12px;color:var(--text-mute);font-weight:500;display:flex;align-items:center;gap:6px;}\n.stat-value{font-family:'IBM Plex Mono',monospace;font-size:24px;font-weight:600;color:var(--ink);margin-top:8px;}\n.stat-note{font-size:11.5px;color:var(--text-faint);margin-top:4px;}\n\ntable{width:100%;border-collapse:collapse;font-size:13.5px;}\nth{\n  text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);\n  font-weight:600;padding:10px 12px;border-bottom:1px solid var(--border);background:#FAFBFA;\n}\ntd{padding:11px 12px;border-bottom:1px solid var(--border-soft);vertical-align:middle;}\ntr:last-child td{border-bottom:none;}\ntbody tr:hover{background:#FAFBFA;}\n.table-wrap{border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface);}\n.right{text-align:right;}\n.center{text-align:center;}\n\n.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:600;}\n.badge-paid{background:var(--success-bg);color:var(--success);}\n.badge-unpaid{background:var(--danger-bg);color:var(--danger);}\n.badge-partial{background:var(--warn-bg);color:var(--warn);}\n.badge-bos{background:#EFEAF7;color:#5A3E9E;}\n.badge-tax{background:#E7EEF9;color:#26497A;}\n\n.empty-state{padding:56px 20px;text-align:center;color:var(--text-mute);}\n.empty-state svg{width:38px;height:38px;color:var(--text-faint);margin-bottom:10px;}\n.empty-state h3{color:var(--text);font-size:15px;margin-bottom:4px;}\n.empty-state p{font-size:13px;margin:0 0 16px;}\n\n\/* Drawer \/ forms *\/\n#overlay{\n  position:fixed;inset:0;background:rgba(22,26,38,0.32);display:none;z-index:40;\n}\n#drawer{\n  position:fixed;top:0;right:0;height:100vh;width:560px;max-width:94vw;background:var(--surface);\n  box-shadow:-8px 0 32px rgba(0,0,0,0.12);z-index:41;display:flex;flex-direction:column;\n  transform:translateX(100%);transition:transform .2s ease;\n}\n#drawer.open, #overlay.open{display:block;}\n#drawer.open{transform:translateX(0);}\n.drawer-head{padding:18px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}\n.drawer-body{padding:20px 24px;overflow-y:auto;flex:1;}\n.drawer-foot{padding:16px 24px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:8px;}\n\n.field{margin-bottom:14px;}\n.field label{display:block;font-size:12.5px;font-weight:500;color:var(--text-mute);margin-bottom:5px;}\n.field input, .field select, .field textarea{\n  width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:8px;background:#fff;color:var(--text);\n}\n.field input:focus, .field select:focus, .field textarea:focus{outline:none;border-color:var(--ink-softer);box-shadow:0 0 0 3px rgba(50,72,117,0.1);}\n.field textarea{resize:vertical;min-height:60px;}\n.field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;}\n.field-hint{font-size:11.5px;color:var(--text-faint);margin-top:4px;}\n\n\/* Invoice type toggle *\/\n.type-toggle{display:flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;}\n.type-toggle button{\n  flex:1;padding:9px 10px;font-size:12.5px;font-weight:600;background:#fff;border:none;color:var(--text-mute);\n  border-right:1px solid var(--border);transition:background .12s,color .12s;\n}\n.type-toggle button:last-child{border-right:none;}\n.type-toggle button.active{background:var(--ink);color:#fff;}\n.type-toggle button.active.bos-active{background:#5A3E9E;color:#fff;}\n\n\/* Invoice line item builder *\/\n.line-table th, .line-table td{padding:8px;font-size:13px;}\n.line-table input, .line-table select{padding:6px 7px;font-size:12.5px;}\n.line-actions{display:flex;justify-content:flex-end;}\n\n.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:18px;}\n.tab{padding:9px 14px;font-size:13px;font-weight:500;color:var(--text-mute);border-bottom:2px solid transparent;background:none;border-top:none;border-left:none;border-right:none;}\n.tab.active{color:var(--ink);border-bottom-color:var(--amber);}\n\n.toast{\n  position:fixed;bottom:22px;right:22px;background:var(--ink);color:#fff;padding:12px 18px;border-radius:9px;\n  font-size:13px;box-shadow:var(--shadow);z-index:80;display:flex;align-items:center;gap:8px;\n  opacity:0;transform:translateY(8px);transition:opacity .18s, transform .18s;pointer-events:none;\n}\n.toast.show{opacity:1;transform:translateY(0);}\n\n\/* Invoice preview \/ print styling \u2014 boarding pass motif *\/\n.invoice-preview{background:#fff;border:1px solid var(--border);border-radius:12px;overflow:hidden;max-width:820px;}\n.ip-head{background:var(--ink);color:#fff;padding:22px 26px;display:flex;justify-content:space-between;align-items:flex-start;}\n.ip-brand{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:18px;}\n.ip-brand-sub{font-size:11.5px;color:#B9C4DE;margin-top:2px;}\n.ip-invno{font-family:'IBM Plex Mono',monospace;text-align:right;font-size:13px;}\n.ip-invno .big{font-size:16px;font-weight:600;color:var(--amber);}\n.ip-doctype{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:#B9C4DE;margin-bottom:2px;}\n.ip-route{\n  display:grid;grid-template-columns:1fr 1fr;align-items:center;padding:20px 26px;gap:14px;\n}\n.ip-party .lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-mute);font-weight:600;}\n.ip-party .name{font-size:15px;font-weight:600;color:var(--ink);margin-top:4px;}\n.ip-party .meta{font-size:12px;color:var(--text-mute);margin-top:3px;line-height:1.5;}\n.ip-plane{color:var(--amber);}\n.ip-plane svg{width:22px;height:22px;}\n.ip-plane img{width:32px;height:32px;object-fit:contain;display:block;}\n.perforation{\n  position:relative;height:0;border-top:1px dashed var(--border);margin:0 26px;\n}\n.perforation::before,.perforation::after{\n  content:\"\";position:absolute;top:-9px;width:18px;height:18px;border-radius:50%;background:var(--paper);border:1px solid var(--border);\n}\n.perforation::before{left:-35px;}\n.perforation::after{right:-35px;}\n.ip-body{padding:22px 26px;}\n.ip-totals{width:280px;margin-left:auto;margin-top:10px;font-size:13px;}\n.ip-totals .row{display:flex;justify-content:space-between;padding:5px 0;color:var(--text-mute);}\n.ip-totals .row.grand{color:var(--ink);font-weight:700;font-size:16px;border-top:1px solid var(--border);margin-top:6px;padding-top:10px;}\n.ip-bos-note{margin:0 26px;padding:10px 12px;background:#F4F1FB;border:1px solid #E1D8F5;border-radius:8px;font-size:12px;color:#5A3E9E;font-weight:500;}\n.ip-foot{padding:18px 26px 24px;border-top:1px solid var(--border-soft);display:grid;grid-template-columns:1fr 1fr;gap:20px;font-size:12px;color:var(--text-mute);}\n.ip-foot h4{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-mute);margin-bottom:6px;}\n.ip-disclaimer{padding:10px 26px 0;text-align:right;border-top:1px solid var(--border-soft);margin-top:6px;}\n.ip-thankyou{padding:14px 26px 0;text-align:center;font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:13.5px;color:var(--ink);letter-spacing:.02em;}\n.ip-signature{padding:2px 26px 22px;display:flex;justify-content:flex-end;}\n\n@page{\n  size: A4;\n  margin: 8mm;\n}\n@media print{\n  .slb-app-root{margin:0 !important;padding:0 !important;}\n  #app, #authScreen, #overlay, #drawer, #toast{display:none !important;}\n  #printArea{display:block !important;position:static !important;width:100%;padding:0;margin:0;}\n  .invoice-preview{border:none;border-radius:0;max-width:100%;page-break-inside:avoid;}\n  .ip-head{padding:14px 18px;}\n  .ip-route{padding:14px 18px;gap:10px;}\n  .ip-body{padding:14px 18px;}\n  .ip-foot{padding:12px 18px 14px;}\n  .ip-disclaimer{padding:6px 18px 0;margin-top:4px;}\n  .ip-thankyou{padding:10px 18px 0;}\n  .ip-signature{padding:2px 18px 12px;}\n  .perforation{margin:0 18px;}\n  .ip-bos-note{margin:0 18px;}\n  table{font-size:12px;}\n  th,td{padding:7px 8px;}\n  .no-print{display:none !important;}\n}\n\n.search-bar{display:flex;gap:10px;margin-bottom:16px;}\n.search-bar input{flex:1;padding:9px 12px;border:1px solid var(--border);border-radius:8px;}\n.filter-pills{display:flex;gap:6px;}\n.pill{padding:6px 12px;border-radius:20px;border:1px solid var(--border);font-size:12.5px;color:var(--text-mute);background:#fff;}\n.pill.active{background:var(--ink);color:#fff;border-color:var(--ink);}\n\n.section-title{font-size:13px;font-weight:600;color:var(--ink);text-transform:uppercase;letter-spacing:.03em;margin:22px 0 10px;}\n.section-title:first-child{margin-top:0;}\n.help-text{font-size:12px;color:var(--text-mute);margin-top:2px;}\n\n\/* Login \/ setup screen *\/\n#authScreen{\n  min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--ink);\n  background-image:radial-gradient(circle at 20% 20%, rgba(232,163,61,0.10), transparent 40%),radial-gradient(circle at 80% 80%, rgba(50,72,117,0.35), transparent 45%);\n  padding:20px;\n}\n.auth-card{\n  width:100%;max-width:380px;background:var(--surface);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,0.35);\n  padding:32px 30px 28px;\n}\n.auth-brand{display:flex;align-items:center;gap:12px;margin-bottom:22px;}\n.auth-brand .logo-wrap{\n  width:56px;height:56px;border-radius:12px;background:#fff;flex-shrink:0;\n  display:flex;align-items:center;justify-content:center;overflow:hidden;\n  border:1px solid var(--border);box-shadow:0 2px 8px rgba(22,35,63,0.10);\n}\n.auth-brand img{width:100%;height:100%;object-fit:contain;padding:6px;display:block;}\n.auth-brand .t1{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:16px;color:var(--ink);}\n.auth-brand .t2{font-size:11.5px;color:var(--text-mute);}\n.auth-title{font-size:17px;margin-bottom:4px;}\n.auth-sub{font-size:12.5px;color:var(--text-mute);margin-bottom:20px;}\n.auth-error{background:var(--danger-bg);color:var(--danger);font-size:12.5px;padding:9px 12px;border-radius:8px;margin-bottom:14px;display:none;}\n.auth-error.show{display:block;}\n<\/style>\n\n\n<div class=\"slb-app-root\">\n<div id=\"authScreen\"><\/div>\n<div id=\"app\" style=\"display:none\"><\/div>\n<div id=\"overlay\" onclick=\"closeDrawer()\"><\/div>\n<div id=\"drawer\"><\/div>\n<div id=\"printArea\" style=\"display:none\"><\/div>\n<div id=\"toast\" class=\"toast\"><\/div>\n<\/div>\n\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.2\/firebase-app-compat.js\"><\/script>\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/10.12.2\/firebase-firestore-compat.js\"><\/script>\n<script>\n  const FIREBASE_CONFIG = {\n    apiKey: \"AIzaSyDYCLnE54y06iMNCu8GD_3VWvULc-uW6rQ\",\n    authDomain: \"slb-books.firebaseapp.com\",\n    projectId: \"slb-books\",\n    storageBucket: \"slb-books.firebasestorage.app\",\n    messagingSenderId: \"871472305647\",\n    appId: \"1:871472305647:web:af7b289e4067dddfdc921a\"\n  };\n  firebase.initializeApp(FIREBASE_CONFIG);\n  const FIRESTORE_DB = firebase.firestore();\n  const FIRESTORE_COLLECTION = 'slbbooks_data';\n<\/script>\n\n<script>\n\/* ---------- Icons ---------- *\/\nconst ICONS = {\n  dashboard:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"9\" rx=\"1.5\"\/><rect x=\"14\" y=\"3\" width=\"7\" height=\"5\" rx=\"1.5\"\/><rect x=\"14\" y=\"12\" width=\"7\" height=\"9\" rx=\"1.5\"\/><rect x=\"3\" y=\"16\" width=\"7\" height=\"5\" rx=\"1.5\"\/><\/svg>',\n  invoice:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 3h9l5 5v13a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z\"\/><path d=\"M14 3v5h5\" \/><path d=\"M8 13h8M8 17h5\"\/><\/svg>',\n  users:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"9\" cy=\"8\" r=\"3.2\"\/><path d=\"M2.5 20c0-3.5 3-6 6.5-6s6.5 2.5 6.5 6\"\/><circle cx=\"17\" cy=\"8.5\" r=\"2.6\"\/><path d=\"M15.5 14.3c2.8.5 4.5 2.6 4.5 5.7\"\/><\/svg>',\n  package:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M21 8l-9-5-9 5 9 5 9-5z\"\/><path d=\"M3 8v8l9 5 9-5V8\"\/><path d=\"M12 13v8\"\/><\/svg>',\n  chart:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M4 20V10M12 20V4M20 20v-7\"\/><\/svg>',\n  settings:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"12\" cy=\"12\" r=\"3.2\"\/><path d=\"M19.4 13.5a7.6 7.6 0 0 0 0-3l2-1.5-2-3.4-2.3.8a7.7 7.7 0 0 0-2.6-1.5L14 2h-4l-.5 2.4a7.7 7.7 0 0 0-2.6 1.5l-2.3-.8-2 3.4 2 1.5a7.6 7.6 0 0 0 0 3l-2 1.5 2 3.4 2.3-.8a7.7 7.7 0 0 0 2.6 1.5L10 22h4l.5-2.4a7.7 7.7 0 0 0 2.6-1.5l2.3.8 2-3.4-2-1.5z\"\/><\/svg>',\n  plus:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 5v14M5 12h14\"\/><\/svg>',\n  edit:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M12 20h9\"\/><path d=\"M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\"\/><\/svg>',\n  trash:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 6h18\"\/><path d=\"M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2\"\/><path d=\"M19 6l-.9 14a1 1 0 0 1-1 1H6.9a1 1 0 0 1-1-1L5 6\"\/><\/svg>',\n  print:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 9V3h12v6\"\/><rect x=\"4\" y=\"9\" width=\"16\" height=\"8\" rx=\"1.2\"\/><path d=\"M6 17v4h12v-4\"\/><\/svg>',\n  download:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M12 3v13\"\/><path d=\"M6 11l6 6 6-6\"\/><path d=\"M4 21h16\"\/><\/svg>',\n  x:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 6 6 18M6 6l12 12\"\/><\/svg>',\n  check:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\"><path d=\"M20 6 9 17l-5-5\"\/><\/svg>',\n  search:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"11\" cy=\"11\" r=\"7\"\/><path d=\"m21 21-4.3-4.3\"\/><\/svg>',\n  plane:'<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M21 15.5v-2l-8-5V4a1.5 1.5 0 0 0-3 0v4.5l-8 5v2l8-2.5V17l-2.5 2v1.5L11 20l3.5.5V19L12 17v-4.5z\"\/><\/svg>',\n  money:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"2\" y=\"6\" width=\"20\" height=\"12\" rx=\"2\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><path d=\"M6 10v.01M18 14v.01\"\/><\/svg>',\n  clock:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><circle cx=\"12\" cy=\"12\" r=\"9\"\/><path d=\"M12 7v5l3 3\"\/><\/svg>',\n  receipt:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M6 2h12v20l-3-2-3 2-3-2-3 2z\"\/><path d=\"M9 8h6M9 12h6\"\/><\/svg>',\n  ticket:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 4v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-4z\"\/><path d=\"M13 5v14\" stroke-dasharray=\"2 2\"\/><\/svg>',\n  building:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"\/><path d=\"M9 8h1M14 8h1M9 12h1M14 12h1M9 16h1M14 16h1\"\/><\/svg>',\n  empty:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><path d=\"M4 7h16l-1.5 12a1 1 0 0 1-1 1H6.5a1 1 0 0 1-1-1L4 7z\"\/><path d=\"M8 7V5a4 4 0 0 1 8 0v2\"\/><\/svg>',\n  lock:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"4\" y=\"10\" width=\"16\" height=\"10\" rx=\"2\"\/><path d=\"M8 10V7a4 4 0 0 1 8 0v3\"\/><\/svg>',\n  logout:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"\/><path d=\"M16 17l5-5-5-5\"\/><path d=\"M21 12H9\"\/><\/svg>',\n  expense:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><rect x=\"2\" y=\"6\" width=\"20\" height=\"14\" rx=\"2\"\/><path d=\"M2 10h20\"\/><path d=\"M14 15h4\"\/><\/svg>',\n  trendUp:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 17l6-6 4 4 8-8\"\/><path d=\"M15 7h6v6\"\/><\/svg>',\n  trendDown:'<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\"><path d=\"M3 7l6 6 4-4 8 8\"\/><path d=\"M15 17h6v-6\"\/><\/svg>'\n};\n\n\/* ---------- Constants ---------- *\/\nconst STATES = [\n {code:'01',name:'Jammu and Kashmir'},{code:'02',name:'Himachal Pradesh'},{code:'03',name:'Punjab'},\n {code:'04',name:'Chandigarh'},{code:'05',name:'Uttarakhand'},{code:'06',name:'Haryana'},{code:'07',name:'Delhi'},\n {code:'08',name:'Rajasthan'},{code:'09',name:'Uttar Pradesh'},{code:'10',name:'Bihar'},{code:'11',name:'Sikkim'},\n {code:'12',name:'Arunachal Pradesh'},{code:'13',name:'Nagaland'},{code:'14',name:'Manipur'},{code:'15',name:'Mizoram'},\n {code:'16',name:'Tripura'},{code:'17',name:'Meghalaya'},{code:'18',name:'Assam'},{code:'19',name:'West Bengal'},\n {code:'20',name:'Jharkhand'},{code:'21',name:'Odisha'},{code:'22',name:'Chhattisgarh'},{code:'23',name:'Madhya Pradesh'},\n {code:'24',name:'Gujarat'},{code:'26',name:'Dadra and Nagar Haveli and Daman and Diu'},{code:'27',name:'Maharashtra'},\n {code:'29',name:'Karnataka'},{code:'30',name:'Goa'},{code:'31',name:'Lakshadweep'},{code:'32',name:'Kerala'},\n {code:'33',name:'Tamil Nadu'},{code:'34',name:'Puducherry'},{code:'35',name:'Andaman and Nicobar Islands'},\n {code:'36',name:'Telangana'},{code:'37',name:'Andhra Pradesh'},{code:'38',name:'Ladakh'},{code:'97',name:'Other Territory'}\n];\nconst GST_RATES = [0,5,12,18,28];\nconst SERVICE_PRESETS = [\n {name:'Domestic Air Ticket', hsn:'9964', rate:5, unit:'ticket'},\n {name:'International Air Ticket', hsn:'9964', rate:5, unit:'ticket'},\n {name:'Hotel Booking (Domestic)', hsn:'996311', rate:12, unit:'night'},\n {name:'Hotel Booking (International)', hsn:'996311', rate:5, unit:'night'},\n {name:'Domestic Tour Package', hsn:'9985', rate:5, unit:'package'},\n {name:'International Tour Package', hsn:'9985', rate:5, unit:'package'},\n {name:'Visa Assistance', hsn:'998596', rate:18, unit:'application'},\n {name:'Travel Insurance', hsn:'997133', rate:18, unit:'policy'},\n {name:'Railway Ticket Booking', hsn:'996421', rate:5, unit:'ticket'},\n {name:'Bus Ticket Booking', hsn:'996421', rate:5, unit:'ticket'},\n {name:'Car \/ Cab Rental', hsn:'996601', rate:5, unit:'trip'},\n {name:'Forex Service', hsn:'997157', rate:18, unit:'transaction'},\n {name:'Passport Assistance', hsn:'998596', rate:18, unit:'application'},\n {name:'Tour Guide Service', hsn:'998552', rate:18, unit:'day'}\n];\nconst PAY_MODES = ['Cash','Bank Transfer','UPI','Cheque','Card','Other'];\nconst EXPENSE_CATEGORIES = [\n  'Vendor Payments (Hotels\/Airlines\/Package Cost)',\n  'Office Rent',\n  'Salaries & Staff',\n  'Marketing & Advertising',\n  'Travel & Conveyance',\n  'Utilities (Electricity\/Internet)',\n  'Mobile Phone Bill',\n  'Credit Card Bill',\n  'Software & Subscriptions',\n  'Bank & Payment Gateway Charges',\n  'Commission Paid',\n  'Printing & Stationery',\n  'Professional Fees (CA\/Legal)',\n  'Other'\n];\nconst DEFAULT_BUSINESS_NAME = 'Sarvalok Bhraman';\nconst DEFAULT_BUSINESS = {\n  name: 'Sarvalok Bhraman',\n  logo: '', \/\/ set below to DEFAULT_LOGO once DEFAULT_LOGO is defined\n  gstin: '07AFEFS0908J1ZY',\n  pan: 'AFEFS0908J',\n  address: 'E-514 F\/F Street No-7A Sangam Vihar',\n  city: 'Delhi',\n  pincode: '110062',\n  state: 'Delhi',\n  stateCode: '07',\n  phone: '8567950007',\n  email: 'enquiry@sarvalokbhraman.com',\n  invoicePrefix: 'SLB',\n  nextInvoiceNumber: 1,\n  bosInvoicePrefix: 'SLB-BOS',\n  nextBosInvoiceNumber: 1,\n  terms: '',\n  bankName: '',\n  bankAccount: '',\n  bankIFSC: '',\n  upiId: ''\n};\nconst DEFAULT_LOGO = 'data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVQAAAE5CAYAAADP+NgxAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAETRSURBVHhe7d15fFxV+T\/wz+fcmSTdFyj73nRhpwVUNrGylTZlU\/Cr6Fdxww2SgiiuyBdFEbAJRRQ31J8riyJNCshSyo6UFATa0oUdCrSlK22TmXs+vz8my9yTtE3SpM1knvfr1VczzzmTubn3PnPu2S5gjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGLM9MAwYY3rGqEeX714al7xvg8\/MWXL8Dq+F5abwuTBgjOl+5fVrRpSw7A\/e+VtLotRVO9+9dEBYxxQ+S6jG9LBDnvYDSt7LXO4zGycgmyV89iPDBw+qCuuZwmcJ1ZietmHtQXCp8wk6kISURjZzxpiHl+0WVjWFzRKqMT0s6\/kXZBtbxisIAPKHOpZ+NlHRFDxLqMb0oLEPLD9LcWYf0LUOAOdaqSnITyyfubA08QZT0CyhGtNjRFdScgOldmfTyPtD04N3OCGMm8JlCdWYHrL\/Q2smKs4OAdkmoZIkof5E6nNhmSlcllCN6SGMeDm93\/QtvUQhHrfrHXP6h0WmMFlCNaYHjJm1ch\/F8UiFBaHY7zZ02Kjjw7ApTJZQjekBUYofh+LBbOd2vxlJgkjD8dywzBQmS6jGdDcpAnkmwSgsClEgiSPDuClMllCN6Wb73L9yD8nvDW3xhj\/Hx3vv8ajvF4ZN4bGEakw3K037IyE\/uL3R\/TZIysep\/g1L9w6LTOGxhGpMN4ui9GGQNj26H6DgolT\/o8O4KTyWUI3pTpKDtNeWm6Z5SDmXtnX9fYAlVGO60wMPODk3PAxvlkQxLgvDpvBYQjWmOy0b4SDEHRyOyiHl43hdGDaFxxKqMd3N+0FhaLPkSWpVGDaFxxKqMd2ofOABBHxpp\/pQAUh6O4yZwmMJ1ZhutPi1et+VR7UpdnEYM4XHEqox3ah8z\/EOQDqMbxadUk4rwrApPJZQjemG8pnLB+\/\/4Or3j5m16oiwrNmB9604Oj1kbQ3lD+zQpP5mAmOpfK+HXhkWFpnC0vGDbkyR2nXOG\/2HbuxXRUb\/B+F135j96oIP71DXXH7g7BV7wrmPebrz6bMjO31dSVI6LcT+l3FDw40LT9zlv2GVsU+u29U1YOd5xw58OiwzvUfnDrwxReiQx9YcmxVmI5txAKRU+sX5dz81dsxJB\/Wj0h9w8N+Cc8cjm2HiUSedJ5FPIpX+2fyjBv29OXjgwxtGMsr8MvbxoRD\/PP\/YoVOTbzO9xdYcfGP6vptvjg7cfWKtsg2ngI6SJDLjougyxdl96aLPK45Bslu6zyR5RNFqxPGvshlMT++yw2q3es1v4mzmbACEc8tBd8H8Y4e2JFzTe1hCNWYzdr5JZQNGrVkAn83fvEQCYkIO6J5EmiAJUQRP3slsPIvOXQV5BwCCxCh9+7xjhpwVvs1sf91\/MhjTh7zyMrJim31NSSDVI8kUuR2o4D1dHJ8K4kr4OK\/hQwjaBbNmpfLfYnqHnjkhjOkrdgMpNYbhbYQE0vkzBiiBQAkmfMjmrfZCllCN2YwD9l\/xbTjsE8a3J4rLAHZquwCzbVhCNaYdB85eccoBD698y4PfRjbba64TAfDww3Gj0jYG0vvYATEmzwGz3i5HSel1ymaPJTkQUq5Ps7dommVAuqUZH5+z6Pgd\/xNWMdtP7zlRjNmODrl76QA\/eOBlcTZzJqSRkHJPJe2NJIGEotR7ymSuXvChHf8vrGK2j955whizrUg84NE1nxHwWcTZD0CKem0ibUuSGl1J6byGlUuPWnzT01ncco4NVm1HhXLiGNPtxj6w4qgo7ap8rJMoPxRomrK0NdT0qNOt\/T6dIi86+Rhnxc88fvfiykkNYQ2zbWzDg25M77D\/I+\/uTe8+AfjzIJXn7qC7kACVy57NbxQRg24dpBJK\/TqUVJtu37ulrzaKvGL9pmFw6nsvHjbonbDY9LytO4DGFJBd57zRf1h28OnMZs4T+GHFsetSIm3OpWQMIEbk3iZdvTyfzDY23hel3Ecc9PUtXl+S5KJlgr+NiD4MxaMhaWuWsUryTJUsjuXPjde88+ziSaOttboNbf6AG9MXXCY39pRVh7kYn5f0ecBHFNjhFmHrbXxuyWkUrQb5jIQZ2Qz\/rfVvvZifuMY8uubbUTbzwy1eX5KUSs3wy5Z+ksN2GEiW7uUdTnfEJym\/u7JZ0nVls5VcF4DEb2hj9m8vnDzizbCG6RldOFjGFI6Rs1fsWercOYS+5aVhlDafSJuTJwABYhQ1gm6lpMcA3JPVxn+vW\/n6W0tPO2J98o2txjy4+rwI2d9u8fqSpFT0q\/nHDPtSS+zGOem9R48cUKJ4TJRKfQSOn6fPDu3SrAMXeXnVqdT9eMH7hzwWFpvu17kDZEwBKZ+5sLRk6M5\/QzZ7+ib7KJsSqACA9HAuQ\/AtkY944d6Y0TOpVPzqghVvr0MHb5\/HPrjq4w7xn7a0cEaQp0t\/fd6xQ6aFZQCAyy5zOx\/9pX5DgJ2igf2\/Bep0ZrJDQKYlEbmxr7b\/pzy53atSS6Hs9zNrceviSTuuCeuY7rPZg2FMIdv1jjf6Dx3afw7h908UNCdR5zzABlKvA+5huOhp0f\/nvcaGV1\/1I5ZhArOJ93XQ6AdWnJpyqN1iQiUbveO5Lxwz7NawrF2XKbXPB1ce2K80VQnFp0AaAPnBbPpA2HRylQcdvItujDduvHHRCTs\/E9Yw3WMTB8CYwrfDw8sG7cKS53w2s2dTc04e2EBwEZxbLESPxtCchlX+uVcrhqzqrvXx5Q8t\/0Ca7iF6bXpHKElw0bosokkLjxv8cFjcESMfeOuokvSAL4J+HLLxPoAGYbMtVwrAE0yna54\/atDfwlKz9dr5pRvTN+w6R\/2HNax7XopBuOcFPBb77IyGeNjCVyZwY1i\/u4x6YM3+6bSvRxyXhWUtJMm55T7b8L4XJuz6cljcKZcpNfqYt493AwZ8lD57BKRRgAaxKbnmd3UI8GBUO3\/p62fjnIO21y5afZYlVNN33axo\/x1W7jH\/hOGvNneTbgtjZq3cx5W4ZxhnB4dlLSTBuaffy64++pUJ+3Zbct\/j76\/267\/r4OMjxw8B\/hgKh8LHA1vmy5IQo3\/MXzrkYziHtqqqm1lCNaab7fXQqmEDHRYijncMy1pIQip1x7xjhp4RFnWXPe56dfjAAcMOB+ITIR1F4DBQAwj3l3nHDfnf7uriMK0222lujOm691ZksorjTU6rQm5ASkL0VhjvTq9P3OvdBccNumfBcUO\/iQZ3phfPJtx3FaXmWzLtGdZCNaa73fRS2djyYY85xYeFRc1ExgS\/Oe+4YdeGZT2MeO65NA6y\/tOeYC1UY7rby79vJN3bYbhFbtpWDJ+dGxZtA7Jk2nMsoRrT3S6\/3MthWf6qqwSS9FqtbGZJWGQKmyVUY3qAy2YXh7EWuSlTb89v2KVH+1DNtmcJ1Zge4GO+EcbyMZV6F6d2bSWW6b0soRrTEyIt0qamvpKAjxeC8GGRKWyWUI3pAT5Ovb2piUkCPMRHbOpS32MJ1Zge8DbitxRF7d7SE1zv1fhsGDeFzxKqMT1g9cIl75FuQxiXJDlm1iP9YlhmCp8lVGN6wvlHZARlw6lTzM2aWj5w6evv5cdN32AJ1ZgeIRJudZtOUhIin51nOz31SZZQjekRlCLX5llOkhez\/pEwbvoGS6jG9BAKy8HkSD7pILhH82Om77CEakxPkOjlNyJcfUqqwTe8mwyavsISqjHdbM9\/L9vtgMfW3MM4e2qbHd1Ilkapy0fNenuTO1GZwmXb9xnTjfZ\/YNU5SLvvIM4ctJlHVst7PYQo9cMFxw29Jyw0hau9g22M6aQxDy8bFEVl30Um80lAu27x2pKEKPW0i0p+9txR\/f8UFpvCtPmDbozZotGz3j42VVJ2gXz2LHhF7T9xtC3lkuprcKmfzz964E\/DclN4OnTgjTHtmDUrdUDZkecp03ghoQO7cj0p97C+1SD\/NP\/5JRfh\/CMyYR1TODp9AhhjgNGzlo1Np9Kf8fIXQr6so63SdkkSkBX4d+\/TUxdOGLw8rGIKQ9dPAmOK1JhH1x7sstkf0qECPnbtXkbNS05JtEydIilJ7Sbf5vqp9H2NGzKVi0\/ccV5YxfR+Nm3KmE5SNrsToBPhfTKZSgIkkY1wqZcRpf4kl\/o5XNQg57zA38OlnwLpJSX3QiVzMwJ8fEK6f8kfRs9as+lHUJteyxKqMZ1EB08ogiRIEhGLXAWXmiuXqvFwn8wAJ655Y835mYaN1SKXEZzPlLu40WfPElNnianfy0WrJCixgYr3YJwdlYnXR4l\/1BSEtrcexpjNGvvIugoq81cKa0QuFDBLMZ7IbNww98WTd1mWv9y0fObC0pKhu\/wGsX9i3r1DbsDlzLVMZy4sHT1wxzF07mgHnkfqCGWzIEA4t2Lj0pXlL54zcnX+v2t6P0uoxnTSqAfe3D8VlZ2srO5bsGz4QpzDze4cNfbx9w73DWveXXj8ri+FZQAw+o41O3JIZrQjz6aLPivEfv6a5btg0uiGsK4xxpiOuHFOunzmmhGjZi0\/EZfJuuOMMcYYY0y3kXWlFSm7rTCmm42dvWrc2EdW\/U8YR9MgFWYpFcZN32CfpMZ0szEPrfy6Iy6Qi2Yjxj9jxi\/R+wEpVzKekZuU8fHUhccMXhC+zxQ+S6jGdKc5Su+\/YfUNUPazucuLGwA15q41DgDdfEaYMO\/oobbJdB9kt\/zGdKO9164aAGksBRJwhAZQGkppCOVTkn9y3lFDVobvM32DJVRjulEJ411BjEpsLN20rFSkd+Aj4XOmTN9hCdWYbsR0tCuhtuvwc9v0NSBKvRIWmb7DEqrZLF0Gp5vPjnTZ8anc33CS9b23S2IEdzDU\/rQpCq9uXLfuuTBu+o52D7wpXn76abvBx+dA\/CjJY8LyZvJ6G8Q\/QH+Lq5w5KywvRuUzF5amB4\/4HeP4422eJSVJUfTX+ccO+yQAu+XvoyyhGgCAv+b0PZHyPyLxqbBsSyQtBnCpq6q9LSwrJuUPrhlR4uLH4f1+YZkAT5e6ZN6xQ34Wlpm+w275DXxNxf8x7V\/tSjJFbsylnOStqqmY42smjgzLi8gIAHuFQQAg2JD1G58I46ZvsYRa5Hx1xZ8Ifi+Mdw0Ph1KP+pqJB4QlxSBFjMltOh3I7Zn6vC+Jng2LTN\/S9uCbouGrK24jeW4Y3xokd4JSD\/nqKYeEZX3azYrI+PRNDUiB0eLF799hbRg2fYsl1CLlp035Gcmzwni7ygYBO40Chu4WlrSL5HBAd\/prTx4elvVVe494eRDJk9sMRgGAo6c0y+af9n1tD77p8\/x1k86ios0PIA3eGTjsNGDkB4BBI5Jlr9QD8+4FFj2cjAckPeiqao8P433Rfo+s3alU2aUMb\/klie7NxjjzoSUTdl6cKDN9jiXUIuOnn7Yb4ngR6fqHZS2OPQ8Yf2YYbWvFy8Bd1wIrNj1XXfLfdFV1Pw3jfc3oB9\/9ZCT\/B5JtE2qUenB+ZsiJmMBsosz0OXbLX2zi+PJNJtNBOwIfu6ZjyRQAdtgH+Ni1wP4nhCUtCH5bN544JIz3KRKdi77QJpkCucdIgw9aMi0ObU8A02f56aeOJt3nw3iLMccDO48Oo5uXKgHe\/z9A6YCwJIccoo1l3w7DfY0j2l0EIWmVMpmZYdz0TZZQi4mPvhKGWvQbDBxxdhjtmME7A4d\/NIy2oHB+GOtLRj2w6lD4OAwDkhhFy7O7D58bFpm+yRJqMRFOC0Mtxp8JlLTfE9Ahh00B0v3CaA45RNUVx4bhviJVmvoy5NsdjxD5yOLRzp5eWiQsoRYJTTt9KMl9w3iL\/T8cRjonVQLse2QYbSFgUhjrKyh\/OsINY3Kj++sh97tE3PRpllCLRRTvEoZaRGmg\/7Aw2nk7bjpfgzouDPUFB8x6u1zZ7OAwDgAuilasdQPnhHHTd1lCLRLKYtP3892RTIFND0wBgLh3GOoTSvufD6IkDIuQgMdeP9ptCMtM32UJtWhwEx2cABreCyNdEzeGkRYk9wxjfYEUT6DkwhVSpGt0jY3X5MdM32cJtXiUhoEWje8BmY1htPNWLQ0jCbr57CiMFbKxs1YcRB\/vE8YBAM5tfA47Ph2GTd9mCbVI0GnTzUcAeOnJMNJ5r20hf5x9iw9DhcyVlPyvwCFh6xSS93H8B5vMX3wsoRYJ0W3+vv6FB8JI57z1ArDyjTDaQtI6sg\/tVH\/ZrJTgj4f3bVvdUYSMcG0YNn2fJdRiQa0KQwkvPQm8sRWPO3r4pjASeiYMFLIxJx52LOJs22VlkgA3L+uHLwuLTN9nCbVIuOsNtWSp97pi0fjoqEWHOzuZDFCELVR7XekqXn9NsomZFNXCUaGSfg7cM4vzo4cDoDcT4kj9bXQAAAAAElFTkSuQmCC';\nDEFAULT_BUSINESS.logo = DEFAULT_LOGO;\n\n\/* ---------- State ---------- *\/\nlet DB = { business:null, customers:[], items:[], invoices:[], expenses:[] };\nlet VIEW = 'dashboard';\nlet VPARAM = null;\n\n\/* ---------- Storage ---------- *\/\nasync function loadAll(){\n  DB.business = await safeGet('business', null);\n  if(!DB.business){\n    DB.business = {...DEFAULT_BUSINESS};\n    await saveBusiness();\n  }\n  DB.customers = await safeGet('customers', []);\n  DB.items = await safeGet('items', []);\n  DB.invoices = await safeGet('invoices', []);\n  DB.expenses = await safeGet('expenses', []);\n  if(!DB.items.length){\n    DB.items = SERVICE_PRESETS.map(p=>({id:uid(),name:p.name,hsn:p.hsn,gstRate:p.rate,unit:p.unit,rate:0}));\n    await saveItems();\n  }\n}\nasync function safeGet(key, fallback){\n  try{\n    const doc = await FIRESTORE_DB.collection(FIRESTORE_COLLECTION).doc(key).get();\n    return doc.exists ? JSON.parse(doc.data().value) : fallback;\n  }catch(e){\n    console.error('Firestore read failed for key \"'+key+'\":', e);\n    return fallback;\n  }\n}\nasync function saveKey(key, value){\n  try{\n    await FIRESTORE_DB.collection(FIRESTORE_COLLECTION).doc(key).set({\n      value: JSON.stringify(value),\n      updatedAt: new Date().toISOString()\n    });\n    return true;\n  }catch(e){\n    console.error('Firestore write failed for key \"'+key+'\":', e);\n    toast('Could not save \u2014 check Firebase setup \/ connection');\n    return false;\n  }\n}\nasync function saveBusiness(){ await saveKey('business', DB.business); }\nasync function saveCustomers(){ await saveKey('customers', DB.customers); }\nasync function saveItems(){ await saveKey('items', DB.items); }\nasync function saveInvoices(){ await saveKey('invoices', DB.invoices); }\nasync function saveExpenses(){ await saveKey('expenses', DB.expenses); }\n\n\/* ---------- Helpers ---------- *\/\nfunction uid(){ return Date.now().toString(36)+Math.random().toString(36).slice(2,8); }\nfunction fmtMoney(n){ n = Number(n)||0; return '\u20b9' + n.toLocaleString('en-IN',{minimumFractionDigits:2,maximumFractionDigits:2}); }\nfunction fmtNum(n){ n=Number(n)||0; return n.toLocaleString('en-IN',{maximumFractionDigits:2}); }\nfunction fmtDate(d){ if(!d) return '-'; const dt=new Date(d); return dt.toLocaleDateString('en-IN',{day:'2-digit',month:'short',year:'numeric'}); }\nfunction todayStr(){ return new Date().toISOString().slice(0,10); }\nfunction monthKey(d){ return new Date(d).toISOString().slice(0,7); }\nfunction stateName(code){ const s=STATES.find(x=>x.code===code); return s?s.name:''; }\nfunction esc(s){ if(s===undefined||s===null) return ''; return String(s).replace(\/[&<>\"']\/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c])); }\nfunction toast(msg){\n  const t=document.getElementById('toast');\n  t.innerHTML = ICONS.check + '<span>'+esc(msg)+'<\/span>';\n  t.classList.add('show');\n  clearTimeout(window.__toastT);\n  window.__toastT = setTimeout(()=>t.classList.remove('show'), 2200);\n}\nfunction customerById(id){ return DB.customers.find(c=>c.id===id); }\nfunction itemById(id){ return DB.items.find(i=>i.id===id); }\nfunction invoiceById(id){ return DB.invoices.find(i=>i.id===id); }\nfunction expenseById(id){ return DB.expenses.find(e=>e.id===id); }\nfunction totalExpensesFor(list){ return list.reduce((s,e)=>s+(Number(e.amount)||0),0); }\n\nfunction computeLine(line){\n  const base = (Number(line.qty)||0) * (Number(line.rate)||0);\n  const discount = base * ((Number(line.discountPct)||0)\/100);\n  const baseAfterDiscount = Math.max(0, base - discount);\n  const serviceCharge = Number(line.serviceCharge)||0;\n  \/\/ GST applies only on the service charge, never on the base\/ticket amount.\n  const taxable = serviceCharge;\n  const intra = isIntraState(line.__intra);\n  const effRate = line.__nonGst ? 0 : (Number(line.gstRate)||0);\n  const taxAmt = taxable * (effRate\/100);\n  let cgst=0, sgst=0, igst=0;\n  if(intra){ cgst = taxAmt\/2; sgst = taxAmt\/2; } else { igst = taxAmt; }\n  return { base, discount, baseAfterDiscount, serviceCharge, taxable, cgst, sgst, igst, total: baseAfterDiscount+serviceCharge+taxAmt };\n}\nfunction isIntraState(flag){ return !!flag; }\n\nfunction computeInvoiceTotals(inv){\n  const intra = DB.business && inv.customerStateCode && DB.business.stateCode === inv.customerStateCode;\n  const nonGst = inv.invoiceType === 'bos';\n  let baseTotal=0, taxable=0, cgst=0, sgst=0, igst=0;\n  const lines = (inv.lines||[]).map(l=>{\n    const c = computeLine({...l, __intra: intra, __nonGst: nonGst});\n    baseTotal += c.baseAfterDiscount; taxable += c.taxable; cgst += c.cgst; sgst += c.sgst; igst += c.igst;\n    return {...l, ...c};\n  });\n  const grand = baseTotal+taxable+cgst+sgst+igst;\n  const rounded = Math.round(grand);\n  const roundOff = rounded - grand;\n  return { lines, intra, nonGst, baseTotal, taxable, cgst, sgst, igst, grand, rounded, roundOff };\n}\nfunction invoicePaid(inv){ return (inv.payments||[]).reduce((s,p)=>s+Number(p.amount||0),0); }\nfunction invoiceStatus(inv){\n  const t = computeInvoiceTotals(inv).rounded;\n  const paid = invoicePaid(inv);\n  if(paid<=0) return 'Unpaid';\n  if(paid >= t - 0.5) return 'Paid';\n  return 'Partial';\n}\nfunction statusBadge(status){\n  const cls = status==='Paid'?'badge-paid':status==='Partial'?'badge-partial':'badge-unpaid';\n  return `<span class=\"badge ${cls}\">${status}<\/span>`;\n}\nfunction invoiceTypeBadge(inv){\n  const isBos = inv.invoiceType === 'bos';\n  return `<span class=\"badge ${isBos?'badge-bos':'badge-tax'}\">${isBos?'Bill of Supply':'Tax Invoice'}<\/span>`;\n}\n\n\/* ---------- App shell \/ routing ---------- *\/\nconst NAV = [\n  {id:'dashboard', label:'Dashboard', icon:'dashboard'},\n  {id:'invoices', label:'Invoices', icon:'invoice'},\n  {id:'customers', label:'Customers', icon:'users'},\n  {id:'items', label:'Services', icon:'package'},\n  {id:'expenses', label:'Expenses', icon:'expense'},\n  {id:'reports', label:'GST Reports', icon:'chart'},\n  {id:'settings', label:'Business Profile', icon:'settings'}\n];\n\nfunction renderShell(){\n  const app = document.getElementById('app');\n  app.innerHTML = `\n    <div id=\"sidebar\">\n      <div class=\"brand\">\n        <div class=\"brand-mark\">${DB.business&&DB.business.logo? `<img decoding=\"async\" src=\"${DB.business.logo}\" alt=\"Logo\">` : `<img decoding=\"async\" src=\"${DEFAULT_LOGO}\" alt=\"Logo\">`}<\/div>\n        <div>\n          <div class=\"brand-name\">${DB.business&&DB.business.name? esc(DB.business.name) : 'Sarvalok Bhraman'}<\/div>\n          <div class=\"brand-sub\">GST Billing<\/div>\n        <\/div>\n      <\/div>\n      <nav class=\"navlist\" id=\"navlist\"><\/nav>\n      <div class=\"sidebar-foot\">\n        ${DB.business?esc(DB.business.name):'Set up your business'} <br>${DB.business&&DB.business.gstin?esc(DB.business.gstin):''}\n        <div style=\"margin-top:10px;\">\n          <button class=\"btn btn-sm\" style=\"width:100%;background:transparent;border-color:rgba(255,255,255,0.18);color:#DDE3F0;\" onclick=\"logout()\">${ICONS.logout} Logout<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n    <div id=\"main\">\n      <div class=\"topbar\">\n        <div>\n          <h1 id=\"topTitle\">Dashboard<\/h1>\n          <div class=\"sub\" id=\"topSub\">Overview of your travel business billing<\/div>\n        <\/div>\n        <div id=\"topActions\"><\/div>\n      <\/div>\n      <div class=\"content\" id=\"content\"><\/div>\n    <\/div>\n  `;\n  const navlist = document.getElementById('navlist');\n  navlist.innerHTML = NAV.map(n=>`\n    <button class=\"navitem ${VIEW===n.id?'active':''}\" data-nav=\"${n.id}\">${ICONS[n.icon]}<span>${n.label}<\/span><\/button>\n  `).join('');\n  navlist.querySelectorAll('[data-nav]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.nav)));\n}\n\nfunction navigate(view, param){\n  VIEW = view; VPARAM = param||null;\n  renderShell();\n  route();\n}\n\nfunction route(){\n  const titles = {\n    dashboard:['Dashboard','Overview of your travel business billing'],\n    invoices:['Invoices','Create and manage GST invoices'],\n    customers:['Customers','Manage client and corporate accounts'],\n    items:['Services','Your travel service catalog with HSN\/SAC and GST rates'],\n    expenses:['Expenses','Track your business costs and see your net profit'],\n    reports:['GST Reports','Tax summaries for return filing'],\n    settings:['Business Profile','Your company details used on every invoice']\n  };\n  document.getElementById('topTitle').textContent = titles[VIEW][0];\n  document.getElementById('topSub').textContent = titles[VIEW][1];\n  if(!DB.business && VIEW!=='settings'){\n    renderNoBusiness();\n    return;\n  }\n  if(VIEW==='dashboard') renderDashboard();\n  else if(VIEW==='invoices') renderInvoices();\n  else if(VIEW==='customers') renderCustomers();\n  else if(VIEW==='items') renderItems();\n  else if(VIEW==='expenses') renderExpenses();\n  else if(VIEW==='reports') renderReports();\n  else if(VIEW==='settings') renderSettings();\n}\n\nfunction renderNoBusiness(){\n  document.getElementById('topActions').innerHTML='';\n  document.getElementById('content').innerHTML = `\n    <div class=\"empty-state card\">\n      ${ICONS.building}\n      <h3>Set up your business profile first<\/h3>\n      <p>Your GSTIN, address and bank details appear on every invoice.<\/p>\n      <button class=\"btn btn-primary\" onclick=\"navigate('settings')\">${ICONS.building} Set up business<\/button>\n    <\/div>`;\n}\n\n\/* ---------- Drawer ---------- *\/\nfunction openDrawer(html, wide){\n  document.getElementById('drawer').innerHTML = html;\n  document.getElementById('drawer').style.width = wide? '760px':'560px';\n  document.getElementById('overlay').classList.add('open');\n  document.getElementById('drawer').classList.add('open');\n}\nfunction closeDrawer(){\n  document.getElementById('overlay').classList.remove('open');\n  document.getElementById('drawer').classList.remove('open');\n}\n\n\/* ---------- Settings \/ Business Profile ---------- *\/\nfunction renderSettings(){\n  document.getElementById('topActions').innerHTML='';\n  const b = DB.business || {name:DEFAULT_BUSINESS_NAME,gstin:'',pan:'',address:'',city:'',state:'',stateCode:'',pincode:'',phone:'',email:'',invoicePrefix:'INV',nextInvoiceNumber:1,bosInvoicePrefix:'INV-BOS',nextBosInvoiceNumber:1,terms:'',bankName:'',bankAccount:'',bankIFSC:'',upiId:'',logo:DEFAULT_LOGO};\n  window.__logoDraft = b.logo || '';\n  document.getElementById('content').innerHTML = `\n    <div class=\"card\" style=\"max-width:720px;\">\n      <div class=\"section-title\" style=\"margin-top:0;\">Company details<\/div>\n      <div class=\"field\">\n        <label>Logo<\/label>\n        <div style=\"display:flex;align-items:center;gap:14px;\">\n          <div id=\"logoPreviewWrap\" style=\"width:64px;height:64px;border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#fff;flex-shrink:0;\">\n            <img decoding=\"async\" id=\"logoPreview\" src=\"${esc(b.logo||'')}\" style=\"max-width:100%;max-height:100%;object-fit:contain;${b.logo?'':'display:none;'}\">\n          <\/div>\n          <div>\n            <input type=\"file\" id=\"f_logo\" accept=\"image\/*\" onchange=\"handleLogoUpload(event)\">\n            <div class=\"field-hint\">PNG or SVG with a transparent background works best.<\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Business name<\/label><input id=\"f_name\" value=\"${esc(b.name)}\" placeholder=\"Wanderlust Travels Pvt Ltd\"><\/div>\n        <div class=\"field\"><label>GSTIN<\/label><input id=\"f_gstin\" value=\"${esc(b.gstin)}\" placeholder=\"07ABCDE1234F1Z5\" maxlength=\"15\" style=\"text-transform:uppercase\"><\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>PAN<\/label><input id=\"f_pan\" value=\"${esc(b.pan)}\" placeholder=\"ABCDE1234F\" style=\"text-transform:uppercase\"><\/div>\n        <div class=\"field\"><label>State (place of business)<\/label>\n          <select id=\"f_state\">${STATES.map(s=>`<option value=\"${s.code}\" ${b.stateCode===s.code?'selected':''}>${s.name} (${s.code})<\/option>`).join('')}<\/select>\n        <\/div>\n      <\/div>\n      <div class=\"field\"><label>Address<\/label><textarea id=\"f_address\" placeholder=\"Office address\">${esc(b.address)}<\/textarea><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>City<\/label><input id=\"f_city\" value=\"${esc(b.city)}\"><\/div>\n        <div class=\"field\"><label>PIN code<\/label><input id=\"f_pincode\" value=\"${esc(b.pincode)}\"><\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Phone<\/label><input id=\"f_phone\" value=\"${esc(b.phone)}\"><\/div>\n        <div class=\"field\"><label>Email<\/label><input id=\"f_email\" value=\"${esc(b.email)}\"><\/div>\n      <\/div>\n\n      <div class=\"section-title\">Invoicing<\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Invoice number prefix<\/label><input id=\"f_prefix\" value=\"${esc(b.invoicePrefix||'SLB')}\"><\/div>\n        <div class=\"field\"><label>Next invoice number<\/label><input id=\"f_nextnum\" type=\"number\" min=\"1\" value=\"${b.nextInvoiceNumber||computeNextInvoiceNumber('tax')}\"><\/div>\n      <\/div>\n      <div class=\"field-hint\" style=\"margin-top:-8px;margin-bottom:14px;\">Normally the next number is worked out automatically from your last saved invoice. Change this only if you need to jump the sequence forward (e.g. to match an old invoice book) \u2014 it won't be allowed to go backwards below your last saved invoice number.<\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Bill of Supply (non-GST) prefix<\/label><input id=\"f_bosprefix\" value=\"${esc(b.bosInvoicePrefix||(b.invoicePrefix||'SLB')+'-BOS')}\"><\/div>\n        <div class=\"field\"><label>Next Bill of Supply number<\/label><input id=\"f_nextbosnum\" type=\"number\" min=\"1\" value=\"${b.nextBosInvoiceNumber||computeNextInvoiceNumber('bos')}\"><\/div>\n      <\/div>\n      <div class=\"field-hint\" style=\"margin-top:-8px;margin-bottom:14px;\">Bill of Supply (non-GST) invoices run in their own separate numbering series, e.g. ${esc(b.bosInvoicePrefix||(b.invoicePrefix||'SLB')+'-BOS')}-0001, so they never clash with your Tax Invoice numbers.<\/div>\n      <div class=\"field\"><label>Default terms & notes (shown on invoice)<\/label><textarea id=\"f_terms\" placeholder=\"Payment due within 7 days. Prices inclusive of applicable GST.\">${esc(b.terms||'')}<\/textarea><\/div>\n\n      <div class=\"section-title\">Bank details<\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Bank name<\/label><input id=\"f_bankname\" value=\"${esc(b.bankName||'')}\"><\/div>\n        <div class=\"field\"><label>Account number<\/label><input id=\"f_bankacc\" value=\"${esc(b.bankAccount||'')}\"><\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>IFSC<\/label><input id=\"f_ifsc\" value=\"${esc(b.bankIFSC||'')}\" style=\"text-transform:uppercase\"><\/div>\n        <div class=\"field\"><label>UPI ID<\/label><input id=\"f_upi\" value=\"${esc(b.upiId||'')}\"><\/div>\n      <\/div>\n      <button class=\"btn btn-primary\" onclick=\"saveBusinessForm()\">${ICONS.check} Save business profile<\/button>\n    <\/div>\n\n    <div class=\"section-title\">Login security<\/div>\n    <div class=\"card\" style=\"max-width:720px;\">\n      <div class=\"field-hint\" style=\"margin-bottom:14px;\">Change the username and\/or password used to open this app. You'll need your current password to confirm.<\/div>\n      <div id=\"pwChangeMsg\" class=\"auth-error\"><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>New username<\/label><input id=\"pw_username\" value=\"${esc(AUTH.username||'')}\"><\/div>\n        <div class=\"field\"><label>Current password<\/label><input id=\"pw_current\" type=\"password\" placeholder=\"Required to confirm changes\"><\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>New password<\/label><input id=\"pw_new\" type=\"password\" placeholder=\"Leave blank to keep current password\"><\/div>\n        <div class=\"field\"><label>Confirm new password<\/label><input id=\"pw_confirm\" type=\"password\"><\/div>\n      <\/div>\n      <button class=\"btn btn-amber\" onclick=\"changeCredentials()\">${ICONS.lock} Update login details<\/button>\n    <\/div>\n  `;\n}\nfunction handleLogoUpload(evt){\n  const file = evt.target.files[0];\n  if(!file) return;\n  const reader = new FileReader();\n  reader.onload = ()=>{\n    window.__logoDraft = reader.result;\n    const img = document.getElementById('logoPreview');\n    img.src = reader.result;\n    img.style.display = 'block';\n  };\n  reader.readAsDataURL(file);\n}\nasync function saveBusinessForm(){\n  const name = document.getElementById('f_name').value.trim();\n  if(!name){ toast('Business name is required'); return; }\n  const stateCode = document.getElementById('f_state').value;\n  DB.business = {\n    name, logo: window.__logoDraft || '',\n    gstin: document.getElementById('f_gstin').value.trim().toUpperCase(),\n    pan: document.getElementById('f_pan').value.trim().toUpperCase(),\n    address: document.getElementById('f_address').value.trim(),\n    city: document.getElementById('f_city').value.trim(),\n    pincode: document.getElementById('f_pincode').value.trim(),\n    state: stateName(stateCode), stateCode,\n    phone: document.getElementById('f_phone').value.trim(),\n    email: document.getElementById('f_email').value.trim(),\n    invoicePrefix: document.getElementById('f_prefix').value.trim() || 'INV',\n    nextInvoiceNumber: Number(document.getElementById('f_nextnum').value)||1,\n    bosInvoicePrefix: document.getElementById('f_bosprefix').value.trim() || ((document.getElementById('f_prefix').value.trim()||'INV')+'-BOS'),\n    nextBosInvoiceNumber: Number(document.getElementById('f_nextbosnum').value)||1,\n    terms: document.getElementById('f_terms').value.trim(),\n    bankName: document.getElementById('f_bankname').value.trim(),\n    bankAccount: document.getElementById('f_bankacc').value.trim(),\n    bankIFSC: document.getElementById('f_ifsc').value.trim().toUpperCase(),\n    upiId: document.getElementById('f_upi').value.trim()\n  };\n  await saveBusiness();\n  if(DB.business.logo) AUTH_LOGO = DB.business.logo;\n  if(DB.business.name) AUTH_BIZNAME = DB.business.name;\n  toast('Business profile saved');\n  renderShell(); route();\n}\n\n\/* ---------- Customers ---------- *\/\nfunction renderCustomers(){\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openCustomerForm()\">${ICONS.plus} Add customer<\/button>`;\n  const list = DB.customers;\n  let body;\n  if(!list.length){\n    body = `<div class=\"empty-state card\">${ICONS.empty}<h3>No customers yet<\/h3><p>Add a client or corporate account to start billing.<\/p>\n      <button class=\"btn btn-primary\" onclick=\"openCustomerForm()\">${ICONS.plus} Add customer<\/button><\/div>`;\n  }else{\n    body = `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Name<\/th><th>GSTIN<\/th><th>State<\/th><th>Contact<\/th><th class=\"right\">Invoices<\/th><th><\/th><\/tr><\/thead>\n      <tbody>${list.map(c=>{\n        const invCount = DB.invoices.filter(i=>i.customerId===c.id).length;\n        return `<tr>\n          <td><strong>${esc(c.name)}<\/strong><\/td>\n          <td class=\"mono\">${esc(c.gstin)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n          <td>${esc(stateName(c.stateCode))}<\/td>\n          <td>${esc(c.phone)}${c.phone&&c.email?' \u00b7 ':''}${esc(c.email)}<\/td>\n          <td class=\"right\">${invCount}<\/td>\n          <td>\n            <div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n              <button class=\"btn btn-sm btn-icon\" onclick=\"openCustomerForm('${c.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button>\n              <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteCustomer('${c.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n            <\/div>\n          <\/td>\n        <\/tr>`;\n      }).join('')}<\/tbody>\n    <\/table><\/div>`;\n  }\n  document.getElementById('content').innerHTML = body;\n}\n\nfunction openCustomerForm(id){\n  const c = id ? customerById(id) : {name:'',gstin:'',stateCode:DB.business?DB.business.stateCode:'',address:'',phone:'',email:''};\n  openDrawer(`\n    <div class=\"drawer-head\"><h3>${id?'Edit customer':'Add customer'}<\/h3><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n    <div class=\"drawer-body\">\n      <div class=\"field\"><label>Customer \/ company name<\/label><input id=\"c_name\" value=\"${esc(c.name)}\" placeholder=\"Sharma Family \/ Acme Corp\"><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>GSTIN (leave blank if unregistered)<\/label><input id=\"c_gstin\" value=\"${esc(c.gstin)}\" maxlength=\"15\" style=\"text-transform:uppercase\"><\/div>\n        <div class=\"field\"><label>State<\/label>\n          <select id=\"c_state\">${STATES.map(s=>`<option value=\"${s.code}\" ${c.stateCode===s.code?'selected':''}>${s.name} (${s.code})<\/option>`).join('')}<\/select>\n        <\/div>\n      <\/div>\n      <div class=\"field\"><label>Address<\/label><textarea id=\"c_address\">${esc(c.address)}<\/textarea><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Phone<\/label><input id=\"c_phone\" value=\"${esc(c.phone)}\"><\/div>\n        <div class=\"field\"><label>Email<\/label><input id=\"c_email\" value=\"${esc(c.email)}\"><\/div>\n      <\/div>\n    <\/div>\n    <div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button>\n      <button class=\"btn btn-primary\" onclick=\"saveCustomerForm('${id||''}')\">${ICONS.check} Save customer<\/button>\n    <\/div>\n  `);\n}\nasync function saveCustomerForm(id){\n  const name = document.getElementById('c_name').value.trim();\n  if(!name){ toast('Customer name is required'); return; }\n  const stateCode = document.getElementById('c_state').value;\n  const data = {\n    name, gstin: document.getElementById('c_gstin').value.trim().toUpperCase(),\n    stateCode, address: document.getElementById('c_address').value.trim(),\n    phone: document.getElementById('c_phone').value.trim(),\n    email: document.getElementById('c_email').value.trim()\n  };\n  if(id){\n    const idx = DB.customers.findIndex(x=>x.id===id);\n    DB.customers[idx] = {...DB.customers[idx], ...data};\n  }else{\n    DB.customers.push({id:uid(), ...data});\n  }\n  await saveCustomers();\n  closeDrawer();\n  toast('Customer saved');\n  route();\n}\nasync function deleteCustomer(id){\n  const used = DB.invoices.some(i=>i.customerId===id);\n  if(used){ toast('Cannot delete \u2014 customer has invoices'); return; }\n  if(!confirm('Delete this customer?')) return;\n  DB.customers = DB.customers.filter(c=>c.id!==id);\n  await saveCustomers();\n  route();\n}\n\n\/* ---------- Services \/ Items ---------- *\/\nfunction renderItems(){\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openItemForm()\">${ICONS.plus} Add service<\/button>`;\n  const list = DB.items;\n  let body;\n  if(!list.length){\n    body = `<div class=\"empty-state card\">${ICONS.empty}<h3>No services yet<\/h3><p>Add the travel services you bill for, with default HSN\/SAC and GST rate.<\/p>\n      <button class=\"btn btn-primary\" onclick=\"openItemForm()\">${ICONS.plus} Add service<\/button><\/div>`;\n  }else{\n    body = `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Service<\/th><th>HSN \/ SAC<\/th><th>Unit<\/th><th class=\"right\">Default rate<\/th><th class=\"right\">GST %<\/th><th><\/th><\/tr><\/thead>\n      <tbody>${list.map(i=>`<tr>\n          <td><strong>${esc(i.name)}<\/strong><\/td>\n          <td class=\"mono\">${esc(i.hsn)}<\/td>\n          <td>${esc(i.unit)}<\/td>\n          <td class=\"right mono\">${fmtMoney(i.rate)}<\/td>\n          <td class=\"right mono\">${i.gstRate}%<\/td>\n          <td><div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n            <button class=\"btn btn-sm btn-icon\" onclick=\"openItemForm('${i.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button>\n            <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteItem('${i.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n          <\/div><\/td>\n        <\/tr>`).join('')}<\/tbody>\n    <\/table><\/div>`;\n  }\n  document.getElementById('content').innerHTML = body;\n}\nfunction openItemForm(id){\n  const i = id ? itemById(id) : {name:'',hsn:'',unit:'unit',rate:0,gstRate:5};\n  openDrawer(`\n    <div class=\"drawer-head\"><h3>${id?'Edit service':'Add service'}<\/h3><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n    <div class=\"drawer-body\">\n      <div class=\"field\"><label>Service name<\/label><input id=\"i_name\" value=\"${esc(i.name)}\" placeholder=\"Domestic Tour Package\"><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>HSN \/ SAC code<\/label><input id=\"i_hsn\" value=\"${esc(i.hsn)}\" placeholder=\"9985\"><\/div>\n        <div class=\"field\"><label>Unit<\/label><input id=\"i_unit\" value=\"${esc(i.unit)}\" placeholder=\"package, night, ticket...\"><\/div>\n      <\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Default rate (\u20b9)<\/label><input id=\"i_rate\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${i.rate||0}\"><\/div>\n        <div class=\"field\"><label>GST rate<\/label><select id=\"i_gst\">${GST_RATES.map(r=>`<option value=\"${r}\" ${i.gstRate===r?'selected':''}>${r}%<\/option>`).join('')}<\/select><\/div>\n      <\/div>\n      <div class=\"field-hint\">Presets follow common SAC codes for tour operators, air\/rail\/bus ticketing, hotels, visa and insurance services. Adjust to match your actual tax position.<\/div>\n    <\/div>\n    <div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button>\n      <button class=\"btn btn-primary\" onclick=\"saveItemForm('${id||''}')\">${ICONS.check} Save service<\/button>\n    <\/div>\n  `);\n}\nasync function saveItemForm(id){\n  const name = document.getElementById('i_name').value.trim();\n  if(!name){ toast('Service name is required'); return; }\n  const data = {\n    name, hsn: document.getElementById('i_hsn').value.trim(),\n    unit: document.getElementById('i_unit').value.trim()||'unit',\n    rate: Number(document.getElementById('i_rate').value)||0,\n    gstRate: Number(document.getElementById('i_gst').value)\n  };\n  if(id){\n    const idx = DB.items.findIndex(x=>x.id===id);\n    DB.items[idx] = {...DB.items[idx], ...data};\n  }else{\n    DB.items.push({id:uid(), ...data});\n  }\n  await saveItems();\n  closeDrawer();\n  toast('Service saved');\n  route();\n}\nasync function deleteItem(id){\n  if(!confirm('Delete this service?')) return;\n  DB.items = DB.items.filter(i=>i.id!==id);\n  await saveItems();\n  route();\n}\n\n\/* ---------- Expenses ---------- *\/\nlet EXP_MONTH_FILTER = 'all';\nlet EXP_CAT_FILTER = 'all';\nfunction expenseMonthOptions(){\n  const set = new Set(DB.expenses.map(e=>monthKey(e.date)));\n  return [...set].sort().reverse();\n}\nfunction renderExpenses(){\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"openExpenseForm()\">${ICONS.plus} Add expense<\/button>`;\n  let list = [...DB.expenses].sort((a,b)=> new Date(b.date)-new Date(a.date));\n  if(EXP_MONTH_FILTER!=='all') list = list.filter(e=>monthKey(e.date)===EXP_MONTH_FILTER);\n  if(EXP_CAT_FILTER!=='all') list = list.filter(e=>e.category===EXP_CAT_FILTER);\n  const months = expenseMonthOptions();\n  const total = totalExpensesFor(list);\n\n  let body = `\n    <div class=\"card stat-card\" style=\"margin-bottom:16px;max-width:280px;\">\n      <div class=\"stat-label\">${ICONS.expense} Total (filtered)<\/div>\n      <div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(total)}<\/div>\n      <div class=\"stat-note\">${list.length} expense${list.length===1?'':'s'}<\/div>\n    <\/div>\n    <div class=\"field-row\" style=\"margin-bottom:16px;max-width:600px;\">\n      <div class=\"field\" style=\"margin-bottom:0;\">\n        <label>Month<\/label>\n        <select onchange=\"EXP_MONTH_FILTER=this.value;route()\">\n          <option value=\"all\" ${EXP_MONTH_FILTER==='all'?'selected':''}>All months<\/option>\n          ${months.map(m=>`<option value=\"${m}\" ${EXP_MONTH_FILTER===m?'selected':''}>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/option>`).join('')}\n        <\/select>\n      <\/div>\n      <div class=\"field\" style=\"margin-bottom:0;\">\n        <label>Category<\/label>\n        <select onchange=\"EXP_CAT_FILTER=this.value;route()\">\n          <option value=\"all\" ${EXP_CAT_FILTER==='all'?'selected':''}>All categories<\/option>\n          ${EXPENSE_CATEGORIES.map(c=>`<option value=\"${esc(c)}\" ${EXP_CAT_FILTER===c?'selected':''}>${esc(c)}<\/option>`).join('')}\n        <\/select>\n      <\/div>\n    <\/div>\n  `;\n  if(!list.length){\n    body += `<div class=\"empty-state card\">${ICONS.empty}<h3>No expenses${EXP_MONTH_FILTER!=='all'||EXP_CAT_FILTER!=='all'?' match this filter':' yet'}<\/h3><p>Log vendor payments, rent, salaries and other business costs here.<\/p>\n      <button class=\"btn btn-primary\" onclick=\"openExpenseForm()\">${ICONS.plus} Add expense<\/button><\/div>`;\n  }else{\n    body += `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Date<\/th><th>Category<\/th><th>Vendor \/ Paid to<\/th><th>Description<\/th><th>Mode<\/th><th class=\"right\">Amount<\/th><th><\/th><\/tr><\/thead>\n      <tbody>${list.map(e=>`<tr>\n          <td>${fmtDate(e.date)}<\/td>\n          <td>${esc(e.category)}<\/td>\n          <td>${esc(e.vendor)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n          <td>${esc(e.description)||'<span style=\"color:var(--text-faint)\">\u2014<\/span>'}<\/td>\n          <td>${esc(e.paymentMode)}<\/td>\n          <td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(e.amount)}<\/td>\n          <td><div style=\"display:flex;gap:6px;justify-content:flex-end;\">\n            <button class=\"btn btn-sm btn-icon\" onclick=\"openExpenseForm('${e.id}')\" aria-label=\"Edit\">${ICONS.edit}<\/button>\n            <button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"deleteExpense('${e.id}')\" aria-label=\"Delete\">${ICONS.trash}<\/button>\n          <\/div><\/td>\n        <\/tr>`).join('')}<\/tbody>\n    <\/table><\/div>`;\n  }\n  document.getElementById('content').innerHTML = body;\n}\nfunction openExpenseForm(id){\n  const e = id ? expenseById(id) : {date:todayStr(), category:EXPENSE_CATEGORIES[0], vendor:'', description:'', amount:0, gstPaid:0, paymentMode:'Bank Transfer', notes:''};\n  openDrawer(`\n    <div class=\"drawer-head\"><h3>${id?'Edit expense':'Add expense'}<\/h3><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n    <div class=\"drawer-body\">\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Date<\/label><input id=\"e_date\" type=\"date\" value=\"${e.date}\"><\/div>\n        <div class=\"field\"><label>Category<\/label><select id=\"e_category\">${EXPENSE_CATEGORIES.map(c=>`<option ${e.category===c?'selected':''}>${esc(c)}<\/option>`).join('')}<\/select><\/div>\n      <\/div>\n      <div class=\"field\"><label>Vendor \/ Paid to<\/label><input id=\"e_vendor\" value=\"${esc(e.vendor)}\" placeholder=\"e.g. Hotel Taj, Landlord, Staff name\"><\/div>\n      <div class=\"field\"><label>Description<\/label><input id=\"e_description\" value=\"${esc(e.description)}\" placeholder=\"What was this expense for?\"><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Amount (\u20b9)<\/label><input id=\"e_amount\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${e.amount||0}\"><\/div>\n        <div class=\"field\"><label>GST paid on this (\u20b9, optional)<\/label><input id=\"e_gstpaid\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${e.gstPaid||0}\"><\/div>\n      <\/div>\n      <div class=\"field\"><label>Payment mode<\/label><select id=\"e_mode\">${PAY_MODES.map(m=>`<option ${e.paymentMode===m?'selected':''}>${m}<\/option>`).join('')}<\/select><\/div>\n      <div class=\"field\"><label>Notes (optional)<\/label><textarea id=\"e_notes\">${esc(e.notes||'')}<\/textarea><\/div>\n      <div class=\"field-hint\">GST paid is for your own records (e.g. input tax credit tracking) \u2014 it isn't linked to your GST return filing automatically.<\/div>\n    <\/div>\n    <div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button>\n      <button class=\"btn btn-primary\" onclick=\"saveExpenseForm('${id||''}')\">${ICONS.check} Save expense<\/button>\n    <\/div>\n  `);\n}\nasync function saveExpenseForm(id){\n  const amount = Number(document.getElementById('e_amount').value)||0;\n  if(amount<=0){ toast('Enter a valid amount'); return; }\n  const data = {\n    date: document.getElementById('e_date').value || todayStr(),\n    category: document.getElementById('e_category').value,\n    vendor: document.getElementById('e_vendor').value.trim(),\n    description: document.getElementById('e_description').value.trim(),\n    amount,\n    gstPaid: Number(document.getElementById('e_gstpaid').value)||0,\n    paymentMode: document.getElementById('e_mode').value,\n    notes: document.getElementById('e_notes').value.trim()\n  };\n  if(id){\n    const idx = DB.expenses.findIndex(x=>x.id===id);\n    DB.expenses[idx] = {...DB.expenses[idx], ...data};\n  }else{\n    DB.expenses.push({id:uid(), ...data});\n  }\n  await saveExpenses();\n  closeDrawer();\n  toast('Expense saved');\n  route();\n}\nasync function deleteExpense(id){\n  if(!confirm('Delete this expense?')) return;\n  DB.expenses = DB.expenses.filter(e=>e.id!==id);\n  await saveExpenses();\n  route();\n}\n\n\/* ---------- Dashboard ---------- *\/\nfunction renderDashboard(){\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button>`;\n  const invs = DB.invoices;\n  const thisMonth = monthKey(new Date());\n  let totalInvoiced=0, totalReceived=0, totalOutstanding=0, gstThisMonth=0, invoicedThisMonth=0;\n  invs.forEach(inv=>{\n    const t = computeInvoiceTotals(inv);\n    const paid = invoicePaid(inv);\n    totalInvoiced += t.rounded;\n    totalReceived += paid;\n    totalOutstanding += Math.max(0, t.rounded-paid);\n    if(monthKey(inv.date)===thisMonth){ gstThisMonth += (t.cgst+t.sgst+t.igst); invoicedThisMonth += t.rounded; }\n  });\n  const totalExpensesAllTime = totalExpensesFor(DB.expenses);\n  const expensesThisMonth = totalExpensesFor(DB.expenses.filter(e=>monthKey(e.date)===thisMonth));\n  const netProfitThisMonth = invoicedThisMonth - expensesThisMonth;\n  const netProfitAllTime = totalInvoiced - totalExpensesAllTime;\n  const recent = [...invs].sort((a,b)=> new Date(b.date)-new Date(a.date)).slice(0,6);\n  document.getElementById('content').innerHTML = `\n    <div class=\"grid grid-4\">\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.invoice} Total invoiced<\/div><div class=\"stat-value\">${fmtMoney(totalInvoiced)}<\/div><div class=\"stat-note\">${invs.length} invoice${invs.length===1?'':'s'} all time<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.money} Received<\/div><div class=\"stat-value\" style=\"color:var(--success)\">${fmtMoney(totalReceived)}<\/div><div class=\"stat-note\">Across all invoices<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.clock} Outstanding<\/div><div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(totalOutstanding)}<\/div><div class=\"stat-note\">Yet to be collected<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.expense} Total expenses<\/div><div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(totalExpensesAllTime)}<\/div><div class=\"stat-note\">${DB.expenses.length} expense${DB.expenses.length===1?'':'s'} all time<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.receipt} GST collected \u2014 this month<\/div><div class=\"stat-value\">${fmtMoney(gstThisMonth)}<\/div><div class=\"stat-note\">${fmtMoney(invoicedThisMonth)} invoiced in ${new Date().toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/div><\/div>\n    <\/div>\n    <div class=\"section-title\">This month's business<\/div>\n    <div class=\"grid grid-3\">\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.trendUp} Revenue (invoiced)<\/div><div class=\"stat-value\">${fmtMoney(invoicedThisMonth)}<\/div><div class=\"stat-note\">${new Date().toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${ICONS.expense} Expenses<\/div><div class=\"stat-value\" style=\"color:var(--danger)\">${fmtMoney(expensesThisMonth)}<\/div><div class=\"stat-note\">${DB.expenses.filter(e=>monthKey(e.date)===thisMonth).length} expense${DB.expenses.filter(e=>monthKey(e.date)===thisMonth).length===1?'':'s'} logged<\/div><\/div>\n      <div class=\"card stat-card\"><div class=\"stat-label\">${netProfitThisMonth>=0?ICONS.trendUp:ICONS.trendDown} Net profit<\/div><div class=\"stat-value\" style=\"color:${netProfitThisMonth>=0?'var(--success)':'var(--danger)'}\">${fmtMoney(netProfitThisMonth)}<\/div><div class=\"stat-note\">All time net: ${fmtMoney(netProfitAllTime)}<\/div><\/div>\n    <\/div>\n    <div class=\"section-title\">Recent invoices<\/div>\n    ${recent.length ? `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Invoice<\/th><th>Customer<\/th><th>Date<\/th><th class=\"right\">Amount<\/th><th>Type<\/th><th>Status<\/th><th><\/th><\/tr><\/thead>\n      <tbody>${recent.map(inv=>{\n        const t=computeInvoiceTotals(inv); const cust=customerById(inv.customerId);\n        return `<tr style=\"cursor:pointer\" onclick=\"navigate('invoices','${inv.id}')\">\n          <td class=\"mono\">${esc(inv.number)}<\/td><td>${esc(cust?cust.name:'\u2014')}<\/td><td>${fmtDate(inv.date)}<\/td>\n          <td class=\"right mono\">${fmtMoney(t.rounded)}<\/td><td>${invoiceTypeBadge(inv)}<\/td><td>${statusBadge(invoiceStatus(inv))}<\/td>\n          <td class=\"right\"><button class=\"btn btn-sm\" onclick=\"event.stopPropagation();navigate('invoices','${inv.id}')\">Open<\/button><\/td>\n        <\/tr>`;\n      }).join('')}<\/tbody><\/table><\/div>`\n      : `<div class=\"empty-state card\">${ICONS.empty}<h3>No invoices yet<\/h3><p>Create your first invoice to see it here.<\/p><button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button><\/div>`}\n  `;\n}\n\n\/* ---------- Invoices: list \/ router ---------- *\/\nfunction renderInvoices(){\n  if(VPARAM==='new'){ renderInvoiceEditor(null); return; }\n  if(VPARAM){ renderInvoiceDetail(VPARAM); return; }\n  renderInvoiceList();\n}\nlet INV_FILTER = 'all';\nfunction renderInvoiceList(){\n  document.getElementById('topActions').innerHTML = `<button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button>`;\n  let list = [...DB.invoices].sort((a,b)=> new Date(b.date)-new Date(a.date));\n  if(INV_FILTER!=='all') list = list.filter(inv=>invoiceStatus(inv)===INV_FILTER);\n  const pills = ['all','Unpaid','Partial','Paid'];\n  let body = `<div class=\"filter-pills\" style=\"margin-bottom:16px;\">${pills.map(p=>`<button class=\"pill ${INV_FILTER===p?'active':''}\" onclick=\"INV_FILTER='${p}';route()\">${p==='all'?'All':p}<\/button>`).join('')}<\/div>`;\n  if(!list.length){\n    body += `<div class=\"empty-state card\">${ICONS.empty}<h3>No invoices${INV_FILTER!=='all'?' with status '+INV_FILTER:''}<\/h3><p>Create an invoice for a tour package, flight, hotel or visa booking.<\/p>\n      <button class=\"btn btn-primary\" onclick=\"navigate('invoices','new')\">${ICONS.plus} New invoice<\/button><\/div>`;\n  }else{\n    body += `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Invoice<\/th><th>Customer<\/th><th>Date<\/th><th>Place of supply<\/th><th class=\"right\">Amount<\/th><th>Type<\/th><th>Status<\/th><th><\/th><\/tr><\/thead>\n      <tbody>${list.map(inv=>{\n        const t = computeInvoiceTotals(inv); const cust = customerById(inv.customerId);\n        return `<tr style=\"cursor:pointer\" onclick=\"navigate('invoices','${inv.id}')\">\n          <td class=\"mono\">${esc(inv.number)}<\/td>\n          <td>${esc(cust?cust.name:'\u2014')}<\/td>\n          <td>${fmtDate(inv.date)}<\/td>\n          <td>${esc(stateName(inv.customerStateCode))}<\/td>\n          <td class=\"right mono\">${fmtMoney(t.rounded)}<\/td>\n          <td>${invoiceTypeBadge(inv)}<\/td>\n          <td>${statusBadge(invoiceStatus(inv))}<\/td>\n          <td class=\"right\"><button class=\"btn btn-sm\" onclick=\"event.stopPropagation();navigate('invoices','${inv.id}')\">Open<\/button><\/td>\n        <\/tr>`;\n      }).join('')}<\/tbody><\/table><\/div>`;\n  }\n  document.getElementById('content').innerHTML = body;\n}\n\n\/* ---------- Invoice Editor ---------- *\/\nlet DRAFT = null;\nfunction newLine(){ return {id:uid(), itemId:'', description:'', hsn:'', qty:1, unit:'unit', rate:0, discountPct:0, serviceCharge:0, gstRate:5}; }\nfunction pad4(n){ return String(n).padStart(4,'0'); }\n\nfunction computeNextInvoiceNumber(type){\n  const b = DB.business;\n  const isBos = type === 'bos';\n  const basePrefix = isBos ? (b.bosInvoicePrefix || ((b.invoicePrefix||'INV')+'-BOS')) : (b.invoicePrefix||'INV');\n  const prefix = basePrefix + '-';\n  let maxNum = 0;\n  DB.invoices.forEach(inv=>{\n    if(((inv.invoiceType==='bos') === isBos) && inv.number && inv.number.startsWith(prefix)){\n      const suffix = inv.number.slice(prefix.length);\n      const n = parseInt(suffix, 10);\n      if(!isNaN(n) && n > maxNum) maxNum = n;\n    }\n  });\n  const manual = Number(b && (isBos ? b.nextBosInvoiceNumber : b.nextInvoiceNumber)) || 0;\n  return Math.max(maxNum + 1, manual);\n}\nfunction invoicePrefixForType(type){\n  const b = DB.business;\n  return type==='bos' ? (b.bosInvoicePrefix || ((b.invoicePrefix||'INV')+'-BOS')) : (b.invoicePrefix||'INV');\n}\n\nfunction customerNameForDraft(){\n  if(DRAFT.customerId){\n    const c = customerById(DRAFT.customerId);\n    return c ? c.name : (DRAFT.customerName||'');\n  }\n  return DRAFT.customerName||'';\n}\n\nfunction renderInvoiceEditor(existingId){\n  document.getElementById('topActions').innerHTML='';\n  if(existingId){\n    const inv = invoiceById(existingId);\n    DRAFT = JSON.parse(JSON.stringify(inv));\n    if(!DRAFT.invoiceType) DRAFT.invoiceType = 'tax';\n  }else{\n    const b = DB.business;\n    const autoNumber = invoicePrefixForType('tax')+'-'+pad4(computeNextInvoiceNumber('tax'));\n    DRAFT = {\n      id: uid(), __fresh:true, __autoNumber: autoNumber,\n      number: autoNumber,\n      date: todayStr(), dueDate:'', customerId:'', customerName:'', customerStateCode:'',\n      invoiceType:'tax', notes: b.terms||'', lines:[newLine()], payments:[]\n    };\n  }\n  document.getElementById('content').innerHTML = `\n    <div style=\"display:grid;grid-template-columns:1fr 300px;gap:20px;align-items:start;\">\n      <div>\n        <div class=\"card\">\n          <div class=\"section-title\" style=\"margin-top:0;\">Invoice details<\/div>\n          <div class=\"field\">\n            <label>Invoice type<\/label>\n            <div class=\"type-toggle\">\n              <button type=\"button\" id=\"typeBtnTax\" class=\"${DRAFT.invoiceType!=='bos'?'active':''}\" onclick=\"setDraftInvoiceType('tax')\">Tax Invoice (with GST)<\/button>\n              <button type=\"button\" id=\"typeBtnBos\" class=\"${DRAFT.invoiceType==='bos'?'active bos-active':''}\" onclick=\"setDraftInvoiceType('bos')\">Bill of Supply (Non-GST)<\/button>\n            <\/div>\n            <div class=\"field-hint\" id=\"typeHint\">${DRAFT.invoiceType==='bos' ? 'No CGST\/SGST\/IGST will be charged or shown on this invoice.' : 'GST is calculated per line item as usual.'}<\/div>\n          <\/div>\n          <div class=\"field-row\">\n            <div class=\"field\"><label>Invoice number<\/label><input id=\"d_number\" value=\"${esc(DRAFT.number)}\"><\/div>\n            <div class=\"field\"><label>Customer<\/label>\n              <input id=\"d_customer\" list=\"customerListOptions\" autocomplete=\"off\" placeholder=\"Type a customer name\u2026\" value=\"${esc(customerNameForDraft())}\" oninput=\"draftCustomerChanged()\">\n              <datalist id=\"customerListOptions\">${DB.customers.map(c=>`<option value=\"${esc(c.name)}\"><\/option>`).join('')}<\/datalist>\n            <\/div>\n          <\/div>\n          <div class=\"field-row\">\n            <div class=\"field\"><label>Invoice date<\/label><input id=\"d_date\" type=\"date\" value=\"${DRAFT.date}\"><\/div>\n            <div class=\"field\"><label>Due date<\/label><input id=\"d_due\" type=\"date\" value=\"${DRAFT.dueDate||''}\"><\/div>\n          <\/div>\n          <div class=\"field-hint\">Start typing to see matching customers, or type a new name to create one when you save. <a href=\"#\" onclick=\"event.preventDefault();openCustomerForm()\">Add a customer with full details instead<\/a>.<\/div>\n        <\/div>\n\n        <div class=\"section-title\">Line items<\/div>\n        <div class=\"card\" style=\"padding:14px 16px;\">\n          <table class=\"line-table\" id=\"lineTable\">\n            <thead><tr><th style=\"width:20%\">Service<\/th><th style=\"width:8%\">HSN\/SAC<\/th><th style=\"width:6%\">Qty<\/th><th style=\"width:9%\">Rate \u20b9<\/th><th style=\"width:6%\">Disc %<\/th><th style=\"width:11%\">Service Charge \u20b9<\/th><th style=\"width:7%\">GST %<\/th><th class=\"right\" style=\"width:11%\">Amount<\/th><th><\/th><\/tr><\/thead>\n            <tbody id=\"lineBody\"><\/tbody>\n          <\/table>\n          <button class=\"btn btn-sm\" style=\"margin-top:10px\" onclick=\"addDraftLine()\">${ICONS.plus} Add line<\/button>\n        <\/div>\n\n        <div class=\"section-title\">Notes \/ terms<\/div>\n        <div class=\"card\"><textarea id=\"d_notes\" style=\"min-height:70px;border:none;padding:0;width:100%;\" placeholder=\"Payment terms, cancellation policy, etc.\">${esc(DRAFT.notes)}<\/textarea><\/div>\n      <\/div>\n\n      <div style=\"position:sticky;top:80px;\">\n        <div class=\"card\" id=\"totalsCard\"><\/div>\n        <div style=\"display:flex;flex-direction:column;gap:8px;margin-top:14px;\">\n          <button class=\"btn btn-primary\" onclick=\"saveDraft()\">${ICONS.check} Save invoice<\/button>\n          <button class=\"btn\" onclick=\"navigate('invoices')\">Cancel<\/button>\n        <\/div>\n      <\/div>\n    <\/div>\n  `;\n  renderLineBody();\n  renderTotalsCard();\n}\n\nfunction setDraftInvoiceType(type){\n  DRAFT.invoiceType = type;\n  const taxBtn = document.getElementById('typeBtnTax');\n  const bosBtn = document.getElementById('typeBtnBos');\n  taxBtn.className = type!=='bos' ? 'active' : '';\n  bosBtn.className = type==='bos' ? 'active bos-active' : '';\n  document.getElementById('typeHint').textContent = type==='bos'\n    ? 'No CGST\/SGST\/IGST will be charged or shown on this invoice. This invoice will use the separate Bill of Supply numbering series.'\n    : 'GST is calculated per line item as usual.';\n  if(DRAFT.__fresh){\n    const numberField = document.getElementById('d_number');\n    const stillAuto = numberField && numberField.value.trim() === (DRAFT.__autoNumber||'');\n    if(stillAuto){\n      const newAuto = invoicePrefixForType(type)+'-'+pad4(computeNextInvoiceNumber(type));\n      DRAFT.__autoNumber = newAuto;\n      DRAFT.number = newAuto;\n      if(numberField) numberField.value = newAuto;\n    }\n  }\n  renderLineBody();\n  renderTotalsCard();\n}\n\nfunction draftCustomerChanged(){\n  const val = document.getElementById('d_customer').value.trim();\n  const match = DB.customers.find(c=>c.name.toLowerCase()===val.toLowerCase());\n  if(match){\n    DRAFT.customerId = match.id;\n    DRAFT.customerStateCode = match.stateCode;\n    DRAFT.customerName = '';\n  }else{\n    DRAFT.customerId = '';\n    DRAFT.customerName = val;\n    DRAFT.customerStateCode = '';\n  }\n  renderLineBody();\n  renderTotalsCard();\n}\n\nfunction renderLineBody(){\n  const tbody = document.getElementById('lineBody');\n  const nonGst = DRAFT.invoiceType === 'bos';\n  tbody.innerHTML = DRAFT.lines.map((l,idx)=>`\n    <tr>\n      <td>\n        <select onchange=\"pickPreset(${idx}, this.value)\">\n          <option value=\"\">Custom\u2026<\/option>\n          ${DB.items.map(it=>`<option value=\"${it.id}\" ${l.itemId===it.id?'selected':''}>${esc(it.name)}<\/option>`).join('')}\n        <\/select>\n        <input style=\"margin-top:5px\" placeholder=\"Description\" value=\"${esc(l.description)}\" oninput=\"updateLineField(${idx},'description',this.value,false)\">\n      <\/td>\n      <td><input value=\"${esc(l.hsn)}\" oninput=\"updateLineField(${idx},'hsn',this.value,false)\"><\/td>\n      <td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.qty}\" oninput=\"updateLineField(${idx},'qty',this.value,true)\"><\/td>\n      <td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.rate}\" oninput=\"updateLineField(${idx},'rate',this.value,true)\"><\/td>\n      <td><input type=\"number\" min=\"0\" max=\"100\" step=\"0.01\" value=\"${l.discountPct}\" oninput=\"updateLineField(${idx},'discountPct',this.value,true)\"><\/td>\n      <td><input type=\"number\" min=\"0\" step=\"0.01\" value=\"${l.serviceCharge||0}\" oninput=\"updateLineField(${idx},'serviceCharge',this.value,true)\"><\/td>\n      <td>${nonGst\n          ? `<select disabled title=\"No GST on a Bill of Supply\"><option>\u2014<\/option><\/select>`\n          : `<select onchange=\"updateLineField(${idx},'gstRate',this.value,true)\">${GST_RATES.map(r=>`<option value=\"${r}\" ${l.gstRate==r?'selected':''}>${r}%<\/option>`).join('')}<\/select>`}\n      <\/td>\n      <td class=\"right mono\" id=\"lineAmt_${l.id}\">${fmtMoney(computeLine({...l,__intra:isSameState(),__nonGst:nonGst}).total)}<\/td>\n      <td class=\"line-actions\"><button class=\"btn btn-sm btn-icon btn-danger-ghost\" onclick=\"removeDraftLine(${idx})\" aria-label=\"Remove\">${ICONS.trash}<\/button><\/td>\n    <\/tr>\n  `).join('');\n}\nfunction isSameState(){ return DB.business && DRAFT.customerStateCode && DB.business.stateCode===DRAFT.customerStateCode; }\n\nfunction pickPreset(idx, itemId){\n  const it = itemById(itemId);\n  if(!it){ DRAFT.lines[idx].itemId=''; return; }\n  DRAFT.lines[idx] = {...DRAFT.lines[idx], itemId, description:it.name, hsn:it.hsn, rate:it.rate, gstRate:it.gstRate, unit:it.unit};\n  renderLineBody();\n  renderTotalsCard();\n}\nfunction updateLineField(idx, field, value, numeric){\n  DRAFT.lines[idx][field] = numeric ? Number(value)||0 : value;\n  const l = DRAFT.lines[idx];\n  const amt = document.getElementById('lineAmt_'+l.id);\n  if(amt) amt.textContent = fmtMoney(computeLine({...l,__intra:isSameState(),__nonGst:DRAFT.invoiceType==='bos'}).total);\n  renderTotalsCard();\n}\nfunction addDraftLine(){ DRAFT.lines.push(newLine()); renderLineBody(); renderTotalsCard(); }\nfunction removeDraftLine(idx){\n  if(DRAFT.lines.length===1){ toast('Invoice needs at least one line'); return; }\n  DRAFT.lines.splice(idx,1); renderLineBody(); renderTotalsCard();\n}\n\nfunction renderTotalsCard(){\n  const t = computeInvoiceTotals(DRAFT);\n  const intraLabel = DRAFT.customerStateCode ? (t.intra?'Intra-state (CGST + SGST)':'Inter-state (IGST)') : 'Select or type a customer';\n  document.getElementById('totalsCard').innerHTML = `\n    <div class=\"section-title\" style=\"margin-top:0;\">Summary<\/div>\n    <div class=\"ip-totals\" style=\"width:100%;margin:0;\">\n      <div class=\"row\"><span>Base amount (non-taxable)<\/span><span class=\"mono\">${fmtMoney(t.baseTotal)}<\/span><\/div>\n      <div class=\"row\"><span>Service charge${t.nonGst?'':' (taxable value)'}<\/span><span class=\"mono\">${fmtMoney(t.taxable)}<\/span><\/div>\n      ${t.nonGst ? `\n        <div class=\"row\"><span>GST<\/span><span class=\"mono\">Not applicable<\/span><\/div>\n      ` : (t.intra ? `\n        <div class=\"row\"><span>CGST<\/span><span class=\"mono\">${fmtMoney(t.cgst)}<\/span><\/div>\n        <div class=\"row\"><span>SGST<\/span><span class=\"mono\">${fmtMoney(t.sgst)}<\/span><\/div>\n      ` : `<div class=\"row\"><span>IGST<\/span><span class=\"mono\">${fmtMoney(t.igst)}<\/span><\/div>`)}\n      <div class=\"row\"><span>Round off<\/span><span class=\"mono\">${fmtMoney(t.roundOff)}<\/span><\/div>\n      <div class=\"row grand\"><span>Total<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n    <\/div>\n    <div class=\"field-hint\" style=\"margin-top:10px;\">${t.nonGst ? 'This is a Bill of Supply \u2014 no GST is charged.' : `GST is applied only on the service charge, not on the base amount. ${intraLabel}`}<\/div>\n  `;\n}\n\nasync function saveDraft(){\n  DRAFT.number = document.getElementById('d_number').value.trim();\n  DRAFT.date = document.getElementById('d_date').value || todayStr();\n  DRAFT.dueDate = document.getElementById('d_due').value || '';\n  DRAFT.notes = document.getElementById('d_notes').value;\n\n  const typedName = document.getElementById('d_customer').value.trim();\n  if(!DRAFT.customerId && typedName){\n    let match = DB.customers.find(c=>c.name.toLowerCase()===typedName.toLowerCase());\n    if(!match){\n      match = {\n        id: uid(), name: typedName, gstin:'',\n        stateCode: DRAFT.customerStateCode || (DB.business?DB.business.stateCode:''),\n        address:'', phone:'', email:''\n      };\n      DB.customers.push(match);\n      await saveCustomers();\n    }\n    DRAFT.customerId = match.id;\n    DRAFT.customerStateCode = match.stateCode;\n  }\n  delete DRAFT.customerName;\n\n  if(!DRAFT.customerId){ toast('Please type or select a customer'); return; }\n  if(!DRAFT.number){ toast('Invoice number is required'); return; }\n  const hasAmount = DRAFT.lines.some(l=> (Number(l.qty)||0)*(Number(l.rate)||0) > 0 || (Number(l.serviceCharge)||0) > 0);\n  if(!hasAmount){ toast('Add at least one line item with an amount'); return; }\n\n  const isNew = DRAFT.__fresh === true;\n  delete DRAFT.__fresh;\n  if(isNew){\n    DB.invoices.push(DRAFT);\n  }else{\n    const idx = DB.invoices.findIndex(i=>i.id===DRAFT.id);\n    DB.invoices[idx] = DRAFT;\n  }\n  await saveInvoices();\n  const savedId = DRAFT.id;\n  DRAFT = null;\n  toast('Invoice saved');\n  navigate('invoices', savedId);\n}\n\n\/* ---------- Invoice detail \/ preview ---------- *\/\nfunction buildInvoicePreviewHTML(inv){\n  const b = DB.business;\n  const c = customerById(inv.customerId) || {name:'Customer', address:'', stateCode:'', gstin:''};\n  const t = computeInvoiceTotals(inv);\n  const docLabel = t.nonGst ? 'Bill of Supply' : 'Tax Invoice';\n  return `\n  <div class=\"invoice-preview\">\n    <div class=\"ip-head\">\n      <div style=\"display:flex;align-items:center;gap:12px;\">\n        ${b.logo? `<div style=\"width:44px;height:44px;border-radius:8px;background:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden;flex-shrink:0;\"><img decoding=\"async\" src=\"${esc(b.logo)}\" style=\"max-width:100%;max-height:100%;object-fit:contain;\"><\/div>`:''}\n        <div>\n          <div class=\"ip-brand\">${esc(b.name)}<\/div>\n          <div class=\"ip-brand-sub\">${t.nonGst ? '' : esc(b.gstin? 'GSTIN '+b.gstin : '')}<\/div>\n          <div class=\"ip-brand-sub\">${esc([b.address,b.city,b.pincode].filter(Boolean).join(', '))}<\/div>\n        <\/div>\n      <\/div>\n      <div class=\"ip-invno\">\n        <div class=\"ip-doctype\">${docLabel}<\/div>\n        <div style=\"font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:#B9C4DE;margin-bottom:2px;\">Invoice No.<\/div>\n        <div class=\"big\">${esc(inv.number)}<\/div>\n        <div>Date ${fmtDate(inv.date)}<\/div>\n        ${inv.dueDate?`<div>Due ${fmtDate(inv.dueDate)}<\/div>`:''}\n      <\/div>\n    <\/div>\n    <div class=\"ip-route\">\n      <div class=\"ip-party\">\n        <div class=\"lbl\">Billed by<\/div>\n        <div class=\"name\">${esc(b.name)}<\/div>\n        <div class=\"meta\">${esc(b.state)} (${esc(b.stateCode)})${b.phone?' \u00b7 '+esc(b.phone):''}${b.email?' \u00b7 '+esc(b.email):''}<\/div>\n      <\/div>\n      <div class=\"ip-party\" style=\"text-align:right;\">\n        <div class=\"lbl\">Billed to<\/div>\n        <div class=\"name\">${esc(c.name)}<\/div>\n        <div class=\"meta\">${(!t.nonGst && c.gstin)? esc('GSTIN '+c.gstin)+'<br>' : ''}${esc(stateName(c.stateCode))}${c.stateCode? ' ('+esc(c.stateCode)+')':''}<\/div>\n      <\/div>\n    <\/div>\n    <div class=\"perforation\"><\/div>\n    ${t.nonGst ? `<div class=\"ip-bos-note\">This is a Bill of Supply. No GST is chargeable on this supply.<\/div>` : ''}\n    <div class=\"ip-body\">\n      <table>\n        <thead><tr><th>Service<\/th><th>HSN\/SAC<\/th><th class=\"right\">Qty<\/th><th class=\"right\">Rate<\/th><th class=\"right\">Amount<\/th><th class=\"right\">Service Charge<\/th>${t.nonGst?'':'<th class=\"right\">GST<\/th>'}<th class=\"right\">Line Total<\/th><\/tr><\/thead>\n        <tbody>${t.lines.map(l=>`<tr>\n          <td>${esc(l.description||'Service')}<\/td>\n          <td class=\"mono\">${esc(l.hsn)}<\/td>\n          <td class=\"right mono\">${fmtNum(l.qty)}<\/td>\n          <td class=\"right mono\">${fmtMoney(l.rate)}<\/td>\n          <td class=\"right mono\">${fmtMoney(l.baseAfterDiscount)}<\/td>\n          <td class=\"right mono\">${fmtMoney(l.serviceCharge)}<\/td>\n          ${t.nonGst?'':`<td class=\"right mono\">${l.gstRate}%<\/td>`}\n          <td class=\"right mono\">${fmtMoney(l.total)}<\/td>\n        <\/tr>`).join('')}<\/tbody>\n      <\/table>\n      <div class=\"ip-totals\">\n        <div class=\"row\"><span>Base amount (non-taxable)<\/span><span class=\"mono\">${fmtMoney(t.baseTotal)}<\/span><\/div>\n        <div class=\"row\"><span>Service charge<\/span><span class=\"mono\">${fmtMoney(t.taxable)}<\/span><\/div>\n        ${t.nonGst ? '' : (t.intra ? `\n          <div class=\"row\"><span>CGST<\/span><span class=\"mono\">${fmtMoney(t.cgst)}<\/span><\/div>\n          <div class=\"row\"><span>SGST<\/span><span class=\"mono\">${fmtMoney(t.sgst)}<\/span><\/div>\n        ` : `<div class=\"row\"><span>IGST<\/span><span class=\"mono\">${fmtMoney(t.igst)}<\/span><\/div>`)}\n        <div class=\"row\"><span>Round off<\/span><span class=\"mono\">${fmtMoney(t.roundOff)}<\/span><\/div>\n        <div class=\"row grand\"><span>Total due<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n      <\/div>\n    <\/div>\n    <div class=\"ip-foot\">\n      <div>\n        <h4>Bank details<\/h4>\n        ${b.bankName?`${esc(b.bankName)}<br>A\/C ${esc(b.bankAccount)}<br>IFSC ${esc(b.bankIFSC)}${b.upiId?'<br>UPI '+esc(b.upiId):''}`:'\u2014'}\n      <\/div>\n      <div>\n        <h4>Notes<\/h4>\n        ${esc(inv.notes)||'\u2014'}\n      <\/div>\n    <\/div>\n    <div class=\"ip-thankyou\">Thank you for your business!<\/div>\n    <div class=\"ip-disclaimer\">\n      <div style=\"display:flex;justify-content:flex-end;\">\n        <div style=\"min-width:210px;text-align:center;font-size:11px;font-weight:700;color:var(--text-faint);\">This bill is auto generated and does<br>not require a physical signature.<\/div>\n      <\/div>\n    <\/div>\n    <div class=\"ip-signature\">\n      <div style=\"text-align:center;min-width:210px;\">\n        <div style=\"height:6px;\"><\/div>\n        <div style=\"border-top:1px solid var(--text-mute);padding-top:6px;font-size:12.5px;font-weight:600;color:var(--ink);\">Authorized Signatory<\/div>\n        <div style=\"font-size:10.5px;color:var(--text-faint);margin-top:3px;\">for ${esc(b.name)}<\/div>\n      <\/div>\n    <\/div>\n  <\/div>`;\n}\n\nfunction renderInvoiceDetail(id){\n  const inv = invoiceById(id);\n  if(!inv){ navigate('invoices'); return; }\n  const status = invoiceStatus(inv);\n  const t = computeInvoiceTotals(inv);\n  const paid = invoicePaid(inv);\n  document.getElementById('topActions').innerHTML = `\n    <div style=\"display:flex;gap:8px;\">\n      <button class=\"btn\" onclick=\"navigate('invoices')\">Back<\/button>\n      <button class=\"btn\" onclick=\"editInvoice('${inv.id}')\">${ICONS.edit} Edit<\/button>\n      <button class=\"btn\" onclick=\"printInvoice('${inv.id}')\">${ICONS.print} Print \/ PDF<\/button>\n      <button class=\"btn btn-danger-ghost\" onclick=\"deleteInvoice('${inv.id}')\">${ICONS.trash} Delete<\/button>\n    <\/div>`;\n  document.getElementById('content').innerHTML = `\n    <div style=\"display:grid;grid-template-columns:1fr 300px;gap:20px;align-items:start;\">\n      <div>${buildInvoicePreviewHTML(inv)}<\/div>\n      <div>\n        <div class=\"card\">\n          <div class=\"section-title\" style=\"margin-top:0;\">Payment status<\/div>\n          <div style=\"margin-bottom:10px;display:flex;gap:6px;flex-wrap:wrap;\">${invoiceTypeBadge(inv)}${statusBadge(status)}<\/div>\n          <div class=\"ip-totals\" style=\"width:100%;margin:0;\">\n            <div class=\"row\"><span>Invoice total<\/span><span class=\"mono\">${fmtMoney(t.rounded)}<\/span><\/div>\n            <div class=\"row\"><span>Received<\/span><span class=\"mono\">${fmtMoney(paid)}<\/span><\/div>\n            <div class=\"row grand\"><span>Balance due<\/span><span class=\"mono\">${fmtMoney(Math.max(0,t.rounded-paid))}<\/span><\/div>\n          <\/div>\n          ${status!=='Paid' ? `<button class=\"btn btn-amber\" style=\"width:100%;margin-top:12px;\" onclick=\"openPaymentForm('${inv.id}')\">${ICONS.money} Record payment<\/button>`:''}\n        <\/div>\n        ${inv.payments && inv.payments.length ? `\n        <div class=\"section-title\">Payment history<\/div>\n        <div class=\"table-wrap\"><table>\n          <thead><tr><th>Date<\/th><th>Mode<\/th><th class=\"right\">Amount<\/th><\/tr><\/thead>\n          <tbody>${inv.payments.map(p=>`<tr><td>${fmtDate(p.date)}<\/td><td>${esc(p.mode)}<\/td><td class=\"right mono\">${fmtMoney(p.amount)}<\/td><\/tr>`).join('')}<\/tbody>\n        <\/table><\/div>` : ''}\n      <\/div>\n    <\/div>\n  `;\n}\n\nfunction editInvoice(id){ DRAFT=null; navigate('invoices'); VIEW='invoices'; VPARAM='new'; renderShell(); renderInvoiceEditor(id); }\n\nasync function deleteInvoice(id){\n  if(!confirm('Delete this invoice? This cannot be undone.')) return;\n  DB.invoices = DB.invoices.filter(i=>i.id!==id);\n  await saveInvoices();\n  toast('Invoice deleted');\n  navigate('invoices');\n}\n\nfunction openPaymentForm(invId){\n  const inv = invoiceById(invId);\n  const t = computeInvoiceTotals(inv);\n  const balance = Math.max(0, t.rounded - invoicePaid(inv));\n  openDrawer(`\n    <div class=\"drawer-head\"><h3>Record payment<\/h3><button class=\"btn btn-icon\" onclick=\"closeDrawer()\">${ICONS.x}<\/button><\/div>\n    <div class=\"drawer-body\">\n      <div class=\"field-hint\" style=\"margin-bottom:12px;\">Balance due: <strong>${fmtMoney(balance)}<\/strong><\/div>\n      <div class=\"field-row\">\n        <div class=\"field\"><label>Amount received (\u20b9)<\/label><input id=\"p_amount\" type=\"number\" min=\"0\" step=\"0.01\" value=\"${balance.toFixed(2)}\"><\/div>\n        <div class=\"field\"><label>Date<\/label><input id=\"p_date\" type=\"date\" value=\"${todayStr()}\"><\/div>\n      <\/div>\n      <div class=\"field\"><label>Mode<\/label><select id=\"p_mode\">${PAY_MODES.map(m=>`<option>${m}<\/option>`).join('')}<\/select><\/div>\n      <div class=\"field\"><label>Note (optional)<\/label><input id=\"p_note\" placeholder=\"Reference \/ transaction ID\"><\/div>\n    <\/div>\n    <div class=\"drawer-foot\">\n      <button class=\"btn\" onclick=\"closeDrawer()\">Cancel<\/button>\n      <button class=\"btn btn-primary\" onclick=\"savePayment('${invId}')\">${ICONS.check} Record payment<\/button>\n    <\/div>\n  `);\n}\nasync function savePayment(invId){\n  const amount = Number(document.getElementById('p_amount').value)||0;\n  if(amount<=0){ toast('Enter a valid amount'); return; }\n  const inv = invoiceById(invId);\n  inv.payments = inv.payments || [];\n  inv.payments.push({ amount, date: document.getElementById('p_date').value||todayStr(), mode: document.getElementById('p_mode').value, note: document.getElementById('p_note').value });\n  await saveInvoices();\n  closeDrawer();\n  toast('Payment recorded');\n  navigate('invoices', invId);\n}\n\nfunction printInvoice(id){\n  const inv = invoiceById(id);\n  document.getElementById('printArea').innerHTML = buildInvoicePreviewHTML(inv);\n  window.print();\n}\n\n\/* ---------- GST Reports ---------- *\/\nfunction renderReports(){\n  document.getElementById('topActions').innerHTML = '';\n  if(!DB.invoices.length &#038;&#038; !DB.expenses.length){\n    document.getElementById('content').innerHTML = `<div class=\"empty-state card\">${ICONS.empty}<h3>No data to report yet<\/h3><p>Reports populate once you have invoices or expenses.<\/p><\/div>`;\n    return;\n  }\n  const byMonth = {};\n  DB.invoices.forEach(inv=>{\n    const k = monthKey(inv.date);\n    const t = computeInvoiceTotals(inv);\n    if(!byMonth[k]) byMonth[k] = {taxable:0,cgst:0,sgst:0,igst:0,total:0,count:0,bosCount:0,expenses:0};\n    byMonth[k].taxable += t.taxable; byMonth[k].cgst += t.cgst; byMonth[k].sgst += t.sgst; byMonth[k].igst += t.igst; byMonth[k].total += t.rounded; byMonth[k].count++;\n    if(t.nonGst) byMonth[k].bosCount++;\n  });\n  DB.expenses.forEach(e=>{\n    const k = monthKey(e.date);\n    if(!byMonth[k]) byMonth[k] = {taxable:0,cgst:0,sgst:0,igst:0,total:0,count:0,bosCount:0,expenses:0};\n    byMonth[k].expenses += Number(e.amount)||0;\n  });\n  const months = Object.keys(byMonth).sort().reverse();\n  const gstMonths = months.filter(m=>byMonth[m].count>0);\n\n  const byExpCat = {};\n  DB.expenses.forEach(e=>{\n    const cat = e.category||'Other';\n    if(!byExpCat[cat]) byExpCat[cat] = 0;\n    byExpCat[cat] += Number(e.amount)||0;\n  });\n  const expCats = Object.keys(byExpCat).sort((a,b)=>byExpCat[b]-byExpCat[a]);\n  const totalExpAll = totalExpensesFor(DB.expenses);\n\n  const byRate = {};\n  DB.invoices.forEach(inv=>{\n    const t = computeInvoiceTotals(inv);\n    t.lines.forEach(l=>{\n      const r = t.nonGst ? 'Non-GST' : l.gstRate;\n      if(!byRate[r]) byRate[r] = {taxable:0,tax:0};\n      byRate[r].taxable += l.taxable; byRate[r].tax += (l.cgst+l.sgst+l.igst);\n    });\n  });\n  const rates = Object.keys(byRate).sort((a,b)=>{\n    if(a==='Non-GST') return 1;\n    if(b==='Non-GST') return -1;\n    return a-b;\n  });\n\n  const byCust = {};\n  DB.invoices.forEach(inv=>{\n    const t = computeInvoiceTotals(inv);\n    const bal = Math.max(0, t.rounded - invoicePaid(inv));\n    if(bal<=0) return;\n    const c = customerById(inv.customerId);\n    const key = c?c.id:'unknown';\n    if(!byCust[key]) byCust[key] = {name:c?c.name:'Unknown', balance:0, count:0};\n    byCust[key].balance += bal; byCust[key].count++;\n  });\n  const custRows = Object.values(byCust).sort((a,b)=>b.balance-a.balance);\n\n  document.getElementById('content').innerHTML = `\n    <div class=\"section-title\" style=\"margin-top:0;\">Monthly GST summary<\/div>\n    <div class=\"field-hint\" style=\"margin-bottom:10px;\">Bill of Supply (non-GST) invoices are included in invoice counts but contribute \u20b90 tax.<\/div>\n    ${gstMonths.length ? `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Month<\/th><th class=\"right\">Invoices<\/th><th class=\"right\">Non-GST<\/th><th class=\"right\">Taxable value<\/th><th class=\"right\">CGST<\/th><th class=\"right\">SGST<\/th><th class=\"right\">IGST<\/th><th class=\"right\">Total tax<\/th><\/tr><\/thead>\n      <tbody>${gstMonths.map(m=>{const r=byMonth[m];return `<tr>\n        <td>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/td>\n        <td class=\"right\">${r.count}<\/td>\n        <td class=\"right\">${r.bosCount||0}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.taxable)}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.cgst)}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.sgst)}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.igst)}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.cgst+r.sgst+r.igst)}<\/td>\n      <\/tr>`;}).join('')}<\/tbody>\n    <\/table><\/div>` : `<div class=\"card\" style=\"color:var(--text-mute)\">No invoices yet.<\/div>`}\n\n    <div class=\"section-title\">Profit &amp; Loss by month<\/div>\n    <div class=\"field-hint\" style=\"margin-bottom:10px;\">Revenue is invoiced amount (including GST); Net profit = Revenue \u2212 Expenses.<\/div>\n    ${months.length ? `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Month<\/th><th class=\"right\">Revenue (invoiced)<\/th><th class=\"right\">Expenses<\/th><th class=\"right\">Net profit<\/th><\/tr><\/thead>\n      <tbody>${months.map(m=>{const r=byMonth[m]; const net=r.total-r.expenses; return `<tr>\n        <td>${new Date(m+'-01').toLocaleDateString('en-IN',{month:'long',year:'numeric'})}<\/td>\n        <td class=\"right mono\">${fmtMoney(r.total)}<\/td>\n        <td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(r.expenses)}<\/td>\n        <td class=\"right mono\" style=\"color:${net>=0?'var(--success)':'var(--danger)'}\">${fmtMoney(net)}<\/td>\n      <\/tr>`;}).join('')}<\/tbody>\n    <\/table><\/div>` : `<div class=\"card\" style=\"color:var(--text-mute)\">No data yet.<\/div>`}\n\n    <div class=\"section-title\">Expenses by category<\/div>\n    ${expCats.length ? `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Category<\/th><th class=\"right\">Amount<\/th><th class=\"right\">% of total<\/th><\/tr><\/thead>\n      <tbody>${expCats.map(c=>`<tr><td>${esc(c)}<\/td><td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(byExpCat[c])}<\/td><td class=\"right mono\">${totalExpAll>0?((byExpCat[c]\/totalExpAll)*100).toFixed(1):'0.0'}%<\/td><\/tr>`).join('')}<\/tbody>\n    <\/table><\/div>` : `<div class=\"card\" style=\"color:var(--text-mute)\">No expenses logged yet.<\/div>`}\n\n    <div class=\"section-title\">Tax rate-wise summary<\/div>\n    <div class=\"table-wrap\"><table>\n      <thead><tr><th>GST rate<\/th><th class=\"right\">Taxable value<\/th><th class=\"right\">Tax amount<\/th><\/tr><\/thead>\n      <tbody>${rates.map(r=>`<tr><td>${r==='Non-GST'?'Non-GST (Bill of Supply)':r+'%'}<\/td><td class=\"right mono\">${fmtMoney(byRate[r].taxable)}<\/td><td class=\"right mono\">${fmtMoney(byRate[r].tax)}<\/td><\/tr>`).join('')}<\/tbody>\n    <\/table><\/div>\n\n    <div class=\"section-title\">Outstanding by customer<\/div>\n    ${custRows.length ? `<div class=\"table-wrap\"><table>\n      <thead><tr><th>Customer<\/th><th class=\"right\">Unpaid invoices<\/th><th class=\"right\">Balance due<\/th><\/tr><\/thead>\n      <tbody>${custRows.map(r=>`<tr><td>${esc(r.name)}<\/td><td class=\"right\">${r.count}<\/td><td class=\"right mono\" style=\"color:var(--danger)\">${fmtMoney(r.balance)}<\/td><\/tr>`).join('')}<\/tbody>\n    <\/table><\/div>` : `<div class=\"card\" style=\"color:var(--text-mute)\">Nothing outstanding \u2014 all invoices are fully paid.<\/div>`}\n  `;\n}\n\n\/* =====================================================================\n   AUTH \u2014 simple in-app username\/password gate\n   ===================================================================== *\/\nlet AUTH = { username:'', unlocked:false };\n\nasync function sha256Hex(str){\n  const enc = new TextEncoder().encode(str);\n  const buf = await crypto.subtle.digest('SHA-256', enc);\n  return Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,'0')).join('');\n}\nfunction randomSalt(){\n  const arr = new Uint8Array(16);\n  crypto.getRandomValues(arr);\n  return Array.from(arr).map(b=>b.toString(16).padStart(2,'0')).join('');\n}\nasync function getAuthDoc(){ return await safeGet('auth', null); }\nasync function setAuthDoc(doc){ return await saveKey('auth', doc); }\n\nlet AUTH_LOGO = DEFAULT_LOGO;\nlet AUTH_BIZNAME = 'Sarvalok Bhraman';\nasync function loadAuthBranding(){\n  try{\n    const biz = await safeGet('business', null);\n    if(biz){\n      if(biz.logo) AUTH_LOGO = biz.logo;\n      if(biz.name) AUTH_BIZNAME = biz.name;\n    }\n  }catch(e){ \/* fall back to defaults silently *\/ }\n}\n\nfunction authShell(innerHTML){\n  document.getElementById('app').style.display='none';\n  document.getElementById('authScreen').style.display='flex';\n  document.getElementById('authScreen').innerHTML = `\n    <div class=\"auth-card\">\n      <div class=\"auth-brand\">\n        <div class=\"logo-wrap\"><img decoding=\"async\" src=\"${esc(AUTH_LOGO)}\" alt=\"\"><\/div>\n        <div>\n          <div class=\"t1\">${esc(AUTH_BIZNAME)}<\/div>\n          <div class=\"t2\">GST Billing \u2014 secure sign in<\/div>\n        <\/div>\n      <\/div>\n      ${innerHTML}\n    <\/div>\n  `;\n}\n\nfunction renderAuthSetup(){\n  authShell(`\n    <div class=\"auth-title\">Create your login<\/div>\n    <div class=\"auth-sub\">No login exists yet for this app. Set a username and password \u2014 you'll need them every time you open this page.<\/div>\n    <div id=\"authError\" class=\"auth-error\"><\/div>\n    <div class=\"field\"><label>Username<\/label><input id=\"su_username\" placeholder=\"e.g. admin\" autofocus><\/div>\n    <div class=\"field\"><label>Password<\/label><input id=\"su_password\" type=\"password\" placeholder=\"Choose a password\"><\/div>\n    <div class=\"field\"><label>Confirm password<\/label><input id=\"su_password2\" type=\"password\"><\/div>\n    <button class=\"btn btn-primary\" style=\"width:100%;justify-content:center;\" onclick=\"submitAuthSetup()\">${ICONS.lock} Create login & continue<\/button>\n  `);\n  document.getElementById('su_password2').addEventListener('keydown', e=>{ if(e.key==='Enter') submitAuthSetup(); });\n}\n\nasync function submitAuthSetup(){\n  const username = document.getElementById('su_username').value.trim();\n  const pw = document.getElementById('su_password').value;\n  const pw2 = document.getElementById('su_password2').value;\n  const err = document.getElementById('authError');\n  err.classList.remove('show');\n  if(!username){ err.textContent='Username is required.'; err.classList.add('show'); return; }\n  if(pw.length < 4){ err.textContent='Password must be at least 4 characters.'; err.classList.add('show'); return; }\n  if(pw !== pw2){ err.textContent='Passwords do not match.'; err.classList.add('show'); return; }\n  const salt = randomSalt();\n  const hash = await sha256Hex(salt + pw);\n  const ok = await setAuthDoc({ username, salt, hash });\n  if(!ok){ err.textContent='Could not save login \u2014 check your connection and try again.'; err.classList.add('show'); return; }\n  AUTH = { username, unlocked:true };\n  toast('Login created');\n  unlockApp();\n}\n\nfunction renderAuthLogin(existingUsername){\n  authShell(`\n    <div class=\"auth-title\">Sign in<\/div>\n    <div class=\"auth-sub\">Enter your username and password to open the billing app.<\/div>\n    <div id=\"authError\" class=\"auth-error\"><\/div>\n    <div class=\"field\"><label>Username<\/label><input id=\"li_username\" value=\"${esc(existingUsername||'')}\" autofocus><\/div>\n    <div class=\"field\"><label>Password<\/label><input id=\"li_password\" type=\"password\"><\/div>\n    <button class=\"btn btn-primary\" style=\"width:100%;justify-content:center;\" onclick=\"submitAuthLogin()\">${ICONS.lock} Sign in<\/button>\n  `);\n  document.getElementById('li_password').addEventListener('keydown', e=>{ if(e.key==='Enter') submitAuthLogin(); });\n}\n\nasync function submitAuthLogin(){\n  const username = document.getElementById('li_username').value.trim();\n  const pw = document.getElementById('li_password').value;\n  const err = document.getElementById('authError');\n  err.classList.remove('show');\n  const doc = await getAuthDoc();\n  if(!doc){ err.textContent='No login found \u2014 please refresh and set one up.'; err.classList.add('show'); return; }\n  if(username.toLowerCase() !== (doc.username||'').toLowerCase()){\n    err.textContent='Incorrect username or password.'; err.classList.add('show'); return;\n  }\n  const hash = await sha256Hex(doc.salt + pw);\n  if(hash !== doc.hash){\n    err.textContent='Incorrect username or password.'; err.classList.add('show'); return;\n  }\n  AUTH = { username: doc.username, unlocked:true };\n  unlockApp();\n}\n\nfunction logout(){\n  window.location.reload();\n}\n\nasync function changeCredentials(){\n  const msg = document.getElementById('pwChangeMsg');\n  msg.classList.remove('show');\n  const newUsername = document.getElementById('pw_username').value.trim();\n  const current = document.getElementById('pw_current').value;\n  const newPw = document.getElementById('pw_new').value;\n  const confirmPw = document.getElementById('pw_confirm').value;\n  if(!newUsername){ msg.textContent='Username cannot be blank.'; msg.classList.add('show'); return; }\n  if(!current){ msg.textContent='Enter your current password to confirm changes.'; msg.classList.add('show'); return; }\n  const doc = await getAuthDoc();\n  if(!doc){ msg.textContent='No login record found.'; msg.classList.add('show'); return; }\n  const currentHash = await sha256Hex(doc.salt + current);\n  if(currentHash !== doc.hash){ msg.textContent='Current password is incorrect.'; msg.classList.add('show'); return; }\n  if(newPw || confirmPw){\n    if(newPw.length < 4){ msg.textContent='New password must be at least 4 characters.'; msg.classList.add('show'); return; }\n    if(newPw !== confirmPw){ msg.textContent='New passwords do not match.'; msg.classList.add('show'); return; }\n  }\n  const salt = randomSalt();\n  const finalPw = newPw || current;\n  const hash = await sha256Hex(salt + finalPw);\n  const ok = await setAuthDoc({ username: newUsername, salt, hash });\n  if(!ok){ msg.textContent='Could not save \u2014 check your connection and try again.'; msg.classList.add('show'); return; }\n  AUTH.username = newUsername;\n  document.getElementById('pw_current').value='';\n  document.getElementById('pw_new').value='';\n  document.getElementById('pw_confirm').value='';\n  toast('Login details updated');\n}\n\nasync function unlockApp(){\n  document.getElementById('authScreen').style.display='none';\n  document.getElementById('app').style.display='flex';\n  await loadAll();\n  renderShell();\n  route();\n}\n\n\/* ---------- Init ---------- *\/\nasync function init(){\n  await loadAuthBranding();\n  const doc = await getAuthDoc();\n  if(!doc){\n    renderAuthSetup();\n  }else{\n    renderAuthLogin(doc.username);\n  }\n}\ninit();\n\n<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-21","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/pages\/21","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=21"}],"version-history":[{"count":10,"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/pages\/21\/revisions"}],"predecessor-version":[{"id":90,"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=\/wp\/v2\/pages\/21\/revisions\/90"}],"wp:attachment":[{"href":"https:\/\/gst.sarvalokbhraman.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=21"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}