buildai / builder.html
VISHAL18for4's picture
Upload 2 files
eddbbf7 verified
Raw
History Blame Contribute Delete
70.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<title>BuildAI β€” Builder</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚑</text></svg>"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
/* ── ROOT LAYOUT β€” works inside HuggingFace iframe ── */
:root{
--bg:#07080f;--bg2:#0b0d17;--bg3:#0f1220;--bg4:#090b12;
--border:#0f1220;--border2:#1a1f33;
--text:#e2e8f0;--muted:#475569;--dim:#334155;--faint:#1e2740;
--accent:#6366f1;--accent2:#8b5cf6;--green:#10b981;
--toolbar-h:46px;--mobtab-h:44px;
}
html{height:100%;height:100dvh;overflow:hidden}
body{
height:100%;height:100dvh;
display:flex;flex-direction:column;
font-family:'Inter',system-ui,sans-serif;
background:var(--bg);color:var(--text);
-webkit-font-smoothing:antialiased;
overflow:hidden;
}
button,input,textarea{font-family:inherit}
::-webkit-scrollbar{width:4px;height:4px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:#1e2740;border-radius:99px}
/* ── ANIMATIONS ── */
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes fadeUp{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:translateY(0)}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes floatY{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
@keyframes blink{0%,100%{opacity:.2}50%{opacity:1}}
@keyframes dotBounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
@keyframes slideDown{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
@keyframes gradFlow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
/* ── TOOLBAR ── */
.tb{
height:var(--toolbar-h);
display:flex;align-items:center;
padding:0 10px;gap:4px;
border-bottom:1px solid var(--border);
background:var(--bg2);
flex-shrink:0;
z-index:200;
overflow:visible;
position:relative;
}
.tb-left{display:flex;align-items:center;gap:4px;flex-shrink:0}
.tb-logo{display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 8px;border-radius:7px;transition:background .15s;text-decoration:none;color:inherit}
.tb-logo:hover{background:#13172a}
.tb-lm{width:20px;height:20px;background:linear-gradient(135deg,var(--accent),var(--accent2));border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
.tb-ln{font-size:13px;font-weight:800;letter-spacing:-.3px}
.tb-proj{display:flex;align-items:center;gap:5px;padding:4px 9px;background:#13172a;border:1px solid var(--border2);border-radius:7px;cursor:pointer;max-width:140px;flex-shrink:0}
.tb-pn{font-size:11px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tb-sep{width:1px;height:22px;background:var(--border);margin:0 3px;flex-shrink:0}
.tb-ic{height:28px;background:transparent;border:none;border-radius:6px;color:var(--dim);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0;transition:background .15s,color .15s;padding:0 8px;gap:4px;white-space:nowrap}
.tb-ic:hover{background:#13172a;color:var(--muted)}
.tb-center{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0}
.tab-b{padding:5px 12px;background:transparent;border:1px solid transparent;border-radius:7px;color:var(--dim);font-size:11px;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s}
.tab-b.on{background:#13172a;border-color:var(--border2);color:#c4d4f0;font-weight:600}
.tab-b:hover:not(.on){color:var(--muted)}
.tb-url{display:flex;align-items:center;gap:5px;padding:4px 10px;background:var(--bg3);border:1px solid #13172a;border-radius:7px;margin-left:6px;max-width:180px;overflow:hidden}
.tb-url-t{font-size:10px;color:var(--dim);font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tb-right{display:flex;align-items:center;gap:4px;flex-shrink:0}
.vp-grp{display:flex;gap:1px;background:var(--bg3);border:1px solid #13172a;border-radius:7px;padding:2px;flex-shrink:0}
.vp-b{width:26px;height:23px;background:transparent;border:none;border-radius:5px;cursor:pointer;font-size:11px;transition:background .15s;display:flex;align-items:center;justify-content:center}
.vp-b.on{background:#1e2740}
.tb-tool{padding:5px 10px;background:#13172a;border:1px solid var(--border2);border-radius:7px;color:var(--muted);font-size:11px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:4px;white-space:nowrap;flex-shrink:0;transition:all .15s}
.tb-tool:hover{border-color:#2e3a55;color:#94a3b8}
.tb-pub{padding:5px 13px;background:linear-gradient(135deg,#059669,var(--green));border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:opacity .2s}
.tb-pub:hover:not(:disabled){opacity:.9}
.tb-pub:disabled{opacity:.4;cursor:default}
.tb-signin{padding:5px 13px;background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0}
.tb-guest{padding:5px 11px;background:#13172a;border:1px solid var(--border2);border-radius:7px;color:var(--muted);font-size:11px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .15s}
.tb-guest:hover{border-color:var(--accent);color:#818cf8}
.tb-av{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;cursor:pointer;flex-shrink:0;position:relative}
.user-dd{position:absolute;top:34px;right:0;width:210px;background:#0e1018;border:1px solid var(--border2);border-radius:12px;z-index:9999;animation:slideDown .15s ease;box-shadow:0 20px 50px rgba(0,0,0,.7);overflow:hidden}
.ud-head{padding:12px 14px;border-bottom:1px solid var(--border)}
.ud-name{font-size:13px;font-weight:600;color:#c4d4f0;margin-bottom:2px}
.ud-email{font-size:11px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ud-item{padding:10px 14px;font-size:12px;color:var(--muted);cursor:pointer;display:flex;align-items:center;gap:8px;transition:background .15s}
.ud-item:hover{background:var(--bg3);color:#94a3b8}
.ud-item.red:hover{color:#f87171}
.ud-sep{height:1px;background:var(--border)}
/* ── MOBILE TABS ── */
.mob-tabs{
display:none;height:var(--mobtab-h);
background:var(--bg2);border-bottom:1px solid var(--border);
flex-shrink:0;
}
.mob-tab{
flex:1;display:flex;align-items:center;justify-content:center;gap:5px;
background:transparent;border:none;border-bottom:2px solid transparent;
color:var(--dim);font-size:12px;font-weight:500;cursor:pointer;transition:all .15s;
}
.mob-tab.on{color:#c4d4f0;border-bottom-color:var(--accent)}
/* ── MAIN AREA β€” critical height containment ── */
.main{
flex:1;
display:flex;
overflow:hidden;
min-height:0; /* CRITICAL: lets flex children shrink below content size */
}
/* ── SIDEBAR ── */
.sb{
width:185px;flex-shrink:0;
display:flex;flex-direction:column;
background:var(--bg2);border-right:1px solid var(--border);
overflow:hidden;min-height:0;
}
.sb-hd{padding:8px 10px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
.sb-lbl{font-size:10px;font-weight:700;color:var(--faint);text-transform:uppercase;letter-spacing:1.2px}
.sb-new{
width:calc(100% - 14px);margin:7px;padding:9px;
background:var(--bg3);border:1px dashed var(--border2);border-radius:8px;
color:var(--dim);font-size:11px;cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:5px;
transition:all .2s;flex-shrink:0;
}
.sb-new:hover{border-color:var(--accent);color:var(--accent)}
.sb-list{flex:1;overflow-y:auto;padding:0 7px 8px;min-height:0}
.sb-sec{font-size:9.5px;font-weight:700;color:#131828;text-transform:uppercase;letter-spacing:1.2px;padding:6px 8px 4px}
.sb-empty{padding:20px 10px;text-align:center;font-size:11px;color:#131828;line-height:1.8}
.sb-signin-prompt{margin:10px 7px;padding:12px;background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.15);border-radius:10px;text-align:center}
.sb-sp-t{font-size:11px;color:var(--dim);margin-bottom:8px;line-height:1.6}
.sb-sp-b{width:100%;padding:7px;background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:600;cursor:pointer}
.pi{display:flex;align-items:center;gap:8px;padding:8px 9px;border-radius:8px;cursor:pointer;margin-bottom:2px;border:1px solid transparent;transition:all .15s}
.pi:hover{background:var(--bg3)}
.pi.on{background:#111e36;border-color:#1e3a5f}
.pi-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.pi-n{font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pi.on .pi-n{color:#c4d4f0;font-weight:500}
.pi-d{font-size:9.5px;color:#131828;margin-top:1px}
.sb-ft{padding:8px;border-top:1px solid var(--border);flex-shrink:0}
.sb-user{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--bg3);border-radius:8px;cursor:pointer;transition:background .15s}
.sb-user:hover{background:#13172a}
.sb-uav{width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}
.sb-un{font-size:11px;font-weight:600;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sb-up{font-size:9.5px;color:var(--faint)}
.sb-signout{font-size:10px;color:var(--dim);background:none;border:none;cursor:pointer;margin-top:4px;display:none;width:100%;text-align:center;padding:4px;transition:color .15s}
.sb-signout:hover{color:#f87171}
/* ── CHAT PANEL ── */
.cp{
width:290px;flex-shrink:0;
display:flex;flex-direction:column;
border-right:1px solid var(--border);
background:var(--bg2);
overflow:hidden;min-height:0;
}
.c-stat{padding:7px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:7px;background:#09091a;flex-shrink:0}
.r-dots{display:flex;gap:3px}
.r-dot{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;font-weight:700;border:1px solid var(--faint);color:var(--faint);transition:all .35s;flex-shrink:0}
.r-dot.act{border-color:var(--accent2);background:rgba(139,92,246,.2);color:#c4b5fd}
.r-dot.dn{border-color:var(--green);background:rgba(16,185,129,.15);color:#34d399}
.c-stat-t{font-size:11px;color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.c-msgs{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px;min-height:0}
.c-empty{display:flex;flex-direction:column;align-items:center;padding:28px 16px;text-align:center}
.c-e-ic{font-size:40px;margin-bottom:12px;animation:floatY 3.5s ease-in-out infinite}
.c-e-t{font-size:14px;font-weight:700;color:#c4d4f0;margin-bottom:6px}
.c-e-s{font-size:12px;color:var(--dim);line-height:1.7;margin-bottom:16px}
.q-prompts{display:flex;flex-direction:column;gap:7px;width:100%}
.q-p{padding:9px 12px;background:var(--bg3);border:1px solid var(--border2);border-radius:9px;color:var(--muted);font-size:11.5px;cursor:pointer;text-align:left;transition:all .2s}
.q-p:hover{border-color:rgba(99,102,241,.35);color:#94a3b8;background:rgba(99,102,241,.06)}
.m-u{display:flex;justify-content:flex-end}
.m-u-b{background:#1a1f33;border-radius:13px 13px 3px 13px;padding:9px 13px;font-size:12.5px;color:#c4d4f0;max-width:87%;line-height:1.6}
.m-a{display:flex;flex-direction:column;gap:7px}
.m-a-lbl{font-size:9px;font-weight:700;color:var(--faint);letter-spacing:1.5px}
.m-card{background:var(--bg3);border:1px solid var(--border2);border-radius:10px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.m-card-h{font-size:12px;font-weight:600;color:#c4d4f0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m-card-tabs{display:flex;gap:4px;flex-shrink:0}
.m-card-tb{padding:4px 9px;background:transparent;border:1px solid var(--faint);border-radius:6px;color:var(--dim);font-size:10px;cursor:pointer;transition:all .15s}
.m-card-tb.on{background:var(--faint);color:#c4d4f0;border-color:#2e3a55}
.m-card-tb:hover:not(.on){color:var(--muted)}
.m-think{background:#0a0f1e;border:1px solid var(--border2);border-radius:10px;padding:12px 14px;font-size:11.5px;line-height:1.9}
.think-hd{display:flex;align-items:center;gap:8px;font-weight:600;color:var(--accent);margin-bottom:8px}
.think-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);flex-shrink:0;animation:blink 1.2s ease-in-out infinite}
.think-ln{color:var(--dim)}
.think-ln.ap{color:var(--muted);margin-bottom:6px}
.think-ln.bl{padding-left:8px}
.think-ln.dn{color:var(--green)}
.m-txt{font-size:12px;color:var(--muted);line-height:1.7}
.m-save-note{font-size:11px;color:var(--green);display:flex;align-items:center;gap:5px}
.m-next-t{font-size:11px;font-weight:700;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-top:4px}
.m-next-i{font-size:11.5px;color:var(--muted);line-height:1.7}
.m-acts{display:flex;gap:6px;flex-wrap:wrap}
.m-act{padding:6px 11px;background:var(--bg3);border:1px solid var(--border2);border-radius:7px;color:var(--muted);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}
.m-act:hover{border-color:rgba(99,102,241,.3);color:#818cf8}
.m-act.primary{background:linear-gradient(135deg,var(--accent),var(--accent2));border-color:transparent;color:#fff;font-weight:600}
.m-act.primary:hover{opacity:.9}
.m-err{background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.15);border-radius:9px;padding:10px 13px;font-size:12px;color:#f87171;line-height:1.6}
.typing{display:flex;gap:4px;padding:4px 0}
.td{width:5px;height:5px;border-radius:50%;background:var(--faint);animation:dotBounce .8s ease-in-out infinite}
.td:nth-child(2){animation-delay:.15s}
.td:nth-child(3){animation-delay:.3s}
.sugg-bar{padding:8px 12px;border-top:1px solid var(--border);display:flex;gap:6px;overflow-x:auto;flex-shrink:0;scrollbar-width:none}
.sugg-bar::-webkit-scrollbar{display:none}
.s-chip{padding:5px 10px;background:var(--bg3);border:1px solid var(--border2);border-radius:999px;color:var(--muted);font-size:11px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .15s}
.s-chip:hover{border-color:rgba(99,102,241,.3);color:#818cf8}
.c-inp-area{padding:8px;border-top:1px solid var(--border);flex-shrink:0;background:#09091a}
.c-inp-row{display:flex;align-items:flex-end;gap:7px;background:rgba(255,255,255,.03);border:1px solid var(--border2);border-radius:13px;padding:7px 7px 7px 13px;transition:border-color .2s}
.c-inp-row:focus-within{border-color:rgba(99,102,241,.35)}
.c-ta{flex:1;background:transparent;border:none;outline:none;color:var(--text);font-size:13px;resize:none;min-height:24px;max-height:80px;line-height:1.6;padding:2px 0}
.c-ta::placeholder{color:var(--faint)}
.c-ta:disabled{opacity:.5;cursor:not-allowed}
.c-send{width:34px;height:34px;border-radius:9px;border:none;flex-shrink:0;background:#1a1f33;color:var(--dim);cursor:default;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .2s}
.c-send.on{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;cursor:pointer}
.c-send.on:hover{opacity:.9}
/* ── PREVIEW PANEL β€” critical layout fix ── */
.pv{
flex:1;
display:flex;flex-direction:column;
overflow:hidden;min-width:0;min-height:0; /* CRITICAL */
}
.pub-bar{padding:5px 14px;background:var(--bg2);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;flex-shrink:0}
.pub-dot{width:5px;height:5px;border-radius:50%;background:var(--green);flex-shrink:0;animation:pulse 2s ease-in-out infinite}
.pub-t{font-size:10px;color:var(--green)}
.pub-dt{font-size:10px;color:#131828;margin-left:auto}
.pv-body{
flex:1;
position:relative;
overflow:hidden; /* contain the iframe/code */
display:flex;align-items:center;justify-content:center;
background:#10121e;
min-height:0; /* CRITICAL */
}
.pv-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;height:100%;width:100%}
.pv-e-ic{font-size:64px;opacity:.09;animation:floatY 3.5s ease-in-out infinite}
.pv-e-t{font-size:15px;font-weight:600;color:#131828}
.pv-overlay{position:absolute;inset:0;background:rgba(7,8,15,.93);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;backdrop-filter:blur(10px)}
.ov-spin{width:48px;height:48px;border-radius:50%;border:3px solid #1a1f33;border-top:3px solid var(--accent);animation:spin .85s linear infinite}
.ov-t{font-size:13px;color:var(--muted);text-align:center;max-width:240px;line-height:1.6}
.ov-s{font-size:11px;color:#131828;text-align:center}
.ov-badges{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:4px}
.ov-b{padding:3px 9px;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.2);border-radius:999px;font-size:10px;color:#a78bfa;font-weight:600}
.ov-b.act{background:rgba(99,102,241,.25);border-color:rgba(99,102,241,.5);color:#c4b5fd}
.ov-bar{width:200px;height:2px;background:#1a1f33;border-radius:999px;overflow:hidden;margin-top:6px}
.ov-prog{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:999px;transition:width .6s ease}
/* Code view β€” must be contained */
.code-wrap{width:100%;height:100%;overflow:auto;background:var(--bg)}
.code-pre{margin:0;padding:20px;font-size:11px;line-height:1.9;color:#5a6a8a;font-family:'JetBrains Mono',monospace;white-space:pre-wrap;word-break:break-all}
/* ── File Tree ── */
.file-tree{width:160px;min-width:140px;background:#0a0c18;border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column}
.file-tree-h{padding:8px 10px;font-size:10px;font-weight:700;color:var(--faint);letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid var(--border)}
.file-item{padding:6px 10px;font-size:10px;font-family:'JetBrains Mono',monospace;color:var(--dim);cursor:pointer;border-left:2px solid transparent;transition:all .12s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.file-item:hover{color:var(--text);background:rgba(99,102,241,.08)}
.file-item.on{color:#a78bfa;border-left-color:#a78bfa;background:rgba(167,139,250,.08)}
.file-item.generating{color:#f59e0b;animation:pulse 1s infinite}
.file-item.done{color:#10b981}
.file-ext-ts{color:#3b82f6}.file-ext-tsx{color:#38bdf8}.file-ext-css{color:#f472b6}.file-ext-js{color:#fbbf24}.file-ext-html{color:#34d399}.file-ext-py{color:#a78bfa}
.code-view{display:flex;height:100%;overflow:hidden}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.pv-btm{padding:7px 14px;border-top:1px solid var(--border);background:var(--bg2);display:flex;align-items:center;gap:7px;flex-shrink:0;flex-wrap:wrap}
.dl-btn{padding:5px 12px;background:var(--bg3);border:1px solid var(--border2);border-radius:7px;color:var(--dim);font-size:11px;cursor:pointer;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap}
.dl-btn:hover{border-color:rgba(99,102,241,.3);color:var(--accent)}
.dl-zip{padding:5px 12px;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.25);border-radius:7px;color:#a78bfa;font-size:11px;cursor:pointer;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap;font-weight:600}
.dl-zip:hover{background:rgba(99,102,241,.2);border-color:rgba(99,102,241,.4);color:#c4b5fd}
.pv-hint{font-size:10px;color:var(--border);margin-left:auto}
/* ── VERSION PANEL ── */
.ver-panel{position:absolute;top:34px;left:0;width:250px;background:#0e1018;border:1px solid var(--border2);border-radius:12px;z-index:9999;animation:slideDown .15s ease;box-shadow:0 20px 50px rgba(0,0,0,.7);overflow:hidden}
.ver-hd{padding:10px 14px;border-bottom:1px solid var(--border2);display:flex;justify-content:space-between;align-items:center}
.ver-hd-t{font-size:12px;font-weight:600}
.ver-x{background:none;border:none;color:var(--dim);cursor:pointer;font-size:16px;line-height:1;padding:0 2px}
.ver-x:hover{color:#94a3b8}
.ver-item{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;cursor:pointer;transition:background .15s}
.ver-item:hover{background:var(--bg3)}
.ver-item.cur{background:#0e1928}
.ver-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
.ver-n{font-size:12px;color:var(--muted)}
.ver-item.cur .ver-n{color:#c4d4f0;font-weight:500}
.ver-d{font-size:10px;color:var(--faint);margin-top:1px}
.ver-cur-lbl{font-size:10px;color:var(--accent);font-weight:600;margin-left:auto}
.ver-empty{padding:16px 14px;font-size:11px;color:#131828;text-align:center}
/* ── MODALS ── */
.modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:9999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(12px);padding:16px;animation:fadeIn .15s ease}
.modal-box{background:#0e1018;border:1px solid var(--border2);border-radius:20px;width:100%;max-height:90vh;overflow-y:auto;animation:slideDown .2s ease;box-shadow:0 40px 100px rgba(0,0,0,.7)}
.mo-p{padding:24px}
.mo-hd{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px;gap:10px}
.mo-t{font-weight:700;font-size:16px}
.mo-sub{font-size:12px;color:var(--muted);margin-top:2px}
.mo-x{background:none;border:none;color:var(--muted);cursor:pointer;font-size:22px;line-height:1;flex-shrink:0;transition:color .15s;padding:0}
.mo-x:hover{color:#94a3b8}
.flbl{font-size:12px;color:var(--muted);font-weight:500;display:block;margin-bottom:6px}
.finp{width:100%;background:var(--bg4);border:1px solid var(--border2);border-radius:11px;padding:12px 14px;color:var(--text);font-size:13px;outline:none;margin-bottom:12px;display:block;transition:border-color .2s}
.finp:focus{border-color:rgba(99,102,241,.4)}
.finp::placeholder{color:var(--faint)}
.mo-btn-p{width:100%;padding:13px;background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;border-radius:11px;color:#fff;font-size:14px;font-weight:700;cursor:pointer;margin-top:4px;transition:opacity .2s}
.mo-btn-p:hover{opacity:.9}
.mo-btn-p:disabled{opacity:.5;cursor:not-allowed}
.mo-btn-s{width:100%;padding:12px;background:var(--bg4);border:1px solid var(--border2);border-radius:11px;color:var(--muted);font-size:13px;cursor:pointer;margin-top:8px;transition:border-color .2s}
.mo-btn-s:hover{border-color:#2e3a55}
.mo-div{display:flex;align-items:center;gap:12px;margin:16px 0}
.mo-div-l{flex:1;height:1px;background:var(--border2)}
.mo-div-t{font-size:11px;color:var(--faint)}
.tab-sw{display:flex;gap:3px;background:var(--bg4);border-radius:11px;padding:3px;margin-bottom:20px}
.tab-sw-b{flex:1;padding:9px;background:transparent;border:none;border-radius:9px;color:var(--dim);font-size:13px;cursor:pointer;transition:all .15s}
.tab-sw-b.on{background:#1a1f33;color:var(--text);font-weight:600}
.auth-err{background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.2);border-radius:9px;padding:10px 13px;font-size:12px;color:#f87171;margin-bottom:12px;display:none}
.auth-ok{background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2);border-radius:9px;padding:10px 13px;font-size:12px;color:#34d399;margin-bottom:12px;display:none}
.suc-c{text-align:center;padding:16px 0 8px}
.suc-ic{font-size:52px;margin-bottom:14px;display:block;animation:floatY 2.5s ease-in-out infinite}
.suc-t{font-weight:900;font-size:19px;margin-bottom:8px;letter-spacing:-.4px}
.pub-plan{background:var(--bg4);border:1px solid var(--border2);border-radius:13px;padding:15px 17px;margin-bottom:9px;cursor:pointer;transition:all .2s}
.pub-plan.on{border-color:var(--accent);border-width:2px}
.pub-plan-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;margin-bottom:4px}
.pub-plan-url{font-size:14px;font-weight:700;color:#a78bfa;font-family:'JetBrains Mono',monospace;margin-bottom:4px}
.pub-plan-d{font-size:11px;color:var(--dim)}
.google-btn{width:100%;padding:13px;background:#fff;border:1px solid #e2e8f0;border-radius:11px;color:#1a1a1a;font-size:14px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:10px;transition:all .2s;margin-bottom:4px}
.google-btn:hover{background:#f8fafc;box-shadow:0 4px 16px rgba(0,0,0,.12)}
/* ── RESPONSIVE ── */
@media(max-width:768px){
.sb{display:none !important}
.mob-tabs{display:flex !important}
.cp{width:100% !important;border-right:none !important}
.pv{width:100% !important}
.tb-tool,.vp-grp,.tb-url{display:none !important}
/* Panel switching via classes on .main */
.main{flex-direction:row} /* keep row, children handle display */
.main.show-chat .cp{display:flex !important;flex:1 !important;min-width:0}
.main.show-chat .pv{display:none !important}
.main.show-preview .pv{display:flex !important;flex:1 !important;min-width:0}
.main.show-preview .cp{display:none !important}
}
</style>
</head>
<body>
<!-- ── TOOLBAR ── -->
<div class="tb">
<div class="tb-left">
<div class="tb-logo" onclick="goHome()">
<div class="tb-lm">⚑</div>
<span class="tb-ln">BuildAI</span>
</div>
<div class="tb-proj">
<span class="tb-pn" id="tb-pn">New Project</span>
<span style="font-size:8px;color:var(--dim)">β–Ύ</span>
</div>
<div class="tb-sep"></div>
<div style="position:relative">
<button class="tb-ic" id="ver-btn" onclick="toggleVer(event)">πŸ• <span style="font-weight:600">History</span></button>
<div class="ver-panel" id="ver-panel" style="display:none">
<div class="ver-hd">
<span class="ver-hd-t">Version History</span>
<button class="ver-x" onclick="closeVer()">Γ—</button>
</div>
<div id="ver-list"><div class="ver-empty">No versions yet.</div></div>
</div>
</div>
<button class="tb-ic" onclick="undoVer()" title="Undo last version">↩</button>
</div>
<div class="tb-center">
<button class="tab-b on" id="tab-preview" onclick="switchTab('preview')">πŸ‘ Preview</button>
<button class="tab-b" id="tab-code" onclick="switchTab('code')">&lt;/&gt; Code</button>
<div class="tb-url" id="tb-url" style="display:none">
<span style="font-size:10px">πŸ”’</span>
<span class="tb-url-t" id="tb-url-t">my-project.buildai.app</span>
</div>
</div>
<div class="tb-right">
<div class="vp-grp">
<button class="vp-b on" id="vp-d" onclick="setVP('d')" title="Desktop">πŸ–₯</button>
<button class="vp-b" id="vp-t" onclick="setVP('t')" title="Tablet">⬜</button>
<button class="vp-b" id="vp-m" onclick="setVP('m')" title="Mobile">πŸ“±</button>
</div>
<button class="tb-tool" onclick="openM('github')">πŸ™ GitHub</button>
<button class="tb-pub" id="pub-tb" disabled onclick="openM('publish')">πŸš€ Publish</button>
<div id="tb-user" style="position:relative">
<button class="tb-signin" onclick="openM('auth','signin')">Sign in</button>
</div>
</div>
</div>
<!-- ── MOBILE TABS ── -->
<div class="mob-tabs" id="mob-tabs">
<button class="mob-tab on" id="mob-chat" onclick="mobTab('chat')">πŸ’¬ Chat</button>
<button class="mob-tab" id="mob-preview" onclick="mobTab('preview')">πŸ‘ Preview</button>
<button class="mob-tab" id="mob-code" onclick="mobTab('code')">&lt;/&gt; Code</button>
</div>
<!-- ── MAIN AREA ── -->
<div class="main show-chat" id="main-area">
<!-- SIDEBAR -->
<div class="sb">
<div class="sb-hd"><span class="sb-lbl">Projects</span></div>
<button class="sb-new" onclick="newProj()">οΌ‹ New Project</button>
<div class="sb-list" id="sb-list">
<div class="sb-sec">Recent</div>
<div id="sb-empty" class="sb-empty">No projects yet.<br/>Build one to save.</div>
<div id="sb-auth-prompt" class="sb-signin-prompt" style="display:none">
<div class="sb-sp-t">Sign in to save projects across sessions</div>
<button class="sb-sp-b" onclick="openM('auth','signin')">Sign in free β†’</button>
</div>
</div>
<div class="sb-ft">
<div class="sb-user" id="sb-user-box" onclick="openM('auth','signin')">
<div class="sb-uav" id="sb-av">?</div>
<div style="overflow:hidden;flex:1">
<div class="sb-un" id="sb-nm">Guest mode</div>
<div class="sb-up" id="sb-plan">Tap to sign in</div>
</div>
</div>
<button class="sb-signout" id="sb-signout" onclick="doSignOut()">Sign out</button>
</div>
</div>
<!-- CHAT PANEL -->
<div class="cp" id="chat-panel">
<div class="c-stat">
<div class="r-dots">
<div class="r-dot" id="rd1">1</div>
<div class="r-dot" id="rd2">2</div>
<div class="r-dot" id="rd3">3</div>
</div>
<div class="c-stat-t" id="c-stat-t">Ready to build</div>
</div>
<div class="c-msgs" id="c-msgs">
<div class="c-empty" id="c-empty">
<div class="c-e-ic">πŸ—οΈ</div>
<div class="c-e-t">What are we building?</div>
<div class="c-e-s">5 AI models collaborate to create your website with React + Tailwind.</div>
<div class="q-prompts">
<button class="q-p" onclick="setBP('SaaS landing page with pricing, features, and testimonials')">πŸ’Ό SaaS landing page</button>
<button class="q-p" onclick="setBP('Premium e-commerce store with product grid and cart')">πŸ›’ E-commerce store</button>
<button class="q-p" onclick="setBP('Dark analytics dashboard with charts and KPI metrics')">πŸ“Š Analytics dashboard</button>
</div>
</div>
</div>
<div class="sugg-bar" id="sugg-bar" style="display:none">
<button class="s-chip" onclick="setBI(this.textContent)">Add Testimonials</button>
<button class="s-chip" onclick="setBI(this.textContent)">Add Pricing Table</button>
<button class="s-chip" onclick="setBI(this.textContent)">Add FAQ Section</button>
<button class="s-chip" onclick="setBI(this.textContent)">Make Dark Theme</button>
<button class="s-chip" onclick="setBI(this.textContent)">Add Animations</button>
<button class="s-chip" onclick="setBI(this.textContent)">Bento Grid Layout</button>
</div>
<div class="c-inp-area">
<div class="c-inp-row">
<textarea class="c-ta" id="c-inp" placeholder="Describe your website or request changes..." rows="1"
onkeydown="chatKey(event)" oninput="updSend();autoR(this)"></textarea>
<button class="c-send" id="c-send" onclick="sendMsg()" disabled>↑</button>
</div>
</div>
</div>
<!-- PREVIEW PANEL -->
<div class="pv" id="preview-panel">
<div class="pub-bar" id="pub-bar" style="display:none">
<div class="pub-dot"></div>
<span class="pub-t">Published</span>
<span class="pub-dt" id="pub-dt"></span>
</div>
<div class="pv-body" id="pv-body">
<div class="pv-empty">
<div class="pv-e-ic">🌐</div>
<div class="pv-e-t">Preview will appear here</div>
</div>
</div>
<div class="pv-btm" id="pv-btm" style="display:none">
<button class="dl-btn" onclick="dlFile()">⬇ Download HTML</button>
<button class="dl-zip" onclick="dlZip()">πŸ“¦ Download ZIP</button>
<span class="pv-hint">Refine via chat anytime</span>
</div>
</div>
</div>
<!-- ── AUTH MODAL ── -->
<div class="modal-bg" id="modal-auth" style="display:none" onclick="if(event.target===this)closeM('auth')">
<div class="modal-box" style="max-width:390px">
<div class="mo-p">
<div class="mo-hd">
<div style="display:flex;align-items:center;gap:9px">
<div style="width:28px;height:28px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px">⚑</div>
<span style="font-weight:800;font-size:17px">BuildAI</span>
</div>
<button class="mo-x" onclick="closeM('auth')">Γ—</button>
</div>
<div class="tab-sw">
<button class="tab-sw-b on" id="at-si" onclick="switchAuth('signin')">Sign in</button>
<button class="tab-sw-b" id="at-su" onclick="switchAuth('signup')">Create account</button>
</div>
<div class="auth-err" id="auth-err"></div>
<div class="auth-ok" id="auth-ok"></div>
<button class="google-btn" onclick="doGoogleAuth()">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
Continue with Google
</button>
<div class="mo-div"><div class="mo-div-l"></div><div class="mo-div-t">or email</div><div class="mo-div-l"></div></div>
<label class="flbl">Email</label>
<input class="finp" id="a-email" type="email" placeholder="you@example.com"/>
<label class="flbl">Password</label>
<input class="finp" id="a-pass" type="password" placeholder="At least 6 characters" onkeydown="if(event.key==='Enter')doAuth()"/>
<div id="name-wrap" style="display:none">
<label class="flbl">Full name (optional)</label>
<input class="finp" id="a-name" placeholder="Your name"/>
</div>
<button class="mo-btn-p" id="a-sub" onclick="doAuth()">Sign in β†’</button>
<button class="mo-btn-s" onclick="guestMode();closeM('auth')">Continue as guest (no saving)</button>
<div style="font-size:11px;color:var(--faint);text-align:center;margin-top:14px" id="a-sw-area">
Don't have an account? <span onclick="switchAuth('signup')" style="color:#818cf8;cursor:pointer;font-weight:600">Sign up free</span>
</div>
</div>
</div>
</div>
<!-- ── GITHUB MODAL ── -->
<div class="modal-bg" id="modal-github" style="display:none" onclick="if(event.target===this)closeM('github')">
<div class="modal-box" style="max-width:440px">
<div class="mo-p">
<div class="mo-hd">
<div style="display:flex;align-items:center;gap:12px">
<div style="width:40px;height:40px;background:#161b22;border:1px solid #30363d;border-radius:11px;display:flex;align-items:center;justify-content:center;font-size:22px">πŸ™</div>
<div><div class="mo-t">Push to GitHub</div><div class="mo-sub">Sync your website to a repo</div></div>
</div>
<button class="mo-x" onclick="closeM('github')">Γ—</button>
</div>
<div id="gh-form">
<label class="flbl">GitHub Personal Access Token</label>
<input class="finp" id="gh-token" placeholder="ghp_xxxxxxxxxxxxxxxxxxxx" style="font-family:'JetBrains Mono',monospace;font-size:12px"/>
<label class="flbl">Repository name</label>
<input class="finp" id="gh-repo" placeholder="my-website"/>
<p style="font-size:11px;color:var(--faint);margin-bottom:16px">Get token at github.com/settings/tokens β€” needs <code style="background:#090b12;padding:2px 5px;border-radius:4px">repo</code> scope</p>
<button class="mo-btn-p" style="background:#238636" id="gh-btn" onclick="doGithub()">Push to GitHub β†’</button>
</div>
<div id="gh-done" style="display:none">
<div class="suc-c">
<span class="suc-ic">πŸŽ‰</span>
<div class="suc-t">Pushed!</div>
<div style="background:#090b12;border:1px solid var(--border2);border-radius:10px;padding:12px;margin-bottom:18px;font-family:'JetBrains Mono',monospace;font-size:12px;color:#818cf8;word-break:break-all" id="gh-url-out"></div>
<button class="mo-btn-p" onclick="closeM('github')">Done</button>
</div>
</div>
</div>
</div>
</div>
<!-- ── PUBLISH MODAL ── -->
<div class="modal-bg" id="modal-publish" style="display:none" onclick="if(event.target===this)closeM('publish')">
<div class="modal-box" style="max-width:420px">
<div class="mo-p">
<div id="pub-main">
<div class="mo-hd">
<div class="mo-t">πŸš€ Publish Website</div>
<button class="mo-x" onclick="closeM('publish')">Γ—</button>
</div>
<p style="font-size:13px;color:var(--muted);margin-bottom:20px">Your site goes live instantly with a unique URL.</p>
<div class="pub-plan on">
<div class="pub-plan-badge" style="color:#6366f1">Free Β· Instant</div>
<div class="pub-plan-url" id="pub-subdomain-display">my-project.buildai.app</div>
<div class="pub-plan-d">Always-on URL with SSL</div>
</div>
<button class="mo-btn-p" style="background:linear-gradient(135deg,#059669,#10b981)" id="pub-go-btn" onclick="doPublish()">πŸš€ Publish now</button>
</div>
<div id="pub-done" style="display:none">
<div style="text-align:right"><button class="mo-x" onclick="closeM('publish')">Γ—</button></div>
<div class="suc-c">
<span class="suc-ic">πŸŽ‰</span>
<div class="suc-t">Your site is live!</div>
<div style="padding:13px;background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.2);border-radius:12px;margin-bottom:20px;font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:#a78bfa;word-break:break-all" id="pub-live-url"></div>
<div style="display:flex;gap:8px">
<button class="mo-btn-p" style="flex:1" id="pub-open-btn">Open site β†—</button>
<button class="mo-btn-s" style="flex:1" onclick="closeM('publish')">Done</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ── ALL JAVASCRIPT ── -->
<script>
var API = window.location.origin;
var S = {
user: null,
generating: false,
code: '',
tab: 'preview',
vp: 'd',
prompt: '',
projectName: 'New Project',
projectId: null,
versions: [],
versionIdx: -1,
authMode: 'signin',
files: {}, // filename β†’ content
activeFile: null, // currently viewed file
projectType: 'html' // 'html' or 'react'
};
/* ── HELPERS ── */
function goHome(){ window.location.href = '/'; }
function escH(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function slugify(s){ return (s||'project').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'').slice(0,28)||'my-project'; }
function $(id){ return document.getElementById(id); }
/* ── MOBILE PANEL SWITCHING ── */
function mobTab(w){
['chat','preview','code'].forEach(function(t){
var b = $('mob-'+t);
if(b) b.className = 'mob-tab' + (t===w?' on':'');
});
var m = $('main-area');
if(!m) return;
if(w === 'chat'){
m.className = 'main show-chat';
} else {
m.className = 'main show-preview';
S.tab = (w === 'code') ? 'code' : 'preview';
updTabs();
if(S.code) renderPV();
}
}
/* ── TAB SWITCHING ── */
function updTabs(){
['preview','code'].forEach(function(t){
var b = $('tab-'+t);
if(b) b.className = 'tab-b' + (t===S.tab?' on':'');
});
}
function switchTab(t){
S.tab = t;
updTabs();
if(t === 'code' && Object.keys(S.files).length > 0){
renderFileTree(false);
} else if(S.code) {
renderPV();
}
}
/* ── VIEWPORT ── */
function setVP(v){
S.vp = v;
['d','t','m'].forEach(function(x){
var b = $('vp-'+x);
if(b) b.className = 'vp-b' + (x===v?' on':'');
});
if(S.code && S.tab==='preview') renderPV();
}
/* ── PREVIEW RENDERING ── */
function setPV(h){
var el = $('pv-body');
if(el) el.innerHTML = h;
}
function renderPV(){
if(!S.code) return;
var pv = $('pv-body');
if(!pv) return;
if(S.tab === 'preview'){
var w = S.vp==='d' ? '100%' : S.vp==='t' ? '768px' : '390px';
var ws = S.vp!=='d'
? 'width:'+w+';height:100%;box-shadow:0 0 80px rgba(0,0,0,.8);border-radius:10px;overflow:hidden;flex-shrink:0'
: 'width:100%;height:100%';
pv.innerHTML = '';
var wrap = document.createElement('div');
wrap.setAttribute('style', ws);
var fr = document.createElement('iframe');
fr.setAttribute('style','width:100%;height:100%;border:none;display:block');
fr.setAttribute('sandbox','allow-scripts allow-forms allow-popups allow-modals allow-pointer-lock allow-presentation');
// Use srcdoc β€” works reliably in all sandboxed iframe environments (HuggingFace, etc.)
// document.write() fails in nested sandboxed iframes; srcdoc does not.
fr.srcdoc = S.code;
wrap.appendChild(fr);
pv.appendChild(wrap);
} else {
// Code view β€” wrapped in a proper scrollable container
pv.innerHTML = '<div class="code-wrap"><pre class="code-pre">' + escH(S.code) + '</pre></div>';
}
}
/* ── STATUS BAR ── */
function setStat(msg, round, done){
var el = $('c-stat-t');
if(el) el.textContent = msg;
if(round < 1) return;
for(var i=1; i<=3; i++){
var d = $('rd'+i);
if(!d) continue;
if(i < round || (i===round && done)){ d.className='r-dot dn'; d.textContent='βœ“'; }
else if(i === round){ d.className='r-dot act'; d.textContent=i; }
else{ d.className='r-dot'; d.textContent=i; }
}
}
/* ── OVERLAY ── */
function showOvl(msg, round){
var pct = Math.min(((round-1)/3)*100, 99) + '%';
var names = ['Cerebras','Gemini','Groq'];
var badges = '';
names.forEach(function(n,i){
var isDone=(round-1)>i, isAct=(round-1)===i;
badges += '<span class="ov-b'+(isAct?' act':'')+'">'+( isDone?'βœ“ ':'')+n+'</span>';
});
setPV(
'<div class="pv-overlay">' +
'<div class="ov-spin"></div>' +
'<div><div class="ov-t">'+escH(msg)+'</div><div class="ov-s">~30 seconds total</div></div>' +
'<div class="ov-badges">'+badges+'</div>' +
'<div class="ov-bar"><div class="ov-prog" style="width:'+pct+'"></div></div>' +
'</div>'
);
}
/* ── VERSION HISTORY ── */
function addVer(code, round, label){
S.versions.push({code:code, round:round, label:label, time:new Date().toLocaleTimeString()});
S.versionIdx = S.versions.length - 1;
renderVers();
}
function renderVers(){
var list = $('ver-list');
if(!list) return;
if(!S.versions.length){ list.innerHTML='<div class="ver-empty">No versions yet.</div>'; return; }
list.innerHTML = '';
S.versions.slice().reverse().forEach(function(v,i){
var ri = S.versions.length-1-i, isCur = ri===S.versionIdx;
var el = document.createElement('div');
el.className = 'ver-item' + (isCur?' cur':'');
el.innerHTML =
'<div class="ver-dot" style="background:'+(isCur?'#6366f1':'#1e2740')+'"></div>' +
'<div><div class="ver-n">'+escH(v.label)+'</div><div class="ver-d">'+v.time+'</div></div>' +
(isCur ? '<span class="ver-cur-lbl">Current</span>' : '');
(function(idx){
el.onclick = function(){
S.code = S.versions[idx].code;
S.versionIdx = idx;
renderPV(); renderVers(); closeVer();
};
})(ri);
list.appendChild(el);
});
}
function undoVer(){
if(S.versionIdx > 0){
S.versionIdx--;
S.code = S.versions[S.versionIdx].code;
renderPV(); renderVers();
}
}
function toggleVer(e){
if(e) e.stopPropagation();
var p = $('ver-panel');
if(p) p.style.display = (p.style.display==='none'||!p.style.display) ? 'block' : 'none';
}
function closeVer(){
var p = $('ver-panel');
if(p) p.style.display = 'none';
}
/* ── SEND / INPUT ── */
function updSend(){
var inp = $('c-inp'), btn = $('c-send');
if(!inp||!btn) return;
var ok = inp.value.trim() && !S.generating;
btn.className = 'c-send' + (ok?' on':'');
btn.disabled = !ok;
inp.disabled = S.generating;
}
function autoR(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,80)+'px'; }
function chatKey(e){ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); sendMsg(); } }
function addUMsg(t){
var m=$('c-msgs'), d=document.createElement('div');
d.className='m-u';
d.innerHTML='<div class="m-u-b">'+escH(t)+'</div>';
m.appendChild(d); m.scrollTop=m.scrollHeight;
}
function setBP(t){
var el=$('c-inp');
if(el){ el.value=t; updSend(); autoR(el); setTimeout(sendMsg,80); }
}
function setBI(t){
var el=$('c-inp');
if(el){ el.value=t; updSend(); el.focus(); }
}
/* ── SEND MESSAGE & BUILD ── */
function sendMsg(){
var el=$('c-inp'), t=el.value.trim();
if(!t||S.generating) return;
el.value=''; updSend(); autoR(el);
addUMsg(t);
S.prompt=t; S.generating=true; S.code=''; S.projectId=null;
var name=t.length>26?t.slice(0,26)+'...':t;
S.projectName=name;
var pn=$('tb-pn'); if(pn) pn.textContent=name;
var ce=$('c-empty'); if(ce) ce.style.display='none';
['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=$(id);if(el)el.style.display='none';});
var pb=$('pub-tb'); if(pb) pb.disabled=true;
var tu=$('tb-url'); if(tu) tu.style.display='none';
setPV(
'<div class="pv-overlay">' +
'<div class="ov-spin"></div>' +
'<div class="ov-t">Connecting to AI models...</div>' +
'<div class="ov-badges">' +
'<span class="ov-b act">Cerebras</span>' +
'<span class="ov-b">Gemini</span>' +
'<span class="ov-b">Groq</span>' +
'</div></div>'
);
addThinking();
doFetch(t);
}
/* ── THINKING INDICATOR ── */
var thinkEl = null;
function addThinking(){
rmThinking();
var m = $('c-msgs');
thinkEl = document.createElement('div');
thinkEl.className = 'm-a';
thinkEl.id = 'think-el';
thinkEl.innerHTML =
'<div class="m-a-lbl">BUILDAI</div>' +
'<div class="m-think">' +
'<div class="think-hd"><div class="think-dot"></div>5-model pipeline starting...</div>' +
'<div class="think-ln ap">React + Tailwind Β· Syne + DM Sans Β· Reference engine</div>' +
'<div class="think-ln bl">⚑ Round 1: Cerebras β€” Ultra-fast builder</div>' +
'<div class="think-ln bl">🧠 Round 2: Gemini Flash β€” Design reviewer</div>' +
'<div class="think-ln bl">✨ Round 3: Groq β€” Final polish + SEO</div>' +
'</div>' +
'<div class="typing"><div class="td"></div><div class="td"></div><div class="td"></div></div>';
m.appendChild(thinkEl);
m.scrollTop = m.scrollHeight;
}
function rmThinking(){
var t=$('think-el');
if(t) t.remove();
thinkEl = null;
}
/* ── BUILD ERROR ── */
function buildErr(msg){
S.generating=false; rmThinking(); setStat('Build failed',0,false);
var m=$('c-msgs'), el=document.createElement('div');
el.className='m-a';
el.innerHTML='<div class="m-a-lbl">BUILDAI</div><div class="m-err">⚠️ '+escH(msg)+'</div>';
m.appendChild(el); m.scrollTop=m.scrollHeight;
updSend();
}
/* ── FETCH / SSE ── */
function doFetch(prompt){
fetch(API+'/api/build', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({prompt:prompt})
})
.then(function(resp){
if(!resp.ok) throw new Error('Server error '+resp.status+'. Is the Space awake?');
var reader=resp.body.getReader(), dec=new TextDecoder(), buf='';
function read(){
return reader.read().then(function(r){
if(r.done){
if(!S.code) buildErr('Build ended without result. Check API keys in HuggingFace Secrets.');
return;
}
buf += dec.decode(r.value,{stream:true});
var lines = buf.split('\n'); buf = lines.pop();
lines.forEach(function(line){
if(line.startsWith('data: ')){
try{ handleEv(JSON.parse(line.slice(6))); }catch(e){}
}
});
return read();
});
}
return read();
})
.catch(function(e){ buildErr(e.message||'Cannot connect to backend.'); });
}
function handleEv(d){
if(d.type==='status'){ setStat(d.message,d.round,false); showOvl(d.message,d.round); }
else if(d.type==='manifest'){
// Show planned files in file tree early
S.files = {}; S.projectType = d.project_type||'html';
d.files.forEach(function(f){ S.files[f.name] = ''; });
renderFileTree(true);
setStat(d.message,1,false);
}
else if(d.type==='file_start'){
// Mark file as generating
var el = document.querySelector('[data-file="'+d.file+'"]');
if(el) el.className = 'file-item generating';
setStat(d.message, 1, false);
}
else if(d.type==='file_done'){
S.files[d.file] = d.content;
if(!S.activeFile) S.activeFile = d.file;
renderFileTree(false);
var el = document.querySelector('[data-file="'+d.file+'"]');
if(el){ el.className='file-item done '+fileExtClass(d.file); }
setStat(d.message, 1, false);
}
else if(d.type==='code'){
S.code=d.code;
if(d.files) S.files = d.files;
if(d.project_type) S.projectType = d.project_type;
addVer(d.code,d.round,d.message);
setStat(d.message,d.round,true);
if(d.final) finish();
}
else if(d.type==='error'){ buildErr(d.message); }
}
function fileExtClass(fname){
var ext = (fname.split('.').pop()||'').toLowerCase();
var map={ts:'file-ext-ts',tsx:'file-ext-tsx',css:'file-ext-css',js:'file-ext-js',html:'file-ext-html',py:'file-ext-py'};
return map[ext]||'';
}
function renderFileTree(placeholder){
var pv = $('pv-body');
if(!pv || S.tab !== 'code') return;
var names = Object.keys(S.files);
if(!names.length) return;
var items = names.map(function(fname){
var isActive = fname===S.activeFile;
var ext = (fname.split('.').pop()||'').toLowerCase();
var icon = {ts:'πŸ“˜',tsx:'βš›οΈ',css:'🎨',js:'πŸ“œ',html:'🌐',py:'🐍'}[ext]||'πŸ“„';
var status = placeholder ? 'file-item' : (S.files[fname] ? 'file-item done '+fileExtClass(fname) : 'file-item');
return '<div class="'+status+(isActive?' on':'')+'" data-file="'+escH(fname)+'" onclick="openFile(\''+escH(fname)+'\')" title="'+escH(fname)+'">'+icon+' '+escH(fname.split('/').pop())+'</div>';
}).join('');
var tree = '<div class="file-tree"><div class="file-tree-h">πŸ“ Files</div>'+items+'</div>';
var content = S.activeFile && S.files[S.activeFile]
? '<div class="code-wrap" style="flex:1"><pre class="code-pre">'+escH(S.files[S.activeFile])+'</pre></div>'
: '<div class="code-wrap" style="flex:1;display:flex;align-items:center;justify-content:center;color:#3a4060;font-size:12px">Select a file</div>';
pv.innerHTML = '<div class="code-view">'+tree+content+'</div>';
}
function openFile(fname){
S.activeFile = fname;
renderFileTree(false);
}
/* ── FINISH ── */
async function finish(){
S.generating = false; rmThinking();
[1,2,3].forEach(function(i){var d=$('rd'+i);if(d){d.className='r-dot dn';d.textContent='βœ“';}});
setStat('βœ… Website ready!',0,false);
var savedNote = '';
if(S.user && !S.user.isGuest){
await autoSave();
addProjItem(S.projectName, S.projectId, true, S.code);
savedNote = '<div class="m-save-note">βœ… Saved to your account</div>';
}
var m=$('c-msgs'), ai=document.createElement('div');
ai.className='m-a';
ai.innerHTML=
'<div class="m-a-lbl">BUILDAI</div>' +
'<div class="m-card">' +
'<div class="m-card-h">βš›οΈ '+escH(S.projectName)+'</div>' +
'<div class="m-card-tabs">' +
'<button class="m-card-tb" onclick="switchTab(\'code\');if(window.innerWidth<=768)mobTab(\'code\')">Code</button>' +
'<button class="m-card-tb on" onclick="switchTab(\'preview\');if(window.innerWidth<=768)mobTab(\'preview\')">Preview</button>' +
'</div></div>' +
savedNote +
'<div class="m-txt">Built with React + Tailwind Β· Cerebras + Gemini + Groq pipeline.</div>' +
'<div class="m-next-t">What\'s next?</div>' +
'<div class="m-next-i"><b>Refine:</b> "Make hero darker", "add pricing", "change to blue"</div>' +
'<div class="m-acts">' +
'<button class="m-act primary" onclick="dlZip()">πŸ“¦ ZIP</button>' +
'<button class="m-act" onclick="dlFile()">⬇ HTML</button>' +
'<button class="m-act" onclick="openM(\'publish\')">πŸš€ Publish</button>' +
'<button class="m-act" onclick="openM(\'github\')">πŸ™ GitHub</button>' +
'</div>';
m.appendChild(ai); m.scrollTop=m.scrollHeight;
renderPV();
['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=$(id);if(el)el.style.display='flex';});
var pb=$('pub-tb'); if(pb) pb.disabled=false;
var tu=$('tb-url');
if(tu){
tu.style.display='flex';
var tt=$('tb-url-t'); if(tt) tt.textContent=slugify(S.projectName)+'.buildai.app';
}
if(window.innerWidth<=768) setTimeout(function(){mobTab('preview');},500);
updSend();
}
/* ── NEW PROJECT ── */
function newProj(){
document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});
var pn=$('tb-pn'); if(pn) pn.textContent='New Project';
var cm=$('c-msgs');
if(cm) cm.innerHTML=
'<div class="c-empty" id="c-empty">' +
'<div class="c-e-ic">πŸ—οΈ</div>' +
'<div class="c-e-t">What are we building?</div>' +
'<div class="c-e-s">Describe your website below.</div>' +
'<div class="q-prompts">' +
'<button class="q-p" onclick="setBP(\'SaaS landing page with pricing\')">πŸ’Ό SaaS</button>' +
'<button class="q-p" onclick="setBP(\'E-commerce store with products\')">πŸ›’ E-commerce</button>' +
'<button class="q-p" onclick="setBP(\'Analytics dashboard\')">πŸ“Š Dashboard</button>' +
'</div></div>';
S.code=''; S.generating=false; S.versions=[]; S.versionIdx=-1; S.projectId=null; S.projectName='New Project';
[1,2,3].forEach(function(i){var d=$('rd'+i);if(d){d.className='r-dot';d.textContent=i;}});
setStat('Ready to build',0,false);
['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=$(id);if(el)el.style.display='none';});
var pb=$('pub-tb'); if(pb) pb.disabled=true;
var tu=$('tb-url'); if(tu) tu.style.display='none';
renderVers();
setPV('<div class="pv-empty"><div class="pv-e-ic">🌐</div><div class="pv-e-t">Preview will appear here</div></div>');
if(window.innerWidth<=768) mobTab('chat');
updSend();
}
/* ── SIDEBAR PROJECTS ── */
function clearSBProjects(){
$('sb-list').querySelectorAll('.pi').forEach(function(p){p.remove();});
var se=$('sb-empty'); if(se) se.style.display='block';
}
function addProjItem(name, id, makeCurrent, code){
var se=$('sb-empty'); if(se) se.style.display='none';
if(makeCurrent) document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});
var colors=['#6366f1','#8b5cf6','#10b981','#f59e0b','#ec4899'];
var c = colors[Math.floor(Math.random()*colors.length)];
var el = document.createElement('div');
el.className='pi'+(makeCurrent?' on':'');
el.dataset.id=id||''; el.dataset.code=code||'';
el.innerHTML=
'<div class="pi-dot" style="background:'+c+'"></div>' +
'<div style="overflow:hidden;flex:1">' +
'<div class="pi-n">'+escH(name)+'</div>' +
'<div class="pi-d">'+new Date().toLocaleDateString('en-US',{day:'numeric',month:'short'})+'</div>' +
'</div>';
el.onclick = function(){
document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});
el.classList.add('on');
var pn=$('tb-pn'); if(pn) pn.textContent=name;
if(el.dataset.code){
S.code=el.dataset.code; S.projectName=name; S.projectId=el.dataset.id;
renderPV();
var pb=$('pv-btm'); if(pb) pb.style.display='flex';
var ptb=$('pub-tb'); if(ptb) ptb.disabled=false;
if(window.innerWidth<=768) mobTab('preview');
}
};
var list=$('sb-list'), first=list.querySelector('.pi');
if(first) list.insertBefore(el,first); else list.appendChild(el);
}
/* ── DOWNLOAD ── */
function dlFile(){
if(!S.code) return;
var blob=new Blob([S.code],{type:'text/html'}), url=URL.createObjectURL(blob);
var a=document.createElement('a'); a.href=url; a.download=slugify(S.projectName)+'.html'; a.click();
URL.revokeObjectURL(url);
}
function dlZip(){
if(!S.code && !Object.keys(S.files).length){ return; }
if(typeof JSZip==='undefined'){ alert('JSZip not loaded. Downloading HTML instead.'); dlFile(); return; }
try{
var zip=new JSZip(), slug=slugify(S.projectName);
if(Object.keys(S.files).length > 1){
Object.keys(S.files).forEach(function(fname){
if(S.files[fname]) zip.file(fname, S.files[fname]);
});
zip.file('README.md','# '+slug+'\nBuilt with BuildAI.\n\nOpen index.html in a browser or deploy to Netlify/Vercel/GitHub Pages.');
} else {
var code=S.code;
var styleMatches=code.match(/<style[^>]*>([\s\S]*?)<\/style>/gi)||[];
var css='/* BuildAI Generated */\n\n', htmlNo=code;
styleMatches.forEach(function(m,i){
css+=m.replace(/<style[^>]*>/i,'').replace(/<\/style>/i,'')+'\n';
if(i===0) htmlNo=htmlNo.replace(m,'<link rel="stylesheet" href="style.css"/>');
else htmlNo=htmlNo.replace(m,'');
});
zip.file('index.html',htmlNo); zip.file('style.css',css);
zip.file('README.md','# '+slug+'\nBuilt with BuildAI.\n\nOpen index.html in browser.');
}
zip.generateAsync({type:'blob'}).then(function(blob){
var url=URL.createObjectURL(blob), a=document.createElement('a');
a.href=url; a.download=slug+'.zip'; a.click(); URL.revokeObjectURL(url);
});
}catch(e){ console.error(e); dlFile(); }
}
/* ── MODALS ── */
function openM(name, extra){
if(name==='auth'){ S.authMode=extra||'signin'; switchAuth(S.authMode); }
if(name==='publish'){
var pd=$('pub-subdomain-display');
if(pd) pd.textContent=slugify(S.projectName)+'.buildai.app';
}
var el=$('modal-'+name);
if(el) el.style.display='flex';
}
function closeM(name){
var el=$('modal-'+name);
if(el) el.style.display='none';
if(name==='publish'){
var pm=$('pub-main'),pd=$('pub-done'),pb=$('pub-go-btn');
if(pm) pm.style.display='block'; if(pd) pd.style.display='none';
if(pb){ pb.textContent='πŸš€ Publish now'; pb.disabled=false; }
}
if(name==='github'){
var gf=$('gh-form'),gd=$('gh-done'),gb=$('gh-btn');
if(gf) gf.style.display='block'; if(gd) gd.style.display='none';
if(gb){ gb.textContent='Push to GitHub β†’'; gb.disabled=false; }
}
}
function switchAuth(mode){
S.authMode=mode;
var si=$('at-si'), su=$('at-su'), nw=$('name-wrap'), sub=$('a-sub'), sw=$('a-sw-area');
if(si) si.className='tab-sw-b'+(mode==='signin'?' on':'');
if(su) su.className='tab-sw-b'+(mode==='signup'?' on':'');
if(nw) nw.style.display=mode==='signup'?'block':'none';
if(sub) sub.textContent=mode==='signin'?'Sign in β†’':'Create account β†’';
if(sw) sw.innerHTML=mode==='signin'
?"Don't have an account? <span onclick=\"switchAuth('signup')\" style='color:#818cf8;cursor:pointer;font-weight:600'>Sign up free</span>"
:"Already have an account? <span onclick=\"switchAuth('signin')\" style='color:#818cf8;cursor:pointer;font-weight:600'>Sign in</span>";
var ae=$('auth-err'), ao=$('auth-ok');
if(ae) ae.style.display='none'; if(ao) ao.style.display='none';
}
function showAuthErr(msg){
var el=$('auth-err');
if(el){ el.textContent='⚠️ '+msg; el.style.display='block'; }
}
/* ── PUBLISH ── */
async function doPublish(){
if(!S.code){ alert('Build a website first.'); return; }
var slug=slugify(S.projectName), btn=$('pub-go-btn');
if(btn){ btn.textContent='Publishing...'; btn.disabled=true; }
try{
var r=await fetch(API+'/api/publish',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project_id:slug,html_code:S.code,subdomain:slug})});
var d=await r.json();
var pm=$('pub-main'), pd=$('pub-done');
if(pm) pm.style.display='none'; if(pd) pd.style.display='block';
var url=d.url||'https://'+slug+'.buildai.app';
var lu=$('pub-live-url'), ob=$('pub-open-btn');
if(lu) lu.textContent=url;
if(ob) ob.onclick=function(){window.open(url,'_blank');};
var pb=$('pub-bar'), pdt=$('pub-dt');
if(pb) pb.style.display='flex'; if(pdt) pdt.textContent=new Date().toLocaleTimeString();
}catch(e){
if(btn){ btn.textContent='πŸš€ Publish now'; btn.disabled=false; }
alert('Publish failed: '+e.message);
}
}
/* ── GITHUB ── */
async function doGithub(){
var token=$('gh-token').value.trim(), repo=$('gh-repo').value.trim();
if(!token||!repo){ alert('Enter GitHub token and repo name.'); return; }
if(!S.code){ alert('Build a website first.'); return; }
var btn=$('gh-btn');
if(btn){ btn.textContent='Pushing...'; btn.disabled=true; }
try{
var r=await fetch(API+'/api/github/sync',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project_id:'demo',html_code:S.code,repo_name:repo,github_token:token})});
var d=await r.json();
if(d.success){
var gf=$('gh-form'),gd=$('gh-done'),gu=$('gh-url-out');
if(gf) gf.style.display='none'; if(gd) gd.style.display='block'; if(gu) gu.textContent=d.repo_url;
}else{
alert('Failed: '+(d.detail||'Unknown'));
if(btn){ btn.textContent='Push to GitHub β†’'; btn.disabled=false; }
}
}catch(e){
alert('Error: '+e.message);
if(btn){ btn.textContent='Push to GitHub β†’'; btn.disabled=false; }
}
}
/* ── AUTH ── */
function guestMode(){
S.user={id:'guest-'+Date.now(),email:'guest',name:'Guest',isGuest:true};
var sn=$('sb-nm'),sp=$('sb-plan'),sa=$('sb-av'),su=$('sb-user-box'),tu=$('tb-user');
if(sn) sn.textContent='Guest mode';
if(sp) sp.textContent='Tap to sign in';
if(sa) sa.textContent='?';
if(su) su.onclick=function(){openM('auth','signin');};
if(tu) tu.innerHTML='<button class="tb-guest" onclick="openM(\'auth\',\'signin\')">πŸ‘€ Sign in</button>';
var sap=$('sb-auth-prompt'); if(sap) sap.style.display='block';
}
function doSignOut(){
if(typeof fbAuth!=='undefined'){ fbAuth.signOut().catch(function(e){console.error(e);}); }
else{ window.location.reload(); }
}
function toggleUD(){
var d=$('user-dd');
if(d) d.style.display=d.style.display==='none'?'block':'none';
}
function doGoogleAuth(){
if(typeof fbAuth==='undefined'){ alert('Auth not loaded. Please wait a moment.'); return; }
var provider=new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({prompt:'select_account'});
var isMobile=/Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if(isMobile){
fbAuth.signInWithRedirect(provider).catch(function(e){showAuthErr(e.message);});
}else{
fbAuth.signInWithPopup(provider).then(function(){closeM('auth');}).catch(function(e){
if(e.code==='auth/popup-closed-by-user') return;
if(e.code==='auth/unauthorized-domain'){
showAuthErr('Add your HuggingFace domain to Firebase Console β†’ Authentication β†’ Authorized domains.');
}else{
showAuthErr(e.message||'Google sign in failed.');
}
});
}
}
async function doAuth(){
if(typeof fbAuth==='undefined'){ alert('Auth not loaded. Please wait.'); return; }
var email=$('a-email').value.trim(), pass=$('a-pass').value;
var ae=$('auth-err'), ao=$('auth-ok');
if(ae) ae.style.display='none'; if(ao) ao.style.display='none';
if(!email||!pass){ showAuthErr('Enter email and password.'); return; }
if(pass.length<6){ showAuthErr('Password must be at least 6 characters.'); return; }
var btn=$('a-sub');
if(btn){ btn.disabled=true; btn.textContent='Please wait...'; }
try{
if(S.authMode==='signup'){ await fbAuth.createUserWithEmailAndPassword(email,pass); }
else{ await fbAuth.signInWithEmailAndPassword(email,pass); }
closeM('auth');
}catch(e){
var msgs={
'auth/email-already-in-use':'Account already exists. Sign in instead.',
'auth/user-not-found':'No account found with that email.',
'auth/invalid-credential':'Wrong email or password.',
'auth/wrong-password':'Wrong password. Try again.',
'auth/invalid-email':'Invalid email address.',
'auth/weak-password':'Password too weak. Use 6+ characters.',
'auth/too-many-requests':'Too many attempts. Try again later.'
};
showAuthErr(msgs[e.code]||e.message||'Something went wrong.');
}
if(btn){ btn.disabled=false; btn.textContent=S.authMode==='signin'?'Sign in β†’':'Create account β†’'; }
}
/* ── FIRESTORE PROJECT SAVING ── */
async function loadProjects(){
if(!S.user||S.user.isGuest||typeof fbDb==='undefined') return;
try{
var snap=await fbDb.collection('projects').where('user_id','==',S.user.id).orderBy('updated_at','desc').limit(20).get();
clearSBProjects();
if(!snap.empty){
var se=$('sb-empty'); if(se) se.style.display='none';
snap.forEach(function(doc){
var d=doc.data();
addProjItem(d.name,doc.id,false,d.html_code);
});
}
}catch(e){ console.warn('Load projects:',e.message); }
}
async function autoSave(){
if(!S.user||S.user.isGuest||!S.code||typeof fbDb==='undefined') return;
try{
var data={user_id:S.user.id,name:S.projectName,html_code:S.code,updated_at:firebase.firestore.FieldValue.serverTimestamp()};
if(S.projectId){
await fbDb.collection('projects').doc(S.projectId).update(data);
}else{
data.created_at=firebase.firestore.FieldValue.serverTimestamp();
var ref=await fbDb.collection('projects').add(data);
S.projectId=ref.id;
}
}catch(e){ console.warn('Save:',e.message); }
}
/* ── AUTH STATE CALLBACKS ── */
function onSignedIn(user){
S.user={id:user.uid,email:user.email,name:user.displayName||user.email.split('@')[0],isGuest:false};
var init=S.user.name[0].toUpperCase();
var tu=$('tb-user');
if(tu) tu.innerHTML=
'<div class="tb-av" onclick="toggleUD()" title="'+escH(S.user.email)+'">' +
init +
'<div class="user-dd" id="user-dd" style="display:none">' +
'<div class="ud-head">' +
'<div class="ud-name">'+escH(S.user.name)+'</div>' +
'<div class="ud-email">'+escH(S.user.email)+'</div>' +
'</div>' +
'<div class="ud-item" onclick="openM(\'github\')">πŸ™ GitHub sync</div>' +
'<div class="ud-sep"></div>' +
'<div class="ud-item red" onclick="doSignOut()">Sign out</div>' +
'</div></div>';
var sa=$('sb-av'),sn=$('sb-nm'),sp=$('sb-plan'),so=$('sb-signout'),su=$('sb-user-box'),sap=$('sb-auth-prompt');
if(sa) sa.textContent=init;
if(sn) sn.textContent=S.user.name;
if(sp) sp.textContent='Free plan';
if(so) so.style.display='block';
if(su) su.onclick=null;
if(sap) sap.style.display='none';
loadProjects();
}
function onSignedOut(){
S.user=null;
var tu=$('tb-user');
if(tu) tu.innerHTML='<button class="tb-signin" onclick="openM(\'auth\',\'signin\')">Sign in</button>';
var sa=$('sb-av'),sn=$('sb-nm'),sp=$('sb-plan'),so=$('sb-signout'),su=$('sb-user-box'),sap=$('sb-auth-prompt');
if(sa) sa.textContent='?';
if(sn) sn.textContent='Guest mode';
if(sp) sp.textContent='Tap to sign in';
if(so) so.style.display='none';
if(su) su.onclick=function(){openM('auth','signin');};
if(sap) sap.style.display='block';
clearSBProjects();
}
/* ── CLOSE DROPDOWNS ON OUTSIDE CLICK ── */
document.addEventListener('click',function(e){
var vp=$('ver-panel'), vb=$('ver-btn');
if(vp&&vp.style.display!=='none'&&!vp.contains(e.target)&&vb&&!vb.contains(e.target)) vp.style.display='none';
var dd=$('user-dd');
if(dd&&dd.style.display!=='none'&&!dd.parentElement.contains(e.target)) dd.style.display='none';
});
/* ── INIT ── */
(function initUI(){
S.tab='preview'; S.code=''; updTabs();
var pvBody=$('pv-body');
if(pvBody) pvBody.innerHTML='<div class="pv-empty"><div class="pv-e-ic">🌐</div><div class="pv-e-t">Preview will appear here</div></div>';
['pv-btm','pub-bar','sugg-bar'].forEach(function(id){var el=$(id);if(el)el.style.display='none';});
var pb=$('pub-tb'); if(pb) pb.disabled=true;
var tu=$('tb-url'); if(tu) tu.style.display='none';
[1,2,3].forEach(function(i){var d=$('rd'+i);if(d){d.className='r-dot';d.textContent=i;}});
setStat('Ready to build',0,false);
updSend();
})();
/* ── PROMPT FROM LANDING PAGE ── */
(function(){
try{
var saved=sessionStorage.getItem('bai_prompt');
if(saved){ sessionStorage.removeItem('bai_prompt'); setTimeout(function(){setBP(saved);},400); }
else{
var params=new URLSearchParams(window.location.search);
var qp=params.get('prompt');
if(qp) setTimeout(function(){setBP(qp);},400);
}
}catch(e){}
})();
</script>
<!-- ── FIREBASE ── -->
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-auth-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore-compat.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script>
try{
firebase.initializeApp({
apiKey:"AIzaSyDGtOYRxO2N6qPDNwkuZmwkhQelmJ9VQkQ",
authDomain:"buildai-b1d98.firebaseapp.com",
projectId:"buildai-b1d98",
storageBucket:"buildai-b1d98.firebasestorage.app",
messagingSenderId:"511431818463",
appId:"1:511431818463:web:846a142888fc3517353d6a"
});
var fbAuth=firebase.auth();
var fbDb=firebase.firestore();
fbAuth.onAuthStateChanged(function(user){if(user) onSignedIn(user); else onSignedOut();});
fbAuth.getRedirectResult().then(function(r){if(r&&r.user) closeM('auth');}).catch(function(e){
if(e.code&&e.code!=='auth/no-auth-event') console.warn('Redirect:',e.code);
});
}catch(e){
console.warn('Firebase failed:',e.message);
var tu=document.getElementById('tb-user');
if(tu) tu.innerHTML='<button class="tb-guest" onclick="guestMode()">Use as Guest</button>';
}
</script>
</body>
</html>