`:""} ${sections} `; const blob=new Blob([html],{type:"text/html"}); const a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=`${page.slug==="/"?"index":page.slug.replace("/","")}.html`; a.click(); }); setDone(true); }; const renderElToHTML=(el,primary,hf,bf)=>{ switch(el.type){ case"heading":return`<${el.tag?.toLowerCase()||"h2"} style="font-family:${hf};font-size:${el.size||28}px;font-weight:${el.bold!==false?800:400};color:${el.color||"#0f172a"};text-align:${el.align||"left"};line-height:1.2;margin-bottom:16px">${el.text||""}`; case"paragraph":return`

${el.text||""}

`; case"sectionlabel":return`
${el.text||""}
`; case"eyebrow":return`
${el.text||""}
`; case"button":return`${el.text||"Button"}`; case"buttongroup":return(el.buttons||[]).map(btn=>`${btn.text}`).join(""); case"image":return`${el.alt||`; case"trustbar":return`
${(el.items||[]).map(i=>`${i}`).join("")}
`; case"checklist":return``; case"twocol":return`
${(el.left||[]).map(e=>renderElToHTML(e,primary,hf,bf)).join("")}
${(el.right||[]).map(e=>renderElToHTML(e,primary,hf,bf)).join("")}
`; case"contactinfo":return(el.items||[]).map(item=>`
${item.icon}
${item.label}
${item.value}
`).join(""); case"contactform":return`
${["Full Name","Email","Phone","Service"].map(f=>``).join("")}
`; case"mapembed":return`

📍 ${el.address||"Your Address"}

`; case"cards3":return`
${(el.cards||[]).map(c=>`
${c.icon}

${c.title}

${c.desc}

`).join("")}
`; case"testimonials":return`
${(el.items||[]).map(t=>`
${"★".repeat(t.rating||5)}

"${t.quote}"

${t.author}
${t.location}
`).join("")}
`; case"gallery":return`
${(el.images||[]).map(src=>``).join("")}
`; case"statgrid":return`
${(el.stats||[]).map(s=>`
${s.value}${s.suffix}
${s.label}
`).join("")}
`; case"faqlist":return`
${(el.items||[]).map(i=>`
${i.q}
${i.a}
`).join("")}
`; case"pricecards":return`
${(el.cards||[]).map(c=>`
${c.name}
${c.price}${c.period}
${c.cta}
`).join("")}
`; case"footergrid":return`
${(el.cols||[]).map(col=>`
${col.title}
${col.content==="type:links"?(col.links||[]).map(l=>`
${l}
`).join(""):col.content==="type:contact"?(col.items||[]).map(i=>`
${i}
`).join(""):`
Your trusted local business.
`}
`).join("")}
`; case"footerbar":return`
${el.copyright||""}
${(el.links||[]).map(l=>`${l}`).join("")}
`; case"brand":return`
${el.text||"My Business"}
`; case"navlinks":return`
${(el.links||[]).map(l=>`${l}`).join("")}
`; case"navbutton":return`${el.text||"Contact"}`; default:return""; } }; const pageCount=Object.keys(pages).length; return(
{!done?<>
📦
Export Your Website
{pageCount} HTML files will be downloaded to your computer
After downloading:
{["Upload all HTML files to your cPanel server via File Manager","Upload to public_html/clientdomain.com/ folder","Point client's A record to your server IP","AutoSSL provides free SSL automatically"].map((s,i)=>(
{i+1}.{s}
))}
:<>
Files Downloaded!
{pageCount} HTML files saved to your Downloads folder
Upload these files to your cPanel server and point the client's domain A record to your server IP. SSL activates automatically via AutoSSL.
}
); } // ─── MAIN APP ───────────────────────────────────────────────────── const INIT_PAGES={ home:{id:"home",name:"Home",slug:"/",meta:{title:"My Business | Professional Services",description:"Professional services serving Toronto and the Greater Toronto Area.",ga4:""}, sections:[ {...SECTION_TEMPLATES.find(t=>t.type==="nav"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="nav").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="hero"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="hero").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="services"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="services").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="stats"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="stats").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="testimonials"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="testimonials").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="cta"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="cta").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="footer"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="footer").elements.map(e=>({...e,id:uid()}))}, ]}, about:{id:"about",name:"About",slug:"/about",meta:{title:"About Us",description:"Learn more about our company and team.",ga4:""}, sections:[ {...SECTION_TEMPLATES.find(t=>t.type==="nav"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="nav").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="about"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="about").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="footer"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="footer").elements.map(e=>({...e,id:uid()}))}, ]}, contact:{id:"contact",name:"Contact",slug:"/contact",meta:{title:"Contact Us",description:"Get in touch with our team today.",ga4:""}, sections:[ {...SECTION_TEMPLATES.find(t=>t.type==="nav"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="nav").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="contact"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="contact").elements.map(e=>({...e,id:uid()}))}, {...SECTION_TEMPLATES.find(t=>t.type==="footer"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="footer").elements.map(e=>({...e,id:uid()}))}, ]}, }; function App(){ const[pages,setPages]=useState(()=>{ try{const s=localStorage.getItem("sitevana_pages");return s?JSON.parse(s):INIT_PAGES;} catch{return INIT_PAGES;} }); const[activePid,setActivePid]=useState("home"); const[selSection,setSelSection]=useState(null); const[selEl,setSelEl]=useState(null); const[rightTab,setRightTab]=useState("properties"); const[showAddSection,setShowAddSection]=useState(false); const[addAfterIdx,setAddAfterIdx]=useState(null); const[showExport,setShowExport]=useState(false); const[viewMode,setViewMode]=useState("desktop"); const[palette,setPalette]=useState({id:"ocean",primary:"#0ea5e9",secondary:"#0284c7",bg:"#f0f9ff",text:"#334155",heading:"#0f172a"}); const[fonts,setFonts]=useState({heading:"'Georgia',serif",body:"'DM Sans',system-ui,sans-serif"}); const[apiKey,setApiKey]=useState(()=>localStorage.getItem("sitevana_apikey")||""); const[dragSec,setDragSec]=useState(null); const[showSaved,setShowSaved]=useState(false); // Auto-save to localStorage useEffect(()=>{ try{localStorage.setItem("sitevana_pages",JSON.stringify(pages));}catch{} },[pages]); const saveApiKey=(key)=>{ setApiKey(key); try{localStorage.setItem("sitevana_apikey",key);}catch{} }; const activePage=pages[activePid]; const sections=activePage?.sections||[]; // ── Page ops ── const addPage=()=>{ const id=`pg_${Date.now()}`; const nav={...SECTION_TEMPLATES.find(t=>t.type==="nav"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="nav").elements.map(e=>({...e,id:uid()}))}; const footer={...SECTION_TEMPLATES.find(t=>t.type==="footer"),id:uid(),elements:SECTION_TEMPLATES.find(t=>t.type==="footer").elements.map(e=>({...e,id:uid()}))}; setPages(p=>({...p,[id]:{id,name:"New Page",slug:`/${id}`,meta:{title:"",description:"",ga4:""},sections:[nav,footer]}})); setActivePid(id); }; const deletePage=pid=>{ if(Object.keys(pages).length<=1)return; const np={...pages};delete np[pid];setPages(np); if(activePid===pid)setActivePid(Object.keys(np)[0]); }; const updatePageName=(pid,name)=>setPages(p=>({...p,[pid]:{...p[pid],name}})); const updateMeta=(key,val)=>setPages(p=>({...p,[activePid]:{...p[activePid],meta:{...p[activePid].meta,[key]:val}}})); // ── Section ops ── const addSection=(type,afterIdx)=>{ const tpl=SECTION_TEMPLATES.find(t=>t.type===type); if(!tpl)return; const newSec={...tpl,id:uid(),elements:tpl.elements.map(e=>({...e,id:uid()}))}; setPages(p=>{ const secs=[...(p[activePid].sections||[])]; const idx=afterIdx!==null?afterIdx+1:secs.length; secs.splice(idx,0,newSec); return{...p,[activePid]:{...p[activePid],sections:secs}}; }); setSelSection(newSec.id);setSelEl(null); }; const removeSection=sid=>{ setPages(p=>({...p,[activePid]:{...p[activePid],sections:(p[activePid].sections||[]).filter(s=>s.id!==sid)}})); if(selSection===sid){setSelSection(null);setSelEl(null);} }; const duplicateSection=sid=>{ const sec=sections.find(s=>s.id===sid);if(!sec)return; const newSec={...sec,id:uid(),elements:sec.elements.map(e=>({...e,id:uid()}))}; setPages(p=>{ const secs=[...p[activePid].sections]; const idx=secs.findIndex(s=>s.id===sid); secs.splice(idx+1,0,newSec); return{...p,[activePid]:{...p[activePid],sections:secs}}; }); }; const moveSection=(sid,dir)=>{ setPages(p=>{ const secs=[...p[activePid].sections]; const idx=secs.findIndex(s=>s.id===sid); const newIdx=idx+dir; if(newIdx<0||newIdx>=secs.length)return p; [secs[idx],secs[newIdx]]=[secs[newIdx],secs[idx]]; return{...p,[activePid]:{...p[activePid],sections:secs}}; }); }; const updateSection=(sid,key,val)=>{ setPages(p=>({...p,[activePid]:{...p[activePid],sections:p[activePid].sections.map(s=>s.id===sid?{...s,[key]:val}:s)}})); }; const updateEl=(sid,eid,key,val)=>{ setPages(p=>({...p,[activePid]:{...p[activePid],sections:p[activePid].sections.map(s=>{ if(s.id!==sid)return s; const updateDeep=(els)=>els.map(e=>{ if(e.id===eid)return{...e,[key]:val}; if(e.left)return{...e,left:updateDeep(e.left)}; if(e.right)return{...e,right:updateDeep(e.right)}; return e; }); return{...s,elements:updateDeep(s.elements)}; })}})); }; // ── Drag ── const handleDrop=(targetId)=>{ if(!dragSec||dragSec===targetId)return; setPages(p=>{ const secs=[...p[activePid].sections]; const fi=secs.findIndex(s=>s.id===dragSec),ti=secs.findIndex(s=>s.id===targetId); if(fi<0||ti<0)return p; const[m]=secs.splice(fi,1);secs.splice(ti,0,m); return{...p,[activePid]:{...p[activePid],sections:secs}}; }); setDragSec(null); }; const save=()=>{ try{localStorage.setItem("sitevana_pages",JSON.stringify(pages));setShowSaved(true);setTimeout(()=>setShowSaved(false),2000);}catch{} }; const pageList=Object.values(pages); const isMobile=viewMode==="mobile"; const isTablet=viewMode==="tablet"; const selSectionObj=sections.find(s=>s.id===selSection); return(
{/* TOP BAR */}
S
SiteVana Standalone
{/* Quick palette */}
{[{id:"ocean",c:"#0ea5e9"},{id:"forest",c:"#22c55e"},{id:"ember",c:"#f97316"},{id:"royal",c:"#8b5cf6"},{id:"rose",c:"#ec4899"},{id:"teal",c:"#14b8a6"}].map(p=>(
setPalette(prev=>({...prev,primary:p.c,id:p.id}))} style={{width:18,height:18,borderRadius:"50%",background:p.c,cursor:"pointer",border:`2px solid ${palette.id===p.id?"#fff":C.border2}`,transition:"border-color 0.15s"}}/> ))}
{showSaved&&
✓ Saved
}
{[["desktop","🖥"],["tablet","📱"],["mobile","📲"]].map(([v,icon])=>( ))}
{/* PAGE TABS */}
{pageList.map(page=>(
{setActivePid(page.id);setSelSection(null);setSelEl(null);}} style={{display:"flex",alignItems:"center",gap:5,padding:"0 10px",height:34,borderRadius:"6px 6px 0 0", background:activePid===page.id?C.bg2:"transparent",border:`1px solid ${activePid===page.id?C.border2:"transparent"}`, cursor:"pointer",flexShrink:0}}> {page.name} {pageList.length>1&&{e.stopPropagation();deletePage(page.id);}} style={{color:C.text3,fontSize:9,cursor:"pointer"}}>✕}
))}
{/* 3 PANEL */}
{/* LEFT — Section library */}
Sections
{["Layout","Sections","Media","Interactive"].map(cat=>{ const catMap={Layout:["nav","footer"],Sections:["hero","services","about","stats","testimonials","pricing","faq","cta"],Media:["gallery"],Interactive:["contact","map"]}; const items=(catMap[cat]||[]).map(type=>SECTION_TEMPLATES.find(t=>t.type===type)).filter(Boolean); if(!items.length)return null; return(
{cat}
{items.map(tpl=>(
{setShowAddSection(true);setAddAfterIdx(sections.length-1);}} onDoubleClick={()=>addSection(tpl.type,sections.length-1)} draggable style={{display:"flex",alignItems:"center",gap:7,padding:"8px 10px",borderRadius:6,marginBottom:3,background:C.bg2,border:`1px solid ${C.border2}`,cursor:"pointer",transition:"all 0.15s"}} onMouseEnter={e=>{e.currentTarget.style.borderColor=C.accent;e.currentTarget.style.background="#0c2340";}} onMouseLeave={e=>{e.currentTarget.style.borderColor=C.border2;e.currentTarget.style.background=C.bg2;}}> {tpl.icon} {tpl.label}
))}
); })}
{/* CANVAS */}
{setSelSection(null);setSelEl(null);}}>
{(isMobile||isTablet)&&
} {sections.map((sec,idx)=>( {/* Add section zone above */}
{setSelSection(sec.id);setSelEl(null);setRightTab("properties");}} onSelEl={id=>{setSelSection(sec.id);setSelEl(id);setRightTab("properties");}} onUpdateEl={updateEl} onRemove={()=>removeSection(sec.id)} onDuplicate={()=>duplicateSection(sec.id)} onMoveUp={()=>moveSection(sec.id,-1)} onMoveDown={()=>moveSection(sec.id,1)} onStartDrag={()=>setDragSec(sec.id)} onDragOver={()=>{}} onDrop={()=>handleDrop(sec.id)} isDragging={dragSec===sec.id} onUpdateSection={updateSection}/>
))} {/* Add zone at bottom */}
{/* RIGHT PANEL */}
{[["properties","Props"],["seo","SEO"],["design","🎨"],["ai","🤖 AI"]].map(([t,l])=>( ))}
{rightTab==="properties"&&( )} {rightTab==="seo"&&( )} {rightTab==="design"&&(
Global Design
Brand Colour
setPalette(p=>({...p,primary:e.target.value}))} style={{width:44,height:36,border:`1px solid ${C.border2}`,borderRadius:6,cursor:"pointer"}}/> setPalette(p=>({...p,primary:e.target.value}))} style={{flex:1,background:C.bg2,border:`1px solid ${C.border2}`,borderRadius:6,padding:"7px 10px",color:C.text1,fontSize:11}}/>
Colour Presets
{[{id:"ocean",label:"Ocean Blue",c:"#0ea5e9"},{id:"forest",label:"Forest Green",c:"#22c55e"},{id:"ember",label:"Ember Orange",c:"#f97316"},{id:"royal",label:"Royal Purple",c:"#8b5cf6"},{id:"rose",label:"Rose Pink",c:"#ec4899"},{id:"teal",label:"Teal",c:"#14b8a6"}].map(p=>(
setPalette(prev=>({...prev,primary:p.c,id:p.id}))} style={{display:"flex",alignItems:"center",gap:7,padding:"8px 10px",background:palette.id===p.id?C.bg3:C.bg2,border:`1px solid ${palette.id===p.id?p.c:C.border2}`,borderRadius:8,cursor:"pointer"}}>
{p.label}
))}
Font Pairing
{[ {id:"classic",label:"Classic — Georgia + DM Sans",h:"'Georgia',serif",b:"'DM Sans',system-ui,sans-serif"}, {id:"modern",label:"Modern — Outfit + Nunito",h:"'Outfit',system-ui,sans-serif",b:"'Nunito',system-ui,sans-serif"}, {id:"elegant",label:"Elegant — Playfair + Lato",h:"'Playfair Display',Georgia,serif",b:"'Lato',system-ui,sans-serif"}, {id:"minimal",label:"Minimal — DM Sans only",h:"'DM Sans',system-ui,sans-serif",b:"'DM Sans',system-ui,sans-serif"}, ].map(fp=>(
setFonts({heading:fp.h,body:fp.b})} style={{padding:"9px 12px",background:fonts.heading===fp.h?C.bg3:C.bg2,border:`1px solid ${fonts.heading===fp.h?C.accent:C.border2}`,borderRadius:8,cursor:"pointer",marginBottom:6}}>
{fp.label.split("—")[0]}
{fp.label.split("—")[1]||""}
))}
)} {rightTab==="ai"&&}
{/* Add Section Modal */} {showAddSection&&( addSection(type,addAfterIdx)} onClose={()=>setShowAddSection(false)}/> )} {/* Export Modal */} {showExport&&setShowExport(false)}/>}
); } ReactDOM.render(,document.getElementById("root"));