:root{--bg:#f4f7fc;--surface:#fff;--surface-2:#f7faff;--text:#0f172a;--muted:#64748b;--line:#e3e9f3;--primary:#1b63f8;--primary-soft:#eaf1ff;--danger:#e5484d;--ok:#10a36b;--radius-xl:20px;--radius-lg:14px;--radius-md:10px;--shadow-lg:0 18px 40px rgba(15,23,42,.09);--shadow-md:0 8px 24px rgba(15,23,42,.08)}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Inter,"Segoe UI",sans-serif;background:radial-gradient(circle at 0 0,#ecf3ff 0,transparent 30%),radial-gradient(circle at 100% 0,#edfef7 0,transparent 30%),var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column}
a{text-decoration:none;color:inherit}
button,input,select,textarea{font-family:inherit}
.app{flex:1;display:flex;flex-direction:column}
.container{width:min(1280px,100% - 30px);margin:0 auto}
.header{position:sticky;top:0;z-index:70;background:rgba(255,255,255,.88);backdrop-filter:blur(12px);border-bottom:1px solid var(--line)}
.header-inner{height:74px;display:grid;grid-template-columns:220px 1fr auto;align-items:center;gap:14px}
.logo{display:flex;align-items:center;gap:10px;border:none;background:none;font-weight:800;font-size:1.05rem;color:var(--text);cursor:pointer}
.logo-badge{width:34px;height:34px;border-radius:11px;background:linear-gradient(135deg,var(--primary),#22b0ff);display:grid;place-items:center;color:#fff;box-shadow:var(--shadow-md)}
.center-nav{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}
.nav-link{padding:9px 12px;border-radius:11px;border:1px solid transparent;color:var(--muted);font-weight:600;cursor:pointer;background:none;transition:.2s}
.nav-link:hover{color:var(--text)}
.nav-link.active{background:var(--primary-soft);color:var(--primary)}
.auth-area{display:flex;align-items:center;gap:8px}
.btn{padding:10px 14px;border-radius:11px;border:1px solid transparent;background:none;color:var(--muted);font-weight:600;cursor:pointer;transition:.2s}
.btn:hover{transform:translateY(-1px)}
.btn.primary{background:linear-gradient(135deg,var(--primary),#1da4ff);color:#fff;box-shadow:var(--shadow-md)}
.btn.secondary{background:var(--surface);border-color:var(--line);color:var(--text)}
.user-chip{padding:8px 11px;border-radius:999px;border:1px solid var(--line);background:var(--surface);font-size:.86rem;color:var(--muted)}
.mobile-top{display:none;justify-content:space-between;align-items:center;gap:10px;margin-top:8px}
.hamburger{width:42px;height:42px;border-radius:12px;border:1px solid var(--line);background:var(--surface);display:grid;place-items:center;cursor:pointer;font-size:1.1rem}
.main{padding:18px 0 22px}
.page{display:none;animation:fade .28s ease}
.page.active{display:block}
.shell{display:grid;grid-template-columns:280px 1fr;gap:16px}
.sidebar{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-xl);padding:14px;box-shadow:var(--shadow-md);position:sticky;top:92px;height:calc(100vh - 110px);overflow:auto}
.sidebar h3{font-size:1rem}
.sub{color:var(--muted);font-size:.9rem;line-height:1.45;margin-top:6px}
.tool-list{display:grid;gap:8px;margin-top:12px}
.tool-item{width:100%;text-align:left;padding:10px 11px;border-radius:11px;border:1px solid var(--line);background:var(--surface-2);font-weight:600;color:var(--text);cursor:pointer;transition:.2s}
.tool-item.active{background:var(--primary-soft);color:var(--primary);border-color:var(--primary)}
.tool-item:hover{border-color:var(--primary)}
.workspace{display:grid;grid-template-columns:1.15fr .85fr;gap:14px;align-items:start}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-xl);padding:16px;box-shadow:var(--shadow-md)}
.card h2,.card h3{letter-spacing:-.02em}
.forms section{display:none;gap:10px;margin-top:12px}
.forms section.active{display:grid}
.field{display:grid;gap:6px}
.field label{font-size:.85rem;color:var(--muted);font-weight:600}
input,select,textarea{border:1px solid var(--line);background:var(--surface-2);color:var(--text);border-radius:10px;padding:10px 11px;outline:none;width:100%}
textarea{min-height:95px;resize:vertical}
input:focus,select:focus,textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(27,99,248,.14)}
.inline-2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.preview-wrap{min-height:340px;border:1px dashed var(--line);border-radius:16px;background:linear-gradient(145deg,var(--surface),var(--surface-2));display:grid;place-items:center;position:relative;margin-top:10px}
.preview-hint{color:var(--muted);font-size:.9rem}
.controls{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px}
.check{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:.9rem;margin-top:8px}
.check input{width:auto}
.actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px}
.actions .btn{width:100%}
.history-list{display:grid;gap:10px;max-height:300px;overflow:auto;margin-top:12px}
.history-item{border:1px solid var(--line);border-radius:12px;padding:10px;background:var(--surface-2)}
.history-top{display:flex;justify-content:space-between;gap:8px;color:var(--muted);font-size:.82rem}
.history-item strong{display:block;color:var(--text);margin-bottom:4px}
.history-actions{display:flex;gap:8px;margin-top:8px}
.stats{display:grid;grid-template-columns:repeat(4,minmax(120px,1fr));gap:10px;margin-top:12px}
.stat{border:1px solid var(--line);border-radius:13px;background:var(--surface);padding:14px;box-shadow:var(--shadow-md)}
.stat p{color:var(--muted);font-size:.84rem;margin-bottom:7px}
.stat h3{font-size:1.35rem}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:12px}
.panel{border:1px solid var(--line);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-md);padding:16px}
.panel p{color:var(--muted);margin-top:8px;line-height:1.45}
.graph{height:190px;border:1px solid var(--line);border-radius:14px;background:linear-gradient(160deg,var(--surface-2),var(--surface));display:flex;align-items:flex-end;gap:10px;padding:12px}
.bar{flex:1;background:linear-gradient(180deg,#8fb3ff,var(--primary));border-radius:10px 10px 4px 4px;animation:grow .5s ease}
.price-pop{border-color:var(--primary);outline:3px solid rgba(27,99,248,.13)}
.big-price{font-size:2rem;font-weight:800;letter-spacing:-.02em}
.contact-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:12px;margin-top:12px}
.map-block{height:220px;border:1px dashed var(--line);border-radius:14px;background:linear-gradient(145deg,var(--surface-2),var(--surface));display:grid;place-items:center;color:var(--muted);font-weight:600}
footer{margin-top:auto;background:var(--surface);border-top:1px solid var(--line)}
.footer-inner{padding:28px 0 24px;display:grid;grid-template-columns:1.1fr .8fr .8fr;gap:16px;align-items:start}
.footer-col h4{font-size:1rem;margin-bottom:8px}
.footer-col p,.footer-col a{font-size:.9rem;color:var(--muted);line-height:1.5}
.quick-links,.socials{display:grid;gap:6px}
.social-row{display:flex;gap:8px;margin-top:8px}
.social-btn{width:34px;height:34px;border:1px solid var(--line);background:var(--surface-2);border-radius:10px;display:grid;place-items:center;color:var(--muted)}
.copy{margin-top:10px;font-size:.84rem;color:var(--muted)}
.toast-wrap{position:fixed;right:14px;bottom:14px;display:grid;gap:8px;max-width:320px;z-index:80}
.toast{border:1px solid var(--line);border-left:4px solid var(--primary);border-radius:12px;background:var(--surface);padding:10px 12px;box-shadow:var(--shadow-md);font-size:.9rem}
.modal{position:fixed;inset:0;background:rgba(15,23,42,.45);display:none;align-items:center;justify-content:center;padding:16px;z-index:90}
.modal.open{display:flex}
.modal-card{width:min(520px,100%);background:var(--surface);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow-lg);padding:16px}
.tabs{display:flex;gap:8px;margin-bottom:12px}
.tab{flex:1}
.form-msg{font-size:.86rem;margin-top:4px;display:none}
.form-msg.show{display:block}
.form-msg.ok{color:var(--ok)}
.form-msg.err{color:var(--danger)}
.loader{width:18px;height:18px;border:2.4px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;display:inline-block;animation:spin .7s linear infinite}
.hidden{display:none !important}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
@keyframes grow{from{transform:scaleY(.1);transform-origin:bottom}to{transform:scaleY(1);transform-origin:bottom}}
@media (max-width:1100px){.workspace,.grid-2,.grid-3,.contact-grid{grid-template-columns:1fr}.stats{grid-template-columns:1fr 1fr}.footer-inner{grid-template-columns:1fr 1fr}}
@media (max-width:900px){.header-inner{grid-template-columns:1fr auto}.center-nav{display:none}.mobile-top{display:flex}.shell{grid-template-columns:1fr}.sidebar{display:none;position:fixed;left:10px;top:90px;z-index:75;width:min(320px,calc(100% - 20px));height:calc(100vh - 100px)}.sidebar.open{display:block}.workspace{grid-template-columns:1fr}.actions{grid-template-columns:1fr}}
@media (max-width:620px){.stats,.inline-2,.controls,.footer-inner{grid-template-columns:1fr}.header-inner{height:auto;padding:12px 0}.auth-area{width:100%;justify-content:flex-end}.btn{padding:10px 12px}}
Create QR codes for links, campaigns, and landing pages.
QR Preview & Customization
Live styling controls with download and edit options.
Generate a QR to preview.
Foreground
Background
Gradient End
Dot Style SquareDotsRounded
Eye Shape SquareDotRounded
Error Level LMQH
Size
Margin
Logo Upload
Use gradient
Download PNG
Download SVG
Edit QR
Copy Image
Export History JSON
Clear History
Dashboard
Track QR performance and recent activity.
Analytics Visual trend placeholder with card-based UI.
Recent History Most recent generated records.
Work
Enterprise-ready QR solutions built for modern growth teams.
Custom Branding Branded QR visuals with logos, style controls, and campaign-level consistency for print and digital channels.
Enterprise Integrations Connect QR outputs to CRM, ERP, and customer success workflows for measurable operational performance.
API Usage Automate generation and distribution across onboarding, support, and transactional funnels.
Marketing Campaigns Drive real-world conversion with packaging, retail, OOH, and event activation use cases.
Product Docs Deliver guides, manuals, and update logs in a single scan experience for end users.
Event Journeys Power check-ins, attendee content, and follow-up messaging through smart QR orchestration.
Pricing
Flexible plans designed for solo teams and enterprise scale.
Free $0
Up to 50 QR / month
Core tools and PNG export
Start Free
Pro $19
Unlimited generation
Advanced styling + SVG export
History management tools
Get Pro
Business $69
Team workflows
API and enterprise support
Priority SLA
Contact Sales
About Us
We build premium QR infrastructure for modern SaaS teams.
Our Mission Enable every business to launch smart, branded QR experiences without engineering overhead.
Why Choose Us We focus on reliability, clean UX, and production-ready tooling designed for business outcomes.
Startup Speed From idea to launch in minutes with configurable templates and seamless edit workflows.
Security Mindset Client-side generation options, safe encoding patterns, and structured validation for confidence.
Professional Design White-premium design language, polished motion, and consistent typography across modules.
Scalable Platform Designed to grow from solo creators to enterprise teams with analytics and automation needs.
Contact Us
Reach our team for onboarding, support, and enterprise needs.
Login
Create Account
Login
Access your QRFlow Pro workspace.
Email
Password
Login
Forgot password?
Create Account
Create your account in seconds.
Full Name
Email
Password
Create Account
const STORAGE={
auth:"qrflow_auth_user",
users:"qrflow_users",
history:"qrflow_history",
total:"qrflow_total"
};
const tools=[
{id:"website",name:"Website URL QR",sub:"Generate QR for websites.",fields:[{id:"url",label:"Website URL",type:"url",req:1,ph:"https://example.com"}],build:v=>v.url.trim()},
{id:"wifi",name:"WiFi QR",sub:"Share WiFi credentials.",fields:[{id:"ssid",label:"SSID",type:"text",req:1,ph:"Office_WiFi"},{id:"pass",label:"Password",type:"text",ph:"Password"},{id:"enc",label:"Encryption",type:"select",opts:["WPA","WEP","nopass"],req:1}],build:v=>`WIFI:T:${v.enc};S:${esc(v.ssid)};P:${esc(v.pass||"")};;`},
{id:"email",name:"Email QR",sub:"Prefill email details.",fields:[{id:"email",label:"Email",type:"email",req:1,ph:"hello@example.com"},{id:"subject",label:"Subject",type:"text",ph:"Subject"},{id:"body",label:"Body",type:"textarea",ph:"Message"}],build:v=>`mailto:${v.email}?subject=${encodeURIComponent(v.subject||"")}&body=${encodeURIComponent(v.body||"")}`},
{id:"phone",name:"Phone QR",sub:"Tap to call.",fields:[{id:"phone",label:"Phone",type:"tel",req:1,ph:"+12025550123"}],build:v=>`tel:${v.phone}`},
{id:"text",name:"Text QR",sub:"Encode plain text.",fields:[{id:"text",label:"Text",type:"textarea",req:1,ph:"Your text"}],build:v=>v.text},
{id:"vcard",name:"vCard QR",sub:"Share contact card.",fields:[{id:"first",label:"First Name",type:"text",req:1,ph:"John"},{id:"last",label:"Last Name",type:"text",req:1,ph:"Doe"},{id:"org",label:"Organization",type:"text",ph:"Company"},{id:"email",label:"Email",type:"email",req:1,ph:"john@company.com"},{id:"phone",label:"Phone",type:"tel",req:1,ph:"+12025550123"}],build:v=>`BEGIN:VCARD\nVERSION:3.0\nN:${v.last};${v.first};;;\nFN:${v.first} ${v.last}\nORG:${v.org||""}\nEMAIL:${v.email}\nTEL:${v.phone}\nEND:VCARD`},
{id:"location",name:"Location QR",sub:"Share map location.",fields:[{id:"lat",label:"Latitude",type:"text",req:1,ph:"37.7749"},{id:"lng",label:"Longitude",type:"text",req:1,ph:"-122.4194"}],build:v=>`https://maps.google.com/?q=${encodeURIComponent(v.lat+","+v.lng)}`},
{id:"pdf",name:"PDF QR",sub:"Share PDF links.",fields:[{id:"pdf",label:"PDF URL",type:"url",req:1,ph:"https://example.com/doc.pdf"}],build:v=>v.pdf}
];
const state={page:"generator",tool:"website",qrData:"",logo:"",history:[],total:0,qr:null,currentFields:{}};
const pages=["generator","dashboard","work","pricing","about","contact"];
const $=id=>document.getElementById(id);
function init(){
loadStorage();
renderNav();
renderAuthArea();
renderToolList();
renderForms();
bindCommon();
initQrEngine();
setTool("website");
setPage("generator");
renderHistory();
refreshDashboard();
}
function renderNav(){
const m=$("mobileNav");
m.innerHTML=pages.filter(p=>p!=="generator").map(p=>`
${label(p)} `).join("");
}
function bindCommon(){
document.querySelectorAll("[data-nav]").forEach(el=>el.addEventListener("click",e=>{e.preventDefault();const p=el.dataset.nav||"generator";setPage(p);if(window.innerWidth$("sidebar").classList.toggle("open"));
["fg","bg","grad","dotType","eyeType","ecLevel","qrSize","qrMargin","useGradient"].forEach(id=>{const el=$(id);el.addEventListener("input",updateQR);el.addEventListener("change",updateQR);});
$("logoInput").addEventListener("change",async e=>{const f=e.target.files[0];if(!f)return;state.logo=await fileToDataURL(f);updateQR();toast("Logo uploaded")});
$("downloadPNG").addEventListener("click",()=>downloadQR("png"));
$("downloadSVG").addEventListener("click",()=>downloadQR("svg"));
$("copyQR").addEventListener("click",copyQRImage);
$("editCurrent").addEventListener("click",()=>toast("Update form fields and generate again."));
$("exportHistory").addEventListener("click",exportHistory);
$("clearHistory").addEventListener("click",clearHistory);
$("historyList").addEventListener("click",onHistoryAction);
$("dashHistory").addEventListener("click",onHistoryAction);
$("contactSubmit").addEventListener("click",submitContact);
// Auth modal interactions
$("tabLogin").addEventListener("click",()=>toggleAuthPane("login"));
$("tabSignup").addEventListener("click",()=>toggleAuthPane("signup"));
$("loginBtn").addEventListener("click",doLogin);
$("signupBtn").addEventListener("click",doSignup);
$("forgotBtn").addEventListener("click",()=>showMsg("loginMsg","Reset link UI simulated. Please use your registered email.",0));
$("authModal").addEventListener("click",e=>{if(e.target.id==="authModal")closeAuthModal();});
}
function setPage(page){
state.page=page;
document.querySelectorAll(".page").forEach(p=>p.classList.remove("active"));
const target=$("page-"+page)||$("page-generator");
target.classList.add("active");
document.querySelectorAll(".nav-link").forEach(n=>n.classList.toggle("active",n.dataset.nav===page));
const showSidebar=page==="generator";
$("sidebar").style.display=showSidebar?"block":"none";
refreshDashboard();
}
function renderToolList(){
$("toolList").innerHTML=tools.map(t=>`
${t.name} `).join("");
$("toolList").addEventListener("click",e=>{const b=e.target.closest(".tool-item");if(!b)return;setTool(b.dataset.tool);});
}
function renderForms(){
$("forms").innerHTML=tools.map(t=>`
${t.fields.map(fieldTemplate).join("")}Generate QR `).join("");
$("forms").addEventListener("click",e=>{const b=e.target.closest("[data-generate]");if(b)generateForTool(b.dataset.generate,b)});
$("forms").addEventListener("input",()=>{if(state.qrData)updateQR();});
}
function fieldTemplate(f){
if(f.type==="textarea")return `
${f.label}${f.req?" *":""}
`;
if(f.type==="select")return `
${f.label}${f.req?" *":""} ${f.opts.map(o=>`${o}`).join("")}
`;
return `
${f.label}${f.req?" *":""}
`;
}
function setTool(id){
state.tool=id;
const tool=getTool(id);
$("toolTitle").textContent=tool.name;
$("toolSub").textContent=tool.sub;
document.querySelectorAll(".tool-item").forEach(btn=>btn.classList.toggle("active",btn.dataset.tool===id));
document.querySelectorAll(".forms section").forEach(sec=>sec.classList.toggle("active",sec.dataset.form===id));
setPage("generator");
}
function initQrEngine(){
state.qr=new QRCodeStyling({
width:300,height:300,type:"svg",margin:8,data:"https://example.com",
qrOptions:{errorCorrectionLevel:"M"},dotsOptions:{color:"#111827",type:"square"},backgroundOptions:{color:"#fff"},cornersSquareOptions:{type:"square"},
imageOptions:{crossOrigin:"anonymous",margin:4,imageSize:.25,hideBackgroundDots:true}
});
state.qr.append($("qrMount"));
}
async function generateForTool(toolId,btn){
const t=getTool(toolId);
const form=document.querySelector(`section[data-form="${toolId}"]`);
const values={};
form.querySelectorAll("[data-field]").forEach(i=>values[i.dataset.field]=i.value.trim());
const check=validateToolValues(t,values);
if(!check.ok){toast(check.msg,1);return;}
const prev=btn.innerHTML;
btn.innerHTML='
';
btn.disabled=true;
await wait(520);
const payload=t.build(values);
state.qrData=payload;
state.currentFields=values;
$("previewHint").style.display="none";
updateQR();
saveHistory(toolId,payload,values);
renderHistory();
refreshDashboard();
btn.innerHTML=prev;
btn.disabled=false;
toast(`${t.name} generated successfully`);
}
function validateToolValues(tool,v){
for(const f of tool.fields){
if(f.req && !v[f.id])return {ok:0,msg:`${f.label} is required.`};
if(v[f.id] && f.type==="url"){try{new URL(v[f.id]);}catch{return{ok:0,msg:`Invalid URL in ${f.label}.`};}}
if(v[f.id] && f.type==="email" && !/^\S+@\S+\.\S+$/.test(v[f.id]))return {ok:0,msg:`Invalid email in ${f.label}.`};
}
if(tool.id==="location" && (Number.isNaN(Number(v.lat)) || Number.isNaN(Number(v.lng))))return {ok:0,msg:"Latitude and Longitude must be numeric."};
return {ok:1};
}
function qrOptions(){
const fg=$("fg").value,bg=$("bg").value,grad=$("grad").value,useGrad=$("useGradient").checked,dotType=$("dotType").value,eyeType=$("eyeType").value,ec=$("ecLevel").value,size=+$("qrSize").value,margin=+$("qrMargin").value;
const dots=useGrad?{type:dotType,gradient:{type:"linear",rotation:0,colorStops:[{offset:0,color:fg},{offset:1,color:grad}]}}:{color:fg,type:dotType};
return {width:size,height:size,data:state.qrData||"https://example.com",margin,qrOptions:{errorCorrectionLevel:ec},dotsOptions:dots,backgroundOptions:{color:bg},cornersSquareOptions:{type:eyeType},image:state.logo||undefined,imageOptions:{crossOrigin:"anonymous",margin:4,imageSize:.25,hideBackgroundDots:true}};
}
function updateQR(){if(!state.qr)return;state.qr.update(qrOptions());}
async function downloadQR(type){
if(!state.qrData){toast("Generate a QR first.",1);return;}
const blob=await state.qr.getRawData(type);
const ext=type==="svg"?"svg":"png";
triggerDownload(blob,`qrflow-${Date.now()}.${ext}`);
toast(`Downloaded ${ext.toUpperCase()} file`);
}
async function copyQRImage(){
if(!state.qrData){toast("Generate a QR first.",1);return;}
try{
const blob=await state.qr.getRawData("png");
await navigator.clipboard.write([new ClipboardItem({"image/png":blob})]);
toast("QR copied to clipboard");
}catch{toast("Clipboard copy is not supported in this browser.",1);}
}
function saveHistory(tool,payload,fields){
const item={id:`${Date.now()}_${Math.random().toString(36).slice(2,7)}`,tool,payload,fields,style:{fg:$("fg").value,bg:$("bg").value,grad:$("grad").value,useGradient:$("useGradient").checked,dotType:$("dotType").value,eyeType:$("eyeType").value,ec:$("ecLevel").value,size:$("qrSize").value,margin:$("qrMargin").value},at:new Date().toISOString()};
state.history.unshift(item);
state.history=state.history.slice(0,60);
state.total+=1;
persistStorage();
}
function renderHistory(){
const html=state.history.length?state.history.slice(0,12).map(h=>{const t=getTool(h.tool),d=new Date(h.at).toLocaleString(),sn=h.payload.length>70?escapeHtml(h.payload.slice(0,70)+"..."):escapeHtml(h.payload);return `
${t.name} ${d}
${sn}
Edit Delete
`;}).join(""):'
No history yet.
';
$("historyList").innerHTML=html;
$("dashHistory").innerHTML=html;
}
function onHistoryAction(e){
const b=e.target.closest("[data-action]");
if(!b)return;
const id=b.dataset.id;
if(b.dataset.action==="delete"){
state.history=state.history.filter(x=>x.id!==id);
persistStorage();
renderHistory();
refreshDashboard();
toast("History item deleted");
return;
}
const item=state.history.find(x=>x.id===id);
if(!item)return;
setTool(item.tool);
const form=document.querySelector(`section[data-form="${item.tool}"]`);
Object.entries(item.fields).forEach(([k,v])=>{const i=form.querySelector(`[data-field="${k}"]`);if(i)i.value=v;});
if(item.style){
$("fg").value=item.style.fg||$("fg").value;
$("bg").value=item.style.bg||$("bg").value;
$("grad").value=item.style.grad||$("grad").value;
$("useGradient").checked=!!item.style.useGradient;
$("dotType").value=item.style.dotType||$("dotType").value;
$("eyeType").value=item.style.eyeType||$("eyeType").value;
$("ecLevel").value=item.style.ec||$("ecLevel").value;
$("qrSize").value=item.style.size||$("qrSize").value;
$("qrMargin").value=item.style.margin||$("qrMargin").value;
}
state.qrData=item.payload;
$("previewHint").style.display="none";
updateQR();
toast("Loaded history item for editing");
}
function refreshDashboard(){
$("statTotal").textContent=String(state.total);
$("statCount").textContent=String(state.history.length);
$("statTop").textContent=getMostUsedTool();
const auth=getAuthUser();
$("statUser").textContent=auth?auth.name:"Guest";
renderGraph();
}
function renderGraph(){
const g=$("graph");
const list=state.history.slice(0,8);
if(!list.length){g.innerHTML='
Generate QR codes to populate analytics.
';return;}
g.innerHTML=list.map((_,i)=>`
`).join("");
}
function getMostUsedTool(){
if(!state.history.length)return"-";
const count={};
state.history.forEach(h=>count[h.tool]=(count[h.tool]||0)+1);
const top=Object.entries(count).sort((a,b)=>b[1]-a[1])[0][0];
return getTool(top).name;
}
function submitContact(){
const name=$("contactName").value.trim(),email=$("contactEmail").value.trim(),msg=$("contactMessage").value.trim();
const target=$("contactMsg");
if(!name||!email||!msg)return showMsg("contactMsg","All fields are required.",1);
if(!/^\S+@\S+\.\S+$/.test(email))return showMsg("contactMsg","Please enter a valid email address.",1);
showMsg("contactMsg","Message submitted successfully. We will contact you soon.",0);
$("contactName").value="";$("contactEmail").value="";$("contactMessage").value="";
toast("Contact form submitted");
}
// Auth system (frontend simulation with localStorage)
function openAuthModal(){
$("authModal").classList.add("open");
toggleAuthPane("login");
}
function closeAuthModal(){$("authModal").classList.remove("open");}
function toggleAuthPane(pane){
const login=pane==="login";
$("loginPane").classList.toggle("hidden",!login);
$("signupPane").classList.toggle("hidden",login);
$("tabLogin").classList.toggle("primary",login);
$("tabSignup").classList.toggle("primary",!login);
}
function doSignup(){
const name=$("signupName").value.trim(),email=$("signupEmail").value.trim(),pass=$("signupPassword").value.trim();
if(!name||!email||!pass)return showMsg("signupMsg","All signup fields are required.",1);
if(!/^\S+@\S+\.\S+$/.test(email))return showMsg("signupMsg","Invalid email format.",1);
if(pass.lengthu.email.toLowerCase()===email.toLowerCase()))return showMsg("signupMsg","Account already exists for this email.",1);
users.push({name,email,password:pass});
localStorage.setItem(STORAGE.users,JSON.stringify(users));
showMsg("signupMsg","Account created. You can now login.",0);
$("loginEmail").value=email;
toggleAuthPane("login");
}
function doLogin(){
const email=$("loginEmail").value.trim(),pass=$("loginPassword").value.trim();
if(!email||!pass)return showMsg("loginMsg","Email and password are required.",1);
if(!/^\S+@\S+\.\S+$/.test(email))return showMsg("loginMsg","Invalid email format.",1);
const users=getUsers();
const user=users.find(u=>u.email.toLowerCase()===email.toLowerCase()&&u.password===pass);
if(!user)return showMsg("loginMsg","Invalid credentials.",1);
localStorage.setItem(STORAGE.auth,JSON.stringify({name:user.name,email:user.email}));
showMsg("loginMsg","Login successful.",0);
renderAuthArea();
refreshDashboard();
closeAuthModal();
toast(`Welcome, ${user.name}`);
}
function logout(){
localStorage.removeItem(STORAGE.auth);
renderAuthArea();
refreshDashboard();
toast("Logged out successfully");
}
function renderAuthArea(){
const auth=getAuthUser();
const area=$("authArea");
if(!auth){
area.innerHTML='
Login / Signup ';
$("authCTA").addEventListener("click",openAuthModal);
return;
}
area.innerHTML=`
${escapeHtml(auth.name)} Logout `;
$("logoutBtn").addEventListener("click",logout);
}
function getUsers(){try{return JSON.parse(localStorage.getItem(STORAGE.users)||"[]");}catch{return [];}}
function getAuthUser(){try{return JSON.parse(localStorage.getItem(STORAGE.auth)||"null");}catch{return null;}}
function exportHistory(){
const blob=new Blob([JSON.stringify(state.history,null,2)],{type:"application/json"});
triggerDownload(blob,`qrflow-history-${Date.now()}.json`);
toast("History exported as JSON");
}
function clearHistory(){
state.history=[];
persistStorage();
renderHistory();
refreshDashboard();
toast("History cleared");
}
function loadStorage(){
try{state.history=JSON.parse(localStorage.getItem(STORAGE.history)||"[]");state.total=Number(localStorage.getItem(STORAGE.total)||"0");}
catch{state.history=[];state.total=0;}
}
function persistStorage(){
localStorage.setItem(STORAGE.history,JSON.stringify(state.history));
localStorage.setItem(STORAGE.total,String(state.total));
}
function getTool(id){return tools.find(t=>t.id===id)||tools[0];}
function triggerDownload(blob,name){const u=URL.createObjectURL(blob),a=document.createElement("a");a.href=u;a.download=name;document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(u);}
function esc(v){return (v||"").replace(/([\\;,:\"])/g,"\\$1");}
function label(p){return p==="about"?"About":p==="contact"?"Contact":p.charAt(0).toUpperCase()+p.slice(1);}
function wait(ms){return new Promise(r=>setTimeout(r,ms));}
function fileToDataURL(file){return new Promise((resolve,reject)=>{const r=new FileReader();r.onload=()=>resolve(r.result);r.onerror=reject;r.readAsDataURL(file);});}
function showMsg(id,text,isErr){const el=$(id);el.textContent=text;el.className=`form-msg show ${isErr?"err":"ok"}`;}
function escapeHtml(str){return (str||"").replace(/&/g,"&").replace(//g,">").replace(/\"/g,""").replace(/'/g,"'");}
function toast(msg,err){const t=document.createElement("div");t.className="toast";if(err)t.style.borderLeftColor="var(--danger)";t.textContent=msg;$("toastWrap").appendChild(t);setTimeout(()=>t.remove(),2800);}
window.addEventListener("DOMContentLoaded",init);