VISHAL18for4 commited on
Commit
9e8cf1e
Β·
verified Β·
1 Parent(s): 69f436a

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +17 -0
  2. ai_engine.py +993 -0
  3. builder.html +840 -0
  4. index.html +365 -0
  5. main.py +240 -0
  6. requirements.txt +6 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+ WORKDIR /home/user/app
3
+
4
+ COPY requirements.txt .
5
+ RUN pip install --no-cache-dir -r requirements.txt
6
+
7
+ COPY main.py .
8
+ COPY ai_engine.py .
9
+ COPY index.html .
10
+ COPY ref_landing.html .
11
+ COPY ref_ecommerce.html .
12
+ COPY ref_restaurant.html .
13
+ COPY ref_portfolio.html .
14
+ COPY ref_dashboard.html .
15
+
16
+ EXPOSE 7860
17
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
ai_engine.py ADDED
@@ -0,0 +1,993 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BuildAI - AI Engine v7 β€” ULTRA UPGRADE
3
+ New providers: Cerebras (ultra-fast), Gemini Flash (smart), DeepSeek (reasoning)
4
+ Pipeline: Cerebras β†’ Gemini β†’ Groq, with deep fallback chains
5
+ Higher quality output: v0/Lovable-level websites
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import asyncio
11
+ import httpx
12
+ from typing import AsyncGenerator
13
+
14
+
15
+ # ─────────────────────────────────────────────────────────────────
16
+ # REFERENCE FILE LOADER
17
+ # ─────────────────────────────────────────────────────────────────
18
+
19
+ _ref_cache = {}
20
+
21
+ def load_ref(filename: str) -> str:
22
+ global _ref_cache
23
+ if filename in _ref_cache:
24
+ return _ref_cache[filename]
25
+ paths = [filename, f"/home/user/app/{filename}", f"./{filename}"]
26
+ for path in paths:
27
+ try:
28
+ with open(path, "r", encoding="utf-8") as f:
29
+ content = f.read()
30
+ _ref_cache[filename] = content
31
+ print(f"[BuildAI] βœ“ Loaded {filename} ({len(content)} chars)")
32
+ return content
33
+ except FileNotFoundError:
34
+ continue
35
+ print(f"[BuildAI] ⚠ Not found: {filename}")
36
+ return ""
37
+
38
+
39
+ def get_references(prompt: str) -> tuple[str, str]:
40
+ p = prompt.lower()
41
+ if any(k in p for k in ['ecommerce','e-commerce','store','shop','product','cart','buy','sell','marketplace','clothing','fashion','sneaker','brand','retail']):
42
+ return load_ref("ref_ecommerce.html"), "E-COMMERCE"
43
+ elif any(k in p for k in ['restaurant','cafe','coffee','food','menu','kitchen','chef','dining','pizza','burger','sushi','bakery','bistro','bar']):
44
+ return load_ref("ref_restaurant.html"), "RESTAURANT"
45
+ elif any(k in p for k in ['portfolio','designer','developer','cv','resume','case study','my work','showcase','freelance','photographer','creative']):
46
+ return load_ref("ref_portfolio.html"), "PORTFOLIO"
47
+ elif any(k in p for k in ['dashboard','admin','analytics','crm','chart','kpi','metric','report','data','fintech','crypto','saas app']):
48
+ return load_ref("ref_dashboard.html"), "DASHBOARD"
49
+ else:
50
+ return load_ref("ref_landing.html"), "SAAS LANDING PAGE"
51
+
52
+
53
+ # ─────────────────────────────────────────────────────────────────
54
+ # SMART IMAGE KEYWORD EXTRACTOR
55
+ # ─────────────────────────────────────────────────────────────────
56
+
57
+ IMAGE_KEYWORDS = {
58
+ "cafe": ["coffee,cafe,cup", "barista,coffee,making", "cafe,interior,cozy", "coffee,latte,art"],
59
+ "coffee": ["coffee,espresso,cup", "coffee,beans,roasted", "barista,coffee,brewing", "coffee,cafe,table"],
60
+ "restaurant": ["restaurant,food,plating", "restaurant,interior,dining", "chef,cooking,kitchen", "food,gourmet,dish"],
61
+ "pizza": ["pizza,italian,fresh", "pizza,oven,baking", "pizza,toppings,cheese", "pizzeria,italian,food"],
62
+ "burger": ["burger,beef,fresh", "hamburger,restaurant,juicy", "burger,fries,meal", "fast,food,burger"],
63
+ "sushi": ["sushi,japanese,fresh", "sushi,roll,seafood", "sushi,chef,making", "japanese,food,restaurant"],
64
+ "bakery": ["bakery,bread,fresh", "pastry,croissant,baked", "cake,decoration,bakery", "bread,oven,baking"],
65
+ "tea": ["tea,cup,hot", "tea,leaves,herbal", "teapot,ceramic,drink", "tea,ceremony,japan"],
66
+ "bar": ["bar,cocktail,drinks", "bartender,mixing,cocktail", "whiskey,glass,bar", "nightlife,bar,drinks"],
67
+ "fashion": ["fashion,clothing,style", "model,outfit,trendy", "clothes,boutique,store", "fashion,designer,wear"],
68
+ "sneaker": ["sneakers,shoes,white", "athletic,shoes,sport", "sneaker,collection,display", "shoes,fashion,urban"],
69
+ "clothing": ["clothing,fashion,store", "outfit,stylish,model", "wardrobe,clothes,fashion", "apparel,shopping,retail"],
70
+ "jewelry": ["jewelry,gold,elegant", "necklace,diamond,luxury", "ring,jewelry,sparkle", "bracelet,fashion,accessories"],
71
+ "saas": ["technology,software,laptop", "office,team,collaboration", "startup,tech,modern", "dashboard,analytics,screen"],
72
+ "startup": ["startup,office,modern", "team,meeting,business", "entrepreneur,laptop,coffee", "tech,office,workspace"],
73
+ "agency": ["office,creative,team", "agency,design,creative", "meeting,business,professional", "workspace,modern,office"],
74
+ "tech": ["technology,innovation,laptop", "coding,developer,screen", "tech,startup,modern", "software,computer,code"],
75
+ "gym": ["gym,workout,fitness", "exercise,weights,strong", "fitness,training,athlete", "gym,equipment,sport"],
76
+ "yoga": ["yoga,meditation,peace", "yoga,pose,wellness", "mindfulness,yoga,calm", "yoga,studio,class"],
77
+ "spa": ["spa,relaxation,wellness", "massage,therapy,calm", "beauty,spa,treatment", "wellness,retreat,peaceful"],
78
+ "medical": ["doctor,medical,hospital", "healthcare,professional,clinic", "medical,team,care", "hospital,health,medicine"],
79
+ "real estate": ["house,modern,architecture", "interior,luxury,home", "property,real,estate", "living,room,design"],
80
+ "interior": ["interior,design,modern", "furniture,home,decor", "living,space,elegant", "home,decoration,style"],
81
+ "education": ["education,students,learning", "classroom,school,teaching", "university,campus,study", "books,learning,knowledge"],
82
+ "course": ["online,learning,laptop", "education,course,digital", "student,studying,desk", "elearning,digital,course"],
83
+ "travel": ["travel,destination,adventure", "landscape,beautiful,nature", "tourism,city,explore", "travel,photography,world"],
84
+ "hotel": ["hotel,luxury,room", "resort,pool,vacation", "hotel,lobby,elegant", "travel,accommodation,comfort"],
85
+ "portfolio": ["designer,creative,workspace", "creative,studio,modern", "photographer,camera,art", "design,portfolio,work"],
86
+ "photography": ["camera,photography,lens", "photo,shoot,studio", "photographer,nature,outdoor", "portrait,photography,light"],
87
+ "music": ["music,studio,recording", "guitar,musician,performance", "concert,music,stage", "headphones,music,listening"],
88
+ "gaming": ["gaming,computer,setup", "game,controller,neon", "esports,gaming,tournament", "game,developer,screen"],
89
+ "finance": ["finance,investment,chart", "money,banking,professional", "stock,market,trading", "wealth,management,business"],
90
+ "law": ["law,office,professional", "lawyer,justice,court", "legal,business,meeting", "attorney,desk,documents"],
91
+ "default_hero": ["modern,business,professional", "office,team,success", "technology,innovation,future", "startup,growth,success"],
92
+ "default_product": ["product,design,modern", "item,display,showcase", "product,photography,clean", "goods,retail,store"],
93
+ "default_person": ["professional,portrait,business", "person,team,corporate", "headshot,professional,smile", "team,member,office"],
94
+ }
95
+
96
+ def extract_image_keywords(prompt: str, website_type: str) -> dict:
97
+ p = prompt.lower()
98
+ keywords = {"hero": None, "section": None, "card": None, "person": None}
99
+ for topic, kw_list in IMAGE_KEYWORDS.items():
100
+ if topic in p:
101
+ if not keywords["hero"]:
102
+ keywords["hero"] = kw_list[0]
103
+ if not keywords["section"] and len(kw_list) > 1:
104
+ keywords["section"] = kw_list[1]
105
+ if not keywords["card"] and len(kw_list) > 2:
106
+ keywords["card"] = kw_list[2]
107
+ break
108
+ if not keywords["hero"]:
109
+ type_defaults = {
110
+ "E-COMMERCE": "product,fashion,store",
111
+ "RESTAURANT": "restaurant,food,dining",
112
+ "PORTFOLIO": "creative,designer,workspace",
113
+ "DASHBOARD": "office,technology,business",
114
+ "SAAS LANDING PAGE": "technology,startup,modern",
115
+ }
116
+ keywords["hero"] = type_defaults.get(website_type, "business,modern,professional")
117
+ if not keywords["section"]:
118
+ keywords["section"] = keywords["hero"]
119
+ if not keywords["card"]:
120
+ keywords["card"] = keywords["hero"]
121
+ keywords["person"] = "professional,portrait,person"
122
+ return keywords
123
+
124
+
125
+ def get_photo_instructions(prompt: str, website_type: str) -> str:
126
+ kw = extract_image_keywords(prompt, website_type)
127
+ return f"""
128
+ ━━━ REAL PHOTOS β€” NON-NEGOTIABLE ━━━
129
+ Use loremflickr.com for ALL images. Format: https://loremflickr.com/WIDTH/HEIGHT/keyword1,keyword2
130
+
131
+ IMPORTANT: Use EXACTLY these pre-matched keywords:
132
+
133
+ Hero background:
134
+ style={{{{backgroundImage:"url('https://loremflickr.com/1600/900/{kw["hero"]}')"}}}}
135
+ className="bg-cover bg-center bg-no-repeat"
136
+
137
+ Section/feature image:
138
+ <img src="https://loremflickr.com/800/500/{kw["section"]}" className="w-full h-64 object-cover rounded-2xl" loading="lazy" decoding="async" alt="..." />
139
+
140
+ Card/product image:
141
+ <img src="https://loremflickr.com/600/500/{kw["card"]}" className="w-full h-52 object-cover" loading="lazy" decoding="async" alt="..." />
142
+
143
+ Person/team avatar:
144
+ <img src="https://loremflickr.com/200/200/{kw["person"]}" className="w-16 h-16 rounded-full object-cover" loading="lazy" decoding="async" alt="..." />
145
+
146
+ STRICT RULES:
147
+ - 6 to 8 images MINIMUM in the site
148
+ - Add ?lock=1, ?lock=2 etc. for variety
149
+ - Hero MUST have real background photo
150
+ - NEVER use placeholder.com β€” gray boxes kill design
151
+ """
152
+
153
+
154
+ # ─────────────────────────────────────────────────────────────────
155
+ # CDN STACK
156
+ # ─────────────────────────────────────────────────────────────────
157
+
158
+ CDN_STACK = """
159
+ ━━━ TECHNOLOGY STACK ━━━
160
+
161
+ PUT IN <head>:
162
+ <script src="https://cdn.tailwindcss.com"></script>
163
+ <script>
164
+ tailwind.config = {
165
+ theme: {
166
+ extend: {
167
+ fontFamily: {
168
+ display: ['Syne', 'system-ui', 'sans-serif'],
169
+ sans: ['DM Sans', 'system-ui', 'sans-serif'],
170
+ mono: ['JetBrains Mono', 'monospace'],
171
+ },
172
+ colors: {
173
+ brand: {
174
+ 50: 'var(--brand-50)',
175
+ 500: 'var(--brand-500)',
176
+ 600: 'var(--brand-600)',
177
+ 700: 'var(--brand-700)',
178
+ }
179
+ },
180
+ animation: {
181
+ 'fade-up': 'fadeUp 0.7s ease forwards',
182
+ 'fade-in': 'fadeIn 0.5s ease forwards',
183
+ 'float': 'float 4s ease-in-out infinite',
184
+ 'pulse-slow': 'pulse 4s ease-in-out infinite',
185
+ 'gradient': 'gradientFlow 6s ease infinite',
186
+ 'slide-in': 'slideIn 0.5s ease forwards',
187
+ 'shimmer': 'shimmer 2s infinite',
188
+ 'spin-slow': 'spin 8s linear infinite',
189
+ 'bounce-slow': 'bounce 3s infinite',
190
+ },
191
+ keyframes: {
192
+ fadeUp: { '0%': { opacity: 0, transform: 'translateY(32px)' }, '100%': { opacity: 1, transform: 'translateY(0)' } },
193
+ fadeIn: { '0%': { opacity: 0 }, '100%': { opacity: 1 } },
194
+ float: { '0%,100%': { transform: 'translateY(0)' }, '50%': { transform: 'translateY(-16px)' } },
195
+ gradientFlow: { '0%,100%': { backgroundPosition: '0% 50%' }, '50%': { backgroundPosition: '100% 50%' } },
196
+ slideIn: { '0%': { opacity: 0, transform: 'translateX(-20px)' }, '100%': { opacity: 1, transform: 'translateX(0)' } },
197
+ shimmer: { '0%': { backgroundPosition: '-200% center' }, '100%': { backgroundPosition: '200% center' } },
198
+ },
199
+ backgroundSize: { '200%': '200%', '300%': '300%', '400%': '400%' },
200
+ }
201
+ }
202
+ }
203
+ </script>
204
+ <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=DM+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
205
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
206
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
207
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
208
+
209
+ PUT IN <body>: <div id="root"></div>
210
+
211
+ PUT AT END OF <body>:
212
+ <script type="text/babel">
213
+ const { useState, useEffect, useRef, useCallback } = React;
214
+ // ALL YOUR COMPONENTS HERE
215
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
216
+ </script>
217
+
218
+ ━━━ REACT RULES ━━━
219
+ - Destructure hooks: const { useState, useEffect, useRef, useCallback } = React;
220
+ - Arrow function components: const Navbar = () => { ... }
221
+ - className not class; htmlFor not for
222
+ - Inline styles: style={{backgroundColor: '#000'}}
223
+ - Events: onClick, onChange, onMouseEnter, onMouseLeave
224
+ - Always add loading="lazy" decoding="async" to images
225
+ - Mobile menu: useState(false) for isOpen
226
+ """
227
+
228
+
229
+ # ─────────────────────────────────────────────────────────────────
230
+ # ELITE DESIGN SYSTEM β€” v7 UPGRADE
231
+ # ─────────────────────────────────────────────────────────────────
232
+
233
+ DESIGN_BIBLE = """
234
+ ━━━ ELITE DESIGN SYSTEM v7 ━━━
235
+
236
+ CSS CUSTOM PROPERTIES (add to <style> in <head>):
237
+ :root {
238
+ --brand-50: #eef2ff;
239
+ --brand-500: #6366f1;
240
+ --brand-600: #4f46e5;
241
+ --brand-700: #4338ca;
242
+ --surface: #0a0b14;
243
+ --surface-2: #0d0e1a;
244
+ --surface-3: #111327;
245
+ --border: rgba(255,255,255,0.07);
246
+ --border-brand: rgba(99,102,241,0.3);
247
+ --text-primary: #f1f5f9;
248
+ --text-secondary: #94a3b8;
249
+ --text-muted: #475569;
250
+ }
251
+
252
+ /* Noise texture for depth */
253
+ .noise::after {
254
+ content: '';
255
+ position: absolute;
256
+ inset: 0;
257
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
258
+ pointer-events: none;
259
+ border-radius: inherit;
260
+ }
261
+
262
+ /* Glow effects */
263
+ .glow-brand { box-shadow: 0 0 40px rgba(99,102,241,0.15), 0 0 80px rgba(99,102,241,0.08); }
264
+ .glow-on-hover:hover { box-shadow: 0 0 30px rgba(99,102,241,0.25); transition: box-shadow 0.3s; }
265
+
266
+ TYPOGRAPHY SCALE:
267
+ - Hero display: font-display text-5xl sm:text-7xl lg:text-[96px] font-bold tracking-[-3px] leading-[0.95] text-white
268
+ - Section title: font-display text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight
269
+ - Card title: font-sans text-xl font-semibold text-white
270
+ - Body large: font-sans text-lg text-gray-400 leading-relaxed
271
+ - Body: font-sans text-base text-gray-500 leading-relaxed
272
+ - Caption: font-sans text-sm text-gray-600
273
+ - Eyebrow: font-mono text-xs font-bold tracking-[0.25em] uppercase text-indigo-400
274
+
275
+ GRADIENT TEXT OPTIONS:
276
+ /* Option A β€” Cool electric */
277
+ className="bg-gradient-to-r from-indigo-400 via-violet-300 to-cyan-400 bg-clip-text text-transparent bg-[size:200%] animate-gradient"
278
+ /* Option B β€” Warm premium */
279
+ className="bg-gradient-to-r from-orange-400 via-pink-400 to-violet-400 bg-clip-text text-transparent bg-[size:200%] animate-gradient"
280
+ /* Option C β€” Sharp neon */
281
+ className="bg-gradient-to-r from-cyan-300 via-blue-400 to-indigo-500 bg-clip-text text-transparent bg-[size:200%] animate-gradient"
282
+
283
+ BUTTONS:
284
+ Primary CTA:
285
+ className="group relative inline-flex items-center gap-2 px-8 py-4 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold rounded-2xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_20px_40px_rgba(99,102,241,0.4)] active:scale-95 overflow-hidden"
286
+ /* Add shimmer: <span className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent translate-x-[-200%] group-hover:translate-x-[200%] transition-transform duration-700" /> */
287
+
288
+ Secondary:
289
+ className="px-8 py-4 border border-white/10 text-white font-semibold rounded-2xl hover:bg-white/5 hover:border-white/20 transition-all duration-200 backdrop-blur-sm"
290
+
291
+ Ghost:
292
+ className="inline-flex items-center gap-1 text-gray-400 hover:text-white font-medium transition-colors group"
293
+ /* Arrow: <span className="group-hover:translate-x-1 transition-transform">β†’</span> */
294
+
295
+ CARDS (choose based on context):
296
+ Glassmorphic:
297
+ className="relative bg-white/[0.03] backdrop-blur-sm border border-white/[0.07] rounded-3xl p-6 hover:-translate-y-1 hover:border-indigo-500/20 hover:bg-white/[0.05] transition-all duration-300 group overflow-hidden"
298
+
299
+ Bento cell (for bento grid layouts):
300
+ className="bg-[#0d0e1a] border border-white/[0.06] rounded-2xl p-5 hover:border-indigo-500/20 transition-colors group"
301
+
302
+ Feature card with glow:
303
+ className="relative p-6 rounded-2xl bg-gradient-to-br from-white/[0.04] to-white/[0.01] border border-white/[0.07] hover:border-indigo-500/30 transition-all duration-300 group"
304
+
305
+ BENTO GRID LAYOUTS (use for features):
306
+ <div className="grid grid-cols-6 grid-rows-auto gap-4">
307
+ <div className="col-span-6 md:col-span-4 row-span-2 ..."> {/* Wide card */}
308
+ <div className="col-span-6 md:col-span-2 ..."> {/* Tall card */}
309
+ <div className="col-span-6 md:col-span-2 ..."> {/* Square card */}
310
+ <div className="col-span-6 md:col-span-3 ..."> {/* Half card */}
311
+ <div className="col-span-6 md:col-span-3 ..."> {/* Half card */}
312
+ </div>
313
+
314
+ NAVBAR (always sticky):
315
+ className="fixed top-0 left-0 right-0 z-50 px-6 py-4 flex items-center justify-between backdrop-blur-2xl bg-black/50 border-b border-white/[0.06]"
316
+ /* Logo: font-display font-bold text-lg tracking-tight */
317
+
318
+ HERO PATTERNS:
319
+ Pattern A β€” Full viewport with mesh bg:
320
+ <section className="relative min-h-screen flex items-center justify-center text-center px-6 pt-20 overflow-hidden">
321
+ {/* Mesh gradient background */}
322
+ <div className="absolute inset-0 bg-[#07080f]">
323
+ <div className="absolute inset-0" style={{background: 'radial-gradient(ellipse 80% 50% at 50% -20%, rgba(99,102,241,0.3) 0%, transparent 60%)'}} />
324
+ <div className="absolute top-1/3 left-1/4 w-[600px] h-[600px] bg-violet-600/10 rounded-full blur-[120px]" />
325
+ <div className="absolute bottom-1/4 right-1/4 w-[400px] h-[400px] bg-indigo-600/10 rounded-full blur-[100px]" />
326
+ {/* Grid overlay */}
327
+ <div className="absolute inset-0" style={{backgroundImage: 'linear-gradient(rgba(99,102,241,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(99,102,241,0.03) 1px, transparent 1px)', backgroundSize: '60px 60px'}} />
328
+ </div>
329
+ <div className="relative z-10 max-w-5xl mx-auto">...</div>
330
+ </section>
331
+
332
+ Pattern B β€” Split hero:
333
+ <section className="min-h-screen grid grid-cols-1 lg:grid-cols-2 gap-0">
334
+ <div className="flex items-center px-8 lg:px-16 py-24 bg-[#07080f]">{/* Text */}</div>
335
+ <div className="relative overflow-hidden bg-indigo-950">{/* Visual */}</div>
336
+ </section>
337
+
338
+ SCROLL REVEAL (add to ALL sections):
339
+ const useReveal = (threshold = 0.1) => {
340
+ const ref = useRef(null);
341
+ useEffect(() => {
342
+ const el = ref.current;
343
+ if (!el) return;
344
+ const obs = new IntersectionObserver(([e]) => {
345
+ if (e.isIntersecting) {
346
+ e.target.style.opacity = '1';
347
+ e.target.style.transform = 'translateY(0)';
348
+ obs.unobserve(e.target);
349
+ }
350
+ }, { threshold });
351
+ el.style.opacity = '0';
352
+ el.style.transform = 'translateY(40px)';
353
+ el.style.transition = 'opacity 0.7s ease, transform 0.7s ease';
354
+ obs.observe(el);
355
+ return () => obs.disconnect();
356
+ }, []);
357
+ return ref;
358
+ };
359
+ /* Stagger children: style={{transitionDelay: `${i * 0.1}s`}} */
360
+
361
+ SECTIONS SPACING:
362
+ <section className="py-28 lg:py-36 px-6">
363
+ <div className="max-w-6xl mx-auto">...</div>
364
+ </section>
365
+
366
+ SECTION HEADER PATTERN:
367
+ <div className="text-center mb-16 lg:mb-20">
368
+ <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-300 font-mono text-xs tracking-widest uppercase mb-6">
369
+ ✦ Section Label
370
+ </div>
371
+ <h2 className="font-display text-4xl lg:text-5xl font-bold text-white tracking-tight mb-4">Section Title</h2>
372
+ <p className="text-gray-400 text-lg max-w-2xl mx-auto leading-relaxed">Subtitle text here</p>
373
+ </div>
374
+
375
+ ICON BOXES:
376
+ className="w-12 h-12 rounded-2xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-2xl mb-5 group-hover:scale-110 group-hover:bg-indigo-500/20 transition-all duration-300"
377
+
378
+ STATS ROW:
379
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-8 py-12 border-y border-white/[0.06]">
380
+ {[['$2.4B', 'Revenue Generated'], ['50K+', 'Customers'], ['99.9%', 'Uptime SLA'], ['4.9β˜…', 'Average Rating']].map(([n, l]) => (
381
+ <div key={l} className="text-center group">
382
+ <div className="font-display text-4xl font-bold text-white mb-1 group-hover:text-indigo-300 transition-colors">{n}</div>
383
+ <div className="font-sans text-sm text-gray-500">{l}</div>
384
+ </div>
385
+ ))}
386
+ </div>
387
+
388
+ TESTIMONIAL CARDS:
389
+ <div className="bg-white/[0.03] border border-white/[0.07] rounded-2xl p-6 hover:border-indigo-500/20 transition-colors">
390
+ <div className="flex gap-0.5 mb-4">
391
+ {[...Array(5)].map((_, i) => <span key={i} className="text-amber-400 text-sm">β˜…</span>)}
392
+ </div>
393
+ <p className="text-gray-300 leading-relaxed mb-6 italic">"Quote here..."</p>
394
+ <div className="flex items-center gap-3">
395
+ <img src="..." className="w-10 h-10 rounded-full object-cover" />
396
+ <div>
397
+ <div className="font-semibold text-white text-sm">Name</div>
398
+ <div className="text-gray-500 text-xs">Role, Company</div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+
403
+ FOOTER:
404
+ <footer className="bg-[#050608] border-t border-white/[0.05] pt-16 pb-8 px-6">
405
+ <div className="max-w-6xl mx-auto">
406
+ <div className="grid grid-cols-2 md:grid-cols-5 gap-8 mb-12">
407
+ <div className="col-span-2">{/* Brand + tagline */}</div>
408
+ {/* 3 link columns */}
409
+ </div>
410
+ <div className="border-t border-white/[0.05] pt-6 flex flex-col sm:flex-row justify-between items-center gap-4">
411
+ <p className="text-gray-600 text-sm">Β© 2025 Brand. All rights reserved.</p>
412
+ {/* Social icons */}
413
+ </div>
414
+ </div>
415
+ </footer>
416
+
417
+ MOBILE HAMBURGER:
418
+ const [menuOpen, setMenuOpen] = useState(false);
419
+ <button onClick={() => setMenuOpen(!menuOpen)} className="md:hidden w-9 h-9 flex flex-col justify-center items-center gap-1.5 group" aria-label="Menu">
420
+ <span className={`w-5 h-0.5 bg-gray-400 transition-all duration-200 ${menuOpen ? 'rotate-45 translate-y-2' : ''}`}/>
421
+ <span className={`w-5 h-0.5 bg-gray-400 transition-all duration-200 ${menuOpen ? 'opacity-0' : ''}`}/>
422
+ <span className={`w-5 h-0.5 bg-gray-400 transition-all duration-200 ${menuOpen ? '-rotate-45 -translate-y-2' : ''}`}/>
423
+ </button>
424
+ {menuOpen && (
425
+ <div className="md:hidden absolute top-full left-0 right-0 bg-black/95 backdrop-blur-2xl border-b border-white/[0.06] py-4 px-6 flex flex-col gap-3 animate-fade-in">
426
+ {/* nav links */}
427
+ </div>
428
+ )}
429
+
430
+ PRICING CARDS:
431
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto">
432
+ {/* Free */}
433
+ <div className="bg-white/[0.03] border border-white/[0.07] rounded-3xl p-8">...</div>
434
+ {/* Pro β€” highlighted */}
435
+ <div className="relative bg-indigo-600 rounded-3xl p-8 scale-105 shadow-[0_0_80px_rgba(99,102,241,0.3)]">
436
+ <div className="absolute -top-3 left-1/2 -translate-x-1/2 px-4 py-1 bg-amber-400 text-black text-xs font-bold rounded-full">Most Popular</div>
437
+ ...
438
+ </div>
439
+ {/* Enterprise */}
440
+ <div className="bg-white/[0.03] border border-white/[0.07] rounded-3xl p-8">...</div>
441
+ </div>
442
+
443
+ FAQ ACCORDION:
444
+ const [openFaq, setOpenFaq] = useState(null);
445
+ <div className="max-w-2xl mx-auto divide-y divide-white/[0.06]">
446
+ {faqs.map((faq, i) => (
447
+ <div key={i} className="py-5 cursor-pointer group" onClick={() => setOpenFaq(openFaq === i ? null : i)}>
448
+ <div className="flex justify-between items-center">
449
+ <span className="font-semibold text-white group-hover:text-indigo-300 transition-colors">{faq.q}</span>
450
+ <span className={`text-indigo-400 transition-transform duration-200 ${openFaq === i ? 'rotate-45' : ''}`}>+</span>
451
+ </div>
452
+ {openFaq === i && <p className="mt-3 text-gray-400 text-sm leading-relaxed animate-fade-in">{faq.a}</p>}
453
+ </div>
454
+ ))}
455
+ </div>
456
+
457
+ ANNOUNCEMENT BAR (for ecommerce):
458
+ <div className="bg-indigo-600 text-white text-center py-2.5 px-4 text-sm font-medium">
459
+ πŸŽ‰ Free shipping on orders over $75 β€” Use code <strong>BUILDAI</strong>
460
+ <button className="ml-4 text-white/60 hover:text-white" onClick={() => ...}>Γ—</button>
461
+ </div>
462
+
463
+ SCROLL TO TOP BUTTON:
464
+ const [showTop, setShowTop] = useState(false);
465
+ useEffect(() => {
466
+ const handleScroll = () => setShowTop(window.scrollY > 400);
467
+ window.addEventListener('scroll', handleScroll);
468
+ return () => window.removeEventListener('scroll', handleScroll);
469
+ }, []);
470
+ {showTop && (
471
+ <button onClick={() => window.scrollTo({top:0,behavior:'smooth'})}
472
+ className="fixed bottom-6 right-6 z-50 w-10 h-10 bg-indigo-600 hover:bg-indigo-500 text-white rounded-full shadow-lg hover:shadow-indigo-500/30 transition-all duration-200 hover:-translate-y-0.5 flex items-center justify-center text-sm animate-fade-in">
473
+ ↑
474
+ </button>
475
+ )}
476
+ """
477
+
478
+
479
+ # ─────────────────────────────────────────────────────────────────
480
+ # MANDATORY SECTIONS PER TYPE
481
+ # ─────────────────────────────────────────────────────────────────
482
+
483
+ SECTIONS_MAP = {
484
+ "SAAS LANDING PAGE": """
485
+ MANDATORY SECTIONS IN THIS ORDER:
486
+ 1. Navbar β€” sticky glassmorphic, logo (font-display), nav links, "Start free" CTA + mobile hamburger
487
+ 2. Hero β€” full viewport mesh-gradient bg, eyebrow pill ("✦ Now powered by AI"), giant display headline with animated gradient text, 2-line subtext, 2 CTA buttons (primary + secondary), floating product screenshot with glow border + floating stat badges
488
+ 3. Social proof β€” "Trusted by 10,000+ teams" + 6 real company name logos in muted grayscale
489
+ 4. Features bento β€” "Built different" heading, BENTO GRID layout with 6 feature cells (1 large + 5 standard), each with icon box, title, 2-line description
490
+ 5. How it works β€” "Simple as 1, 2, 3", 3 numbered steps with connecting line, icon + bold title + description + screenshot
491
+ 6. Stats β€” 4 big numbers in dark row (users, revenue, countries, rating)
492
+ 7. Testimonials β€” "Loved by builders worldwide", 3 card grid with stars + quote + avatar + name + company
493
+ 8. Pricing β€” 3 tiers, middle card highlighted indigo with scale-105 + "Most Popular" badge, full feature list with checkmarks
494
+ 9. FAQ β€” 6 questions with smooth accordion, max-w-2xl
495
+ 10. Final CTA β€” dark gradient section, huge headline, email capture, "No credit card" note
496
+ 11. Footer β€” 4-col links, social icons, copyright
497
+ """,
498
+ "E-COMMERCE": """
499
+ MANDATORY SECTIONS IN THIS ORDER:
500
+ 1. Announcement bar β€” promo text + dismiss button, indigo bg
501
+ 2. Navbar β€” logo, search bar, cart icon (badge count), account, hamburger
502
+ 3. Hero banner β€” full-bleed image bg + dark overlay, large headline, 2 CTAs (Shop Now / View Lookbook)
503
+ 4. Categories β€” "Shop by Category", 4–6 category cards each with image + label overlay + hover zoom
504
+ 5. Featured products β€” "Best Sellers", 4-col product grid; each card: image (hover zoom), wishlist heart, sale badge, brand name, product name, price + strikethrough, star rating, Add to Cart button
505
+ 6. Flash sale β€” countdown timer (useEffect setInterval hours:mins:secs), 4 discounted products
506
+ 7. Value props β€” 4 icons row (Free Shipping / Easy Returns / Secure Pay / 24/7 Support)
507
+ 8. Brand story β€” split: image left + text right with "Our Story" + signature
508
+ 9. Reviews β€” 3 customer cards with product photo, stars, quote, reviewer name + date
509
+ 10. Newsletter β€” "Get 15% off", email input + submit
510
+ 11. Footer β€” links, payment icons, social, copyright
511
+ """,
512
+ "RESTAURANT": """
513
+ MANDATORY SECTIONS IN THIS ORDER:
514
+ 1. Navbar β€” logo, nav links, "Reserve a Table" warm CTA, phone number
515
+ 2. Hero β€” full-screen image bg + dark overlay, restaurant name + tagline, 2 CTAs (View Menu / Book Table), floating badge ("Michelin Recommended")
516
+ 3. Story β€” split: chef photo left + story text right, signature, award badges
517
+ 4. Menu β€” "Our Menu", tab switcher (Starters/Mains/Desserts/Drinks with useState), menu items in grid: image + name + description + price
518
+ 5. Gallery β€” "From Our Kitchen", masonry-style 3-col grid with varied heights, food + ambiance photos with hover overlay
519
+ 6. Reservation form β€” date picker, time select, party size, name, email, notes, CTA button
520
+ 7. Chef special β€” featured dish card: large image + ingredient tags + story text
521
+ 8. Reviews β€” 3 Google-style cards: stars + quote + reviewer + occasion
522
+ 9. Location & Hours β€” address + opening hours table (day + time) + map placeholder
523
+ 10. Footer β€” social, newsletter, copyright
524
+ """,
525
+ "PORTFOLIO": """
526
+ MANDATORY SECTIONS IN THIS ORDER:
527
+ 1. Navbar β€” name/monogram, nav (Work/About/Skills/Contact), "Hire Me" CTA
528
+ 2. Hero β€” split or centered; name + role with typewriter effect (useEffect + setInterval), tagline, available indicator (green pulse dot + "Open to work"), 2 CTAs + social links
529
+ 3. Skills β€” horizontal scrollable chips, each with emoji/icon + skill name, show 15-20 skills
530
+ 4. Work β€” "Selected Work", 2-col grid; each project: image with hover overlay showing title + tags + visit links
531
+ 5. About β€” photo + bio + experience timeline with years
532
+ 6. Services β€” 3 cards: icon + service + description + included items
533
+ 7. Testimonials β€” 2–3 client quotes: avatar + name + company + quote
534
+ 8. Process β€” "How I Work", 4 numbered steps with icon + title + description
535
+ 9. Contact β€” "Let's Create Together", email + social icons, contact form (name/email/message/send)
536
+ 10. Footer β€” minimal: name + copyright + social
537
+ """,
538
+ "DASHBOARD": """
539
+ MANDATORY LAYOUT:
540
+ Fixed sidebar (w-64) + scrollable main area
541
+
542
+ SIDEBAR:
543
+ - Logo + app name at top
544
+ - Nav groups: Overview / Analytics / Projects / Team / Settings
545
+ - Each item: icon + label, active state highlighted
546
+ - User profile card at bottom (avatar + name + email + sign out)
547
+
548
+ TOPBAR (sticky within main):
549
+ - Page title left
550
+ - Search center
551
+ - Notifications bell + avatar right
552
+
553
+ MAIN CONTENT SECTIONS:
554
+ 1. Welcome banner β€” "Good morning, [Name]" + date + quick action buttons
555
+ 2. KPI cards β€” 4 cards: Total Revenue / Active Users / Conversion Rate / Monthly Growth
556
+ Each: icon box + big number + % change badge (green up / red down) + sparkline SVG
557
+ 3. Charts row β€” large area/line chart (col-span-2) + donut/pie chart (col-span-1)
558
+ 4. Recent activity table β€” headers + 6 rows with avatar + name + action + amount + status badge
559
+ 5. Bottom row β€” activity feed (left) + top performers list (right)
560
+
561
+ MOBILE: sidebar collapses to bottom tab bar on small screens
562
+ """
563
+ }
564
+
565
+
566
+ # ─────────────────────────────────────────────────────────────────
567
+ # SYSTEM PROMPTS
568
+ # ─────────────────────────────────────────────────────────────────
569
+
570
+ def get_builder_prompt(ref_content: str, website_type: str, prompt: str = "") -> str:
571
+ sections = SECTIONS_MAP.get(website_type, SECTIONS_MAP["SAAS LANDING PAGE"])
572
+ photo_instructions = get_photo_instructions(prompt, website_type)
573
+
574
+ ref_section = ""
575
+ if ref_content:
576
+ ref_section = f"""
577
+ ━━━ DESIGN REFERENCE β€” STUDY AND ADAPT ━━━
578
+ Learn the spacing rhythm, color tokens, animation approach, and component patterns.
579
+ Adapt these to the user's specific brand β€” do NOT copy verbatim.
580
+
581
+ {ref_content[:5000]}
582
+ ━━━ END REFERENCE ━━━
583
+ """
584
+
585
+ return f"""You are a world-class senior frontend engineer who has shipped production code at Stripe, Linear, Vercel, and Loom. You build websites so visually stunning they go viral on Twitter. Your work is indistinguishable from Lovable, v0.dev, or Framer β€” except yours is BETTER.
586
+
587
+ You are building a {website_type} right now.
588
+
589
+ {CDN_STACK}
590
+
591
+ {DESIGN_BIBLE}
592
+
593
+ {sections}
594
+
595
+ {ref_section}
596
+
597
+ {photo_instructions}
598
+
599
+ ━━━ QUALITY MANDATE ━━━
600
+ This website must look like it costs $50,000 to build. Ask yourself: "Would this appear on awwwards.com?"
601
+ - Every section must have visual hierarchy that guides the eye
602
+ - Dark theme: bg-[#07080f] base, subtle lighter sections for contrast
603
+ - Typography must use Syne (display) + DM Sans (body) β€” NOT Inter or Roboto
604
+ - Animations must feel intentional and premium, not cheap
605
+ - Every card must have hover state (translate-y, border color change, or glow)
606
+ - Hero must be breathtaking β€” if it's not shocking how impressive it is, make it more impressive
607
+
608
+ ━━━ OUTPUT RULES β€” NEVER BREAK ━━━
609
+ 1. Return ONLY raw HTML from <!DOCTYPE html> to </html>. NOTHING before or after.
610
+ 2. Zero markdown. Zero backticks. Zero explanations.
611
+ 3. ALL React inside <script type="text/babel"> at bottom of body
612
+ 4. Tailwind CDN for all utility classes; <style> block for CSS custom properties + animations
613
+ 5. Google Fonts via <link> in head
614
+ 6. REAL compelling content β€” specific names, real numbers, actual compelling copy
615
+ 7. Every section scroll-reveal via IntersectionObserver
616
+ 8. Scroll-to-top button fixed bottom-right"""
617
+
618
+
619
+ PHOTO_SYSTEM = """
620
+ Use loremflickr.com for ALL images: https://loremflickr.com/WIDTH/HEIGHT/keyword1,keyword2
621
+ - Hero bg: style={{backgroundImage:"url('https://loremflickr.com/1600/900/KEYWORD')"}}
622
+ - Section img: <img src="https://loremflickr.com/800/500/KEYWORD" className="w-full h-64 object-cover rounded-2xl" loading="lazy" decoding="async" alt="..." />
623
+ - Card img: <img src="https://loremflickr.com/600/500/KEYWORD" className="w-full h-52 object-cover" loading="lazy" decoding="async" alt="..." />
624
+ - Minimum 6 images. Add ?lock=1 ?lock=2 for variety. NEVER placeholder.com.
625
+ """
626
+
627
+ REVIEWER_PROMPT = f"""You are a world-class design director at a top-tier product studio. Your job: take a good website and make it extraordinary β€” Stripe/Linear/Vercel caliber.
628
+
629
+ OUTPUT RULES:
630
+ 1. Return ONLY the complete improved HTML. Nothing else. No backticks.
631
+ 2. KEEP ALL sections and images. Only elevate β€” never remove.
632
+
633
+ {DESIGN_BIBLE}
634
+
635
+ {PHOTO_SYSTEM}
636
+
637
+ ━━━ YOUR ENHANCEMENT CHECKLIST ━━━
638
+
639
+ TYPOGRAPHY UPGRADE:
640
+ β–‘ Hero headline must be MASSIVE (text-7xl lg:text-[96px] font-bold) β€” if it doesn't feel oversized, go bigger
641
+ β–‘ Use font-display class on all headlines (Syne font)
642
+ β–‘ Gradient text on main hero headline β€” animated gradientFlow
643
+ β–‘ Eyebrow labels use font-mono tracking-widest uppercase text-indigo-400
644
+
645
+ VISUAL HIERARCHY:
646
+ β–‘ Hero is the most impressive thing on the page β€” period
647
+ β–‘ Strong contrast between sections (alternate bg-[#07080f] and bg-[#0d0e1a])
648
+ β–‘ Section headers centered with eyebrow label + large title + muted subtitle
649
+ β–‘ Generous white space β€” padding on sections should be py-28 lg:py-36
650
+
651
+ ANIMATIONS β€” NON-NEGOTIABLE:
652
+ β–‘ useReveal hook (IntersectionObserver) on EVERY section
653
+ β–‘ Staggered animation delays on grid items (i * 0.1s)
654
+ β–‘ Hero content animates in on mount
655
+ β–‘ Cards: hover:-translate-y-1 + hover:border-indigo-500/20
656
+ β–‘ CTA buttons have shimmer/shine effect on hover
657
+ β–‘ Floating orb blobs behind hero (blur-[120px] large circles)
658
+
659
+ BENTO GRID FEATURES:
660
+ β–‘ Features section uses bento grid, NOT just a uniform 3-col grid
661
+ β–‘ One large featured card + smaller cells
662
+
663
+ SOCIAL PROOF:
664
+ β–‘ Hero has "Join 25,000+ teams" badge
665
+ β–‘ Real company names in trust bar
666
+ β–‘ Specific metrics with decimal places (not "many" but "14,832")
667
+ β–‘ Star ratings on ALL testimonials
668
+
669
+ PREMIUM DETAILS:
670
+ β–‘ Gradient border on highlighted pricing card
671
+ β–‘ Scroll-to-top button (fixed, bottom-right)
672
+ β–‘ Active anchor links with smooth scroll behavior
673
+ β–‘ FAQ accordion (useState) smooth expand/collapse
674
+ β–‘ Input focus rings (ring-2 ring-indigo-500/50)
675
+ β–‘ All interactive elements: cursor-pointer
676
+
677
+ MOBILE:
678
+ β–‘ All grids: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3
679
+ β–‘ Hamburger with animated lines (rotate on open)
680
+ β–‘ No horizontal overflow
681
+ β–‘ Tap targets at least 44px height
682
+
683
+ {CDN_STACK}"""
684
+
685
+
686
+ POLISHER_PROMPT = f"""You are the creative director doing the final QA pass before launch. Make it flawless.
687
+
688
+ OUTPUT RULES:
689
+ 1. Return ONLY the final complete HTML. Nothing else.
690
+ 2. Do NOT remove anything. Only polish.
691
+
692
+ {PHOTO_SYSTEM}
693
+
694
+ ━━━ FINAL POLISH CHECKLIST ━━━
695
+
696
+ SEO + META:
697
+ β–‘ <title> specific, compelling, under 60 chars
698
+ β–‘ <meta name="description"> under 160 chars
699
+ β–‘ <meta property="og:title"> and <meta property="og:description">
700
+ β–‘ <link rel="icon"> emoji favicon
701
+ β–‘ <meta name="theme-color"> matching brand color (#6366f1)
702
+ β–‘ <meta name="viewport" content="width=device-width, initial-scale=1.0">
703
+
704
+ MICRO-INTERACTIONS:
705
+ β–‘ Nav links: underline slide-in on hover via CSS
706
+ β–‘ Feature icons: scale on card hover (transition in CSS, applied via group)
707
+ β–‘ CTA primary button: shimmer overlay on hover
708
+ β–‘ All interactive elements have cursor-pointer
709
+ β–‘ Buttons: active:scale-95 press feedback
710
+
711
+ VISUAL FINAL TOUCHES:
712
+ β–‘ Color palette consistent across ALL sections
713
+ β–‘ No section looks out of place
714
+ β–‘ Fonts loading correctly from Google Fonts
715
+ β–‘ Images all have loading="lazy" decoding="async" and alt text
716
+ β–‘ CSS custom properties in :root are complete
717
+
718
+ JAVASCRIPT:
719
+ β–‘ Hamburger toggle works flawlessly
720
+ β–‘ FAQ accordion opens/closes smoothly
721
+ β–‘ Scroll-to-top shows at 300px and works
722
+ β–‘ Smooth scroll on all anchor links
723
+ β–‘ No console errors from React
724
+
725
+ PERFORMANCE:
726
+ β–‘ will-change: transform on animated elements
727
+ β–‘ Images have loading="lazy" and decoding="async"
728
+ β–‘ No unused CDN imports
729
+
730
+ FOOTER FINAL:
731
+ β–‘ Dark bg (#050608 or #07080f)
732
+ β–‘ 3-4 link columns
733
+ β–‘ Social icons with hover color
734
+ β–‘ Copyright with current year
735
+ β–‘ Brief brand tagline
736
+
737
+ {CDN_STACK}"""
738
+
739
+
740
+ # ─────────────────────────────────────────────────────────────────
741
+ # API CALLERS
742
+ # ─────────────────────────────────────────────────────────────────
743
+
744
+ async def call_cerebras(system_role: str, user_message: str, previous_code: str = "") -> str:
745
+ """Cerebras β€” ultra-fast inference (1000+ tokens/sec). PRIMARY generator."""
746
+ api_key = os.environ.get("CEREBRAS_API_KEY", "")
747
+ if not api_key:
748
+ print("[BuildAI] No CEREBRAS_API_KEY β€” falling back to Groq")
749
+ return await call_groq(system_role, user_message, previous_code)
750
+ full_message = user_message
751
+ if previous_code:
752
+ full_message += f"\n\nCurrent code to review and improve:\n\n{previous_code}"
753
+ payload = {
754
+ "model": "llama-3.3-70b",
755
+ "messages": [{"role": "system", "content": system_role}, {"role": "user", "content": full_message}],
756
+ "max_tokens": 8192,
757
+ "temperature": 0.7,
758
+ }
759
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
760
+ async with httpx.AsyncClient(timeout=90.0) as client:
761
+ r = await client.post("https://api.cerebras.ai/v1/chat/completions", json=payload, headers=headers)
762
+ if r.status_code == 429:
763
+ print("[BuildAI] Cerebras rate limited β€” falling back to Groq")
764
+ return await call_groq(system_role, user_message, previous_code)
765
+ if r.status_code in (500, 503):
766
+ print(f"[BuildAI] Cerebras server error {r.status_code} β€” falling back to Groq")
767
+ return await call_groq(system_role, user_message, previous_code)
768
+ if not r.is_success:
769
+ print(f"[BuildAI] Cerebras failed {r.status_code}: {r.text[:100]} β€” falling back to Groq")
770
+ return await call_groq(system_role, user_message, previous_code)
771
+ return clean_code(r.json()["choices"][0]["message"]["content"])
772
+
773
+
774
+ async def call_gemini(system_role: str, user_message: str, previous_code: str = "") -> str:
775
+ """Gemini Flash β€” excellent quality reviewer via OpenAI-compatible endpoint."""
776
+ api_key = os.environ.get("GEMINI_API_KEY", "")
777
+ if not api_key:
778
+ print("[BuildAI] No GEMINI_API_KEY β€” falling back to OpenRouter")
779
+ return await call_openrouter(system_role, user_message, previous_code)
780
+ full_message = user_message
781
+ if previous_code:
782
+ full_message += f"\n\nCode to review and dramatically elevate:\n\n{previous_code}"
783
+ payload = {
784
+ "model": "gemini-2.5-flash",
785
+ "messages": [{"role": "system", "content": system_role}, {"role": "user", "content": full_message}],
786
+ "max_tokens": 8192,
787
+ "temperature": 0.6,
788
+ }
789
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
790
+ async with httpx.AsyncClient(timeout=120.0) as client:
791
+ r = await client.post(
792
+ "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
793
+ json=payload, headers=headers
794
+ )
795
+ if not r.is_success:
796
+ print(f"[BuildAI] Gemini failed {r.status_code}: {r.text[:100]} β€” falling back to OpenRouter")
797
+ return await call_openrouter(system_role, user_message, previous_code)
798
+ return clean_code(r.json()["choices"][0]["message"]["content"])
799
+
800
+
801
+ async def call_groq(system_role: str, user_message: str, previous_code: str = "") -> str:
802
+ """Groq LPU β€” fast inference. Fallback for Cerebras / final polisher."""
803
+ api_key = os.environ.get("GROQ_API_KEY", "")
804
+ if not api_key:
805
+ print("[BuildAI] No GROQ_API_KEY β€” falling back to OpenRouter")
806
+ return await call_openrouter(system_role, user_message, previous_code)
807
+ full_message = user_message
808
+ if previous_code:
809
+ full_message += f"\n\nCurrent code to improve:\n\n{previous_code}"
810
+ payload = {
811
+ "model": "llama-3.3-70b-versatile",
812
+ "messages": [{"role": "system", "content": system_role}, {"role": "user", "content": full_message}],
813
+ "max_tokens": 8000,
814
+ "temperature": 0.7,
815
+ }
816
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
817
+ async with httpx.AsyncClient(timeout=90.0) as client:
818
+ r = await client.post("https://api.groq.com/openai/v1/chat/completions", json=payload, headers=headers)
819
+ if r.status_code == 429:
820
+ print("[BuildAI] Groq rate limited β€” falling back to OpenRouter")
821
+ return await call_openrouter(system_role, user_message, previous_code)
822
+ if r.status_code in (500, 503):
823
+ print(f"[BuildAI] Groq error {r.status_code} β€” falling back to OpenRouter")
824
+ return await call_openrouter(system_role, user_message, previous_code)
825
+ if not r.is_success:
826
+ raise ValueError(f"Groq failed: {r.status_code} β€” {r.text[:150]}")
827
+ return clean_code(r.json()["choices"][0]["message"]["content"])
828
+
829
+
830
+ async def call_openrouter(system_role: str, user_message: str, previous_code: str = "") -> str:
831
+ """OpenRouter β€” 300+ models. Uses DeepSeek for superior reasoning."""
832
+ api_key = os.environ.get("OPENROUTER_API_KEY", "")
833
+ if not api_key:
834
+ print("[BuildAI] No OPENROUTER_API_KEY β€” falling back to Mistral")
835
+ return await call_mistral(system_role, user_message, previous_code)
836
+ full_message = user_message
837
+ if previous_code:
838
+ full_message += f"\n\nCode to review and improve:\n\n{previous_code}"
839
+ payload = {
840
+ "model": "meta-llama/llama-3.3-70b-instruct",
841
+ "messages": [{"role": "system", "content": system_role}, {"role": "user", "content": full_message}],
842
+ "max_tokens": 8000,
843
+ "temperature": 0.65,
844
+ }
845
+ headers = {
846
+ "Authorization": f"Bearer {api_key}",
847
+ "HTTP-Referer": "https://buildai.app",
848
+ "X-Title": "BuildAI",
849
+ "Content-Type": "application/json"
850
+ }
851
+ async with httpx.AsyncClient(timeout=90.0) as client:
852
+ r = await client.post("https://openrouter.ai/api/v1/chat/completions", json=payload, headers=headers)
853
+ if not r.is_success:
854
+ print(f"[BuildAI] OpenRouter failed {r.status_code} β€” falling back to Mistral")
855
+ return await call_mistral(system_role, user_message, previous_code)
856
+ return clean_code(r.json()["choices"][0]["message"]["content"])
857
+
858
+
859
+ async def call_mistral(system_role: str, user_message: str, previous_code: str = "") -> str:
860
+ """Mistral β€” European model, GDPR-friendly final polisher."""
861
+ api_key = os.environ.get("MISTRAL_API_KEY", "")
862
+ if not api_key:
863
+ raise ValueError("All API keys exhausted. Please add at least one API key in HuggingFace Secrets.")
864
+ full_message = user_message
865
+ if previous_code:
866
+ full_message += f"\n\nCode to polish:\n\n{previous_code}"
867
+ payload = {
868
+ "model": "mistral-small-latest",
869
+ "messages": [{"role": "system", "content": system_role}, {"role": "user", "content": full_message}],
870
+ "max_tokens": 7000,
871
+ "temperature": 0.6,
872
+ }
873
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
874
+ async with httpx.AsyncClient(timeout=120.0) as client:
875
+ r = await client.post("https://api.mistral.ai/v1/chat/completions", json=payload, headers=headers)
876
+ if not r.is_success:
877
+ raise ValueError(f"Mistral failed: {r.status_code} β€” {r.text[:200]}")
878
+ return clean_code(r.json()["choices"][0]["message"]["content"])
879
+
880
+
881
+ def clean_code(text: str) -> str:
882
+ """Strip markdown fences and any text before DOCTYPE/after </html>."""
883
+ text = text.strip()
884
+ for fence in ["```html\n", "```html", "```\n", "```"]:
885
+ if text.startswith(fence):
886
+ text = text[len(fence):]
887
+ break
888
+ if text.endswith("```"):
889
+ text = text[:-3]
890
+ if "<!DOCTYPE" in text:
891
+ idx = text.index("<!DOCTYPE")
892
+ text = text[idx:]
893
+ if "</html>" in text:
894
+ idx = text.rindex("</html>")
895
+ text = text[:idx + 7]
896
+ return text.strip()
897
+
898
+
899
+ # ─────────────────────────────────────────────────────────────────
900
+ # MAIN PIPELINE
901
+ # ─────────────────────────────────────────────────────────────────
902
+
903
+ async def run_pipeline(prompt: str) -> AsyncGenerator[str, None]:
904
+ ref_content, website_type = get_references(prompt)
905
+ ref_status = "βœ“ reference loaded" if ref_content else "⚠ no reference"
906
+ print(f"[BuildAI] Building: {website_type} | {ref_status}")
907
+
908
+ builder_system = get_builder_prompt(ref_content, website_type, prompt)
909
+
910
+ task = (
911
+ f"Build a stunning, complete {website_type} website for: {prompt}\n\n"
912
+ f"CRITICAL REQUIREMENTS:\n"
913
+ f"- Use Syne (display) + DM Sans (body) fonts β€” NOT Inter\n"
914
+ f"- Use React + Tailwind CDN exactly as specified\n"
915
+ f"- Include ALL mandatory sections for {website_type}\n"
916
+ f"- Real photos from loremflickr.com (minimum 6 images)\n"
917
+ f"- Write real, specific, compelling content β€” NO Lorem ipsum EVER\n"
918
+ f"- Every card must have hover animation\n"
919
+ f"- Hero must be breathtaking with mesh gradient + floating orbs\n"
920
+ f"- Bento grid for features section\n"
921
+ f"- CSS custom properties in :root for design tokens\n"
922
+ f"- Scroll reveal animations via IntersectionObserver on every section\n"
923
+ f"- This must look like a premium $50,000+ agency website\n"
924
+ f"- Output ONLY raw HTML, nothing else"
925
+ )
926
+
927
+ # ── Round 1: Cerebras builds (ultra-fast) ──────────────────
928
+ yield make_sse({"type": "status", "round": 1, "done": False,
929
+ "message": f"⚑ Round 1 β€” Cerebras building your {website_type} at 1,000 tokens/sec..."})
930
+ try:
931
+ r1 = await call_cerebras(builder_system, task)
932
+ yield make_sse({"type": "code", "round": 1, "done": True,
933
+ "message": "βœ“ Round 1 complete β€” foundation built",
934
+ "code": r1, "final": False})
935
+ except Exception as e:
936
+ yield make_sse({"type": "error", "round": 1, "done": False, "message": str(e)})
937
+ return
938
+
939
+ await asyncio.sleep(1)
940
+
941
+ # ── Round 2: Gemini Flash reviews ─────────────────────────
942
+ yield make_sse({"type": "status", "round": 2, "done": False,
943
+ "message": "🎨 Round 2 β€” Gemini Flash elevating design to Stripe/Linear level..."})
944
+ reviewer_with_photos = REVIEWER_PROMPT + "\n\n" + get_photo_instructions(prompt, website_type)
945
+ try:
946
+ r2 = await call_gemini(
947
+ reviewer_with_photos,
948
+ f"Elevate this {website_type} to award-winning quality. Make every section breathtaking. KEEP all existing images. Brief: {prompt}",
949
+ r1
950
+ )
951
+ yield make_sse({"type": "code", "round": 2, "done": True,
952
+ "message": "βœ“ Round 2 complete β€” design elevated",
953
+ "code": r2, "final": False})
954
+ except Exception as e:
955
+ yield make_sse({"type": "error", "round": 2, "done": False,
956
+ "message": f"Round 2 issue: {e} β€” using Round 1 result"})
957
+ r2 = r1
958
+
959
+ await asyncio.sleep(1)
960
+
961
+ # ── Round 3: Groq/Mistral polishes ────────────────────────
962
+ yield make_sse({"type": "status", "round": 3, "done": False,
963
+ "message": "✨ Round 3 β€” Final polish: SEO, micro-interactions, perfection..."})
964
+ polisher_with_photos = POLISHER_PROMPT + "\n\n" + get_photo_instructions(prompt, website_type)
965
+ try:
966
+ r3 = await call_groq(
967
+ polisher_with_photos,
968
+ f"Final quality pass. Ensure this {website_type} is flawless β€” perfect SEO, micro-interactions, no bugs. Keep ALL images. Brief: {prompt}",
969
+ r2
970
+ )
971
+ yield make_sse({"type": "code", "round": 3, "done": True,
972
+ "message": "πŸŽ‰ Website ready! Download or publish it.",
973
+ "code": r3, "final": True})
974
+ except Exception as e:
975
+ # Final fallback β€” try Mistral
976
+ try:
977
+ r3 = await call_mistral(
978
+ polisher_with_photos,
979
+ f"Final polish pass for this {website_type}. Brief: {prompt}",
980
+ r2
981
+ )
982
+ yield make_sse({"type": "code", "round": 3, "done": True,
983
+ "message": "πŸŽ‰ Website ready!",
984
+ "code": r3, "final": True})
985
+ except Exception as e2:
986
+ yield make_sse({"type": "error", "round": 3, "done": False,
987
+ "message": f"Round 3 failed: {e2} β€” delivering Round 2 result"})
988
+ yield make_sse({"type": "code", "round": 3, "done": True,
989
+ "message": "Website ready (Round 2)!", "code": r2, "final": True})
990
+
991
+
992
+ def make_sse(data: dict) -> str:
993
+ return f"data: {json.dumps(data)}\n\n"
builder.html ADDED
@@ -0,0 +1,840 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0"/>
6
+ <title>BuildAI β€” Builder</title>
7
+ <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>"/>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
9
+ <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"/>
10
+ <style>
11
+ *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
12
+ html,body{height:100%;font-family:'Inter',system-ui,sans-serif;background:#07080f;color:#e2e8f0;-webkit-font-smoothing:antialiased;overflow:hidden}
13
+ button,input,textarea{font-family:inherit}
14
+ ::-webkit-scrollbar{width:4px;height:4px}
15
+ ::-webkit-scrollbar-track{background:transparent}
16
+ ::-webkit-scrollbar-thumb{background:#1e2740;border-radius:99px}
17
+ @keyframes spin{to{transform:rotate(360deg)}}
18
+ @keyframes fadeUp{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:translateY(0)}}
19
+ @keyframes fadeIn{from{opacity:0}to{opacity:1}}
20
+ @keyframes floatY{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
21
+ @keyframes blink{0%,100%{opacity:.2}50%{opacity:1}}
22
+ @keyframes gradFlow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
23
+ @keyframes dotBounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
24
+ @keyframes slideDown{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
25
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
26
+
27
+ body{display:flex;flex-direction:column}
28
+
29
+ /* ── TOOLBAR ── */
30
+ .tb{height:42px;display:flex;align-items:center;padding:0 8px;border-bottom:1px solid #0f1220;background:#0b0d17;flex-shrink:0;gap:3px;z-index:200;overflow:visible}
31
+ .tb-left{display:flex;align-items:center;gap:3px;flex-shrink:0}
32
+ .tb-logo{display:flex;align-items:center;gap:6px;cursor:pointer;padding:3px 7px;border-radius:7px;transition:background .15s}
33
+ .tb-logo:hover{background:#13172a}
34
+ .tb-lm{width:18px;height:18px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:9px;flex-shrink:0}
35
+ .tb-ln{font-size:12px;font-weight:800;letter-spacing:-.3px}
36
+ .tb-proj{display:flex;align-items:center;gap:5px;padding:4px 8px;background:#13172a;border:1px solid #1a1f33;border-radius:7px;cursor:pointer;max-width:130px;flex-shrink:0}
37
+ .tb-pn{font-size:11px;color:#64748b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
38
+ .tb-sep{width:1px;height:22px;background:#0f1220;margin:0 2px;flex-shrink:0}
39
+ .tb-ic{width:27px;height:27px;background:transparent;border:none;border-radius:6px;color:#334155;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;transition:background .15s,color .15s}
40
+ .tb-ic:hover{background:#13172a;color:#64748b}
41
+ .tb-center{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0}
42
+ .tab-b{padding:5px 11px;background:transparent;border:1px solid transparent;border-radius:7px;color:#334155;font-size:11px;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s}
43
+ .tab-b.on{background:#13172a;border-color:#1e2740;color:#c4d4f0;font-weight:600}
44
+ .tab-b:hover:not(.on){color:#64748b}
45
+ .tb-url{display:flex;align-items:center;gap:5px;padding:4px 10px;background:#0f1220;border:1px solid #13172a;border-radius:7px;margin-left:6px;max-width:190px;overflow:hidden}
46
+ .tb-url-t{font-size:10px;color:#334155;font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
47
+ .tb-right{display:flex;align-items:center;gap:3px;flex-shrink:0}
48
+ .vp-grp{display:flex;gap:1px;background:#0f1220;border:1px solid #13172a;border-radius:7px;padding:2px;flex-shrink:0}
49
+ .vp-b{width:24px;height:22px;background:transparent;border:none;border-radius:5px;cursor:pointer;font-size:10px;transition:background .15s;display:flex;align-items:center;justify-content:center}
50
+ .vp-b.on{background:#1e2740}
51
+ .tb-tool{padding:4px 9px;background:#13172a;border:1px solid #1a1f33;border-radius:7px;color:#64748b;font-size:11px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:3px;white-space:nowrap;flex-shrink:0;transition:all .15s}
52
+ .tb-tool:hover{border-color:#2e3a55;color:#94a3b8}
53
+ .tb-pub{padding:5px 12px;background:linear-gradient(135deg,#059669,#10b981);border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0}
54
+ .tb-pub:disabled{opacity:.4;cursor:default}
55
+ .tb-signin{padding:5px 12px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0}
56
+ .tb-guest{padding:5px 10px;background:#13172a;border:1px solid #1a1f33;border-radius:7px;color:#475569;font-size:11px;cursor:pointer;white-space:nowrap;flex-shrink:0}
57
+ .tb-guest:hover{border-color:#6366f1;color:#818cf8}
58
+ .tb-av{width:26px;height:26px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;cursor:pointer;flex-shrink:0;position:relative}
59
+ .user-dd{position:absolute;top:32px;right:0;width:200px;background:#0e1018;border:1px solid #1a1f33;border-radius:12px;z-index:9999;animation:slideDown .15s ease;box-shadow:0 20px 50px rgba(0,0,0,.6);overflow:hidden}
60
+ .ud-head{padding:12px 14px;border-bottom:1px solid #0f1220}
61
+ .ud-name{font-size:13px;font-weight:600;color:#c4d4f0;margin-bottom:2px}
62
+ .ud-email{font-size:11px;color:#334155;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
63
+ .ud-item{padding:10px 14px;font-size:12px;color:#475569;cursor:pointer;display:flex;align-items:center;gap:8px;transition:background .15s}
64
+ .ud-item:hover{background:#0f1220;color:#94a3b8}
65
+ .ud-item.red:hover{color:#f87171}
66
+ .ud-sep{height:1px;background:#0f1220}
67
+ .mob-tabs{display:none;height:44px;background:#0b0d17;border-bottom:1px solid #0f1220;flex-shrink:0}
68
+ .mob-tab{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;background:transparent;border:none;border-bottom:2px solid transparent;color:#334155;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
69
+ .mob-tab.on{color:#c4d4f0;border-bottom-color:#6366f1}
70
+
71
+ /* ── MAIN AREA ── */
72
+ .main{flex:1;display:flex;overflow:hidden;min-height:0}
73
+ .sb{width:185px;flex-shrink:0;display:flex;flex-direction:column;background:#0b0d17;border-right:1px solid #0f1220;overflow:hidden}
74
+ .sb-hd{padding:8px 10px;border-bottom:1px solid #0f1220;display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
75
+ .sb-lbl{font-size:10px;font-weight:700;color:#1e2740;text-transform:uppercase;letter-spacing:1.2px}
76
+ .sb-new{width:calc(100% - 14px);margin:7px;padding:8px;background:#0f1220;border:1px dashed #1a1f33;border-radius:8px;color:#334155;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:5px;transition:all .2s}
77
+ .sb-new:hover{border-color:#6366f1;color:#6366f1}
78
+ .sb-list{flex:1;overflow-y:auto;padding:0 7px 8px}
79
+ .sb-sec{font-size:9.5px;font-weight:700;color:#131828;text-transform:uppercase;letter-spacing:1.2px;padding:6px 8px 4px}
80
+ .sb-empty{padding:20px 10px;text-align:center;font-size:11px;color:#131828;line-height:1.8}
81
+ .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}
82
+ .sb-sp-t{font-size:11px;color:#334155;margin-bottom:8px;line-height:1.6}
83
+ .sb-sp-b{width:100%;padding:7px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:7px;color:#fff;font-size:11px;font-weight:600;cursor:pointer}
84
+ .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}
85
+ .pi:hover{background:#0f1220}
86
+ .pi.on{background:#111e36;border-color:#1e3a5f}
87
+ .pi-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
88
+ .pi-n{font-size:11.5px;color:#475569;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
89
+ .pi.on .pi-n{color:#c4d4f0;font-weight:500}
90
+ .pi-d{font-size:9.5px;color:#131828;margin-top:1px}
91
+ .sb-ft{padding:8px;border-top:1px solid #0f1220;flex-shrink:0}
92
+ .sb-user{display:flex;align-items:center;gap:8px;padding:8px 10px;background:#0f1220;border-radius:8px;cursor:pointer}
93
+ .sb-uav{width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}
94
+ .sb-un{font-size:11px;font-weight:600;color:#64748b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
95
+ .sb-up{font-size:9.5px;color:#1e2740}
96
+ .sb-signout{font-size:10px;color:#334155;background:none;border:none;cursor:pointer;margin-top:4px;display:none;width:100%;text-align:center;padding:4px}
97
+ .sb-signout:hover{color:#f87171}
98
+
99
+ /* ── CHAT PANEL ── */
100
+ .cp{width:286px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid #0f1220;background:#0b0d17;overflow:hidden}
101
+ .c-stat{padding:6px 12px;border-bottom:1px solid #0f1220;display:flex;align-items:center;gap:6px;background:#09091a;flex-shrink:0}
102
+ .r-dots{display:flex;gap:3px}
103
+ .r-dot{width:19px;height:19px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;font-weight:700;border:1px solid #1e2740;color:#1e2740;transition:all .35s;flex-shrink:0}
104
+ .r-dot.act{border-color:#8b5cf6;background:rgba(139,92,246,.2);color:#c4b5fd}
105
+ .r-dot.dn{border-color:#10b981;background:rgba(16,185,129,.15);color:#34d399}
106
+ .c-stat-t{font-size:11px;color:#334155;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
107
+ .c-msgs{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:10px}
108
+ .c-empty{display:flex;flex-direction:column;align-items:center;padding:32px 16px;text-align:center}
109
+ .c-e-ic{font-size:36px;margin-bottom:10px;animation:floatY 3.5s ease-in-out infinite}
110
+ .c-e-t{font-size:13.5px;font-weight:700;color:#c4d4f0;margin-bottom:6px}
111
+ .c-e-s{font-size:12px;color:#334155;line-height:1.7;margin-bottom:16px}
112
+ .q-prompts{display:flex;flex-direction:column;gap:7px;width:100%}
113
+ .q-p{padding:9px 12px;background:#0f1220;border:1px solid #1a1f33;border-radius:9px;color:#475569;font-size:11.5px;cursor:pointer;text-align:left;transition:all .2s}
114
+ .q-p:hover{border-color:rgba(99,102,241,.3);color:#94a3b8;background:rgba(99,102,241,.05)}
115
+ .m-u{display:flex;justify-content:flex-end}
116
+ .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}
117
+ .m-a{display:flex;flex-direction:column;gap:7px}
118
+ .m-a-lbl{font-size:9px;font-weight:700;color:#1e2740;letter-spacing:1.5px}
119
+ .m-card{background:#0f1220;border:1px solid #1a1f33;border-radius:10px;padding:10px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px}
120
+ .m-card-h{font-size:12px;font-weight:600;color:#c4d4f0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
121
+ .m-card-tabs{display:flex;gap:4px;flex-shrink:0}
122
+ .m-card-tb{padding:4px 9px;background:transparent;border:1px solid #1e2740;border-radius:6px;color:#334155;font-size:10px;cursor:pointer;transition:all .15s}
123
+ .m-card-tb.on{background:#1e2740;color:#c4d4f0;border-color:#2e3a55}
124
+ .m-card-tb:hover:not(.on){color:#64748b}
125
+ .m-think{background:#0a0f1e;border:1px solid #1a1f33;border-radius:10px;padding:12px 14px;font-size:11.5px;line-height:1.9}
126
+ .think-hd{display:flex;align-items:center;gap:8px;font-weight:600;color:#6366f1;margin-bottom:8px}
127
+ .think-dot{width:6px;height:6px;border-radius:50%;background:#6366f1;flex-shrink:0;animation:blink 1.2s ease-in-out infinite}
128
+ .think-ln{color:#334155}
129
+ .think-ln.ap{color:#475569;margin-bottom:6px}
130
+ .think-ln.bl{padding-left:8px}
131
+ .think-ln.dn{color:#10b981}
132
+ .m-txt{font-size:12px;color:#475569;line-height:1.7}
133
+ .m-save-note{font-size:11px;color:#10b981;display:flex;align-items:center;gap:5px}
134
+ .m-next-t{font-size:11px;font-weight:700;color:#334155;text-transform:uppercase;letter-spacing:1px;margin-top:4px}
135
+ .m-next-i{font-size:11.5px;color:#475569;line-height:1.7}
136
+ .m-acts{display:flex;gap:6px;flex-wrap:wrap}
137
+ .m-act{padding:6px 11px;background:#0f1220;border:1px solid #1a1f33;border-radius:7px;color:#475569;font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}
138
+ .m-act:hover{border-color:rgba(99,102,241,.3);color:#818cf8}
139
+ .m-act.primary{background:linear-gradient(135deg,#6366f1,#8b5cf6);border-color:transparent;color:#fff;font-weight:600}
140
+ .m-act.primary:hover{opacity:.9}
141
+ .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}
142
+ .typing{display:flex;gap:4px;padding:4px 0}
143
+ .td{width:5px;height:5px;border-radius:50%;background:#1e2740;animation:dotBounce .8s ease-in-out infinite}
144
+ .td:nth-child(2){animation-delay:.15s}
145
+ .td:nth-child(3){animation-delay:.3s}
146
+ .sugg-bar{padding:8px 12px;border-top:1px solid #0f1220;display:flex;gap:6px;overflow-x:auto;flex-shrink:0;scrollbar-width:none}
147
+ .sugg-bar::-webkit-scrollbar{display:none}
148
+ .s-chip{padding:5px 10px;background:#0f1220;border:1px solid #1a1f33;border-radius:999px;color:#475569;font-size:11px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .15s}
149
+ .s-chip:hover{border-color:rgba(99,102,241,.3);color:#818cf8}
150
+ .c-inp-area{padding:8px;border-top:1px solid #0f1220;flex-shrink:0;background:#09091a}
151
+ .c-inp-row{display:flex;align-items:flex-end;gap:7px;background:rgba(255,255,255,.03);border:1px solid #1a1f33;border-radius:13px;padding:6px 6px 6px 12px;transition:border-color .2s}
152
+ .c-inp-row:focus-within{border-color:rgba(99,102,241,.3)}
153
+ .c-ta{flex:1;background:transparent;border:none;outline:none;color:#e2e8f0;font-size:13px;resize:none;min-height:24px;max-height:80px;line-height:1.6;padding:3px 0}
154
+ .c-ta::placeholder{color:#1e2740}
155
+ .c-ta:disabled{opacity:.5;cursor:not-allowed}
156
+ .c-send{width:34px;height:34px;border-radius:10px;border:none;flex-shrink:0;background:#1a1f33;color:#334155;cursor:default;display:flex;align-items:center;justify-content:center;font-size:15px;transition:all .2s}
157
+ .c-send.on{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;cursor:pointer}
158
+
159
+ /* ── PREVIEW PANEL ── */
160
+ .pv{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
161
+ .pub-bar{padding:4px 14px;background:#0b0d17;border-bottom:1px solid #0f1220;display:flex;align-items:center;gap:8px;flex-shrink:0}
162
+ .pub-dot{width:5px;height:5px;border-radius:50%;background:#10b981;flex-shrink:0}
163
+ .pub-t{font-size:10px;color:#10b981}
164
+ .pub-dt{font-size:10px;color:#131828;margin-left:auto}
165
+ .pv-body{flex:1;position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#10121e}
166
+ .pv-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;height:100%;width:100%}
167
+ .pv-e-ic{font-size:64px;opacity:.09;animation:floatY 3.5s ease-in-out infinite}
168
+ .pv-e-t{font-size:15px;font-weight:600;color:#131828}
169
+ .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)}
170
+ .ov-spin{width:48px;height:48px;border-radius:50%;border:3px solid #1a1f33;border-top:3px solid #6366f1;animation:spin .85s linear infinite}
171
+ .ov-t{font-size:13px;color:#475569;text-align:center;max-width:240px}
172
+ .ov-s{font-size:11px;color:#131828;text-align:center}
173
+ .ov-badges{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:4px}
174
+ .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}
175
+ .ov-b.act{background:rgba(99,102,241,.25);border-color:rgba(99,102,241,.5);color:#c4b5fd}
176
+ .ov-bar{width:200px;height:2px;background:#1a1f33;border-radius:999px;overflow:hidden;margin-top:6px}
177
+ .ov-prog{height:100%;background:linear-gradient(90deg,#6366f1,#8b5cf6);border-radius:999px;transition:width .6s ease}
178
+ .code-view{width:100%;height:100%;overflow-y:auto;background:#07080f}
179
+ .code-pre{margin:0;padding:22px;font-size:11px;line-height:1.9;color:#2e3a55;font-family:'JetBrains Mono',monospace;white-space:pre-wrap;word-break:break-word}
180
+ .pv-btm{padding:6px 14px;border-top:1px solid #0f1220;background:#0b0d17;display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}
181
+ .dl-btn{padding:5px 12px;background:#0f1220;border:1px solid #1a1f33;border-radius:7px;color:#334155;font-size:11px;cursor:pointer;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap}
182
+ .dl-btn:hover{border-color:rgba(99,102,241,.3);color:#6366f1}
183
+ .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}
184
+ .dl-zip:hover{background:rgba(99,102,241,.2);border-color:rgba(99,102,241,.4);color:#c4b5fd}
185
+ .pv-hint{font-size:10px;color:#0f1220;margin-left:auto}
186
+ .ver-panel{position:absolute;top:38px;left:0;width:250px;background:#0e1018;border:1px solid #1a1f33;border-radius:12px;z-index:9999;animation:slideDown .15s ease;box-shadow:0 20px 50px rgba(0,0,0,.6)}
187
+ .ver-hd{padding:10px 14px;border-bottom:1px solid #1a1f33;display:flex;justify-content:space-between;align-items:center}
188
+ .ver-hd-t{font-size:12px;font-weight:600}
189
+ .ver-x{background:none;border:none;color:#334155;cursor:pointer;font-size:15px}
190
+ .ver-item{padding:10px 14px;border-bottom:1px solid #0f1220;display:flex;align-items:center;gap:10px;cursor:pointer;transition:background .15s}
191
+ .ver-item:hover{background:#0f1220}
192
+ .ver-item.cur{background:#0e1928}
193
+ .ver-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
194
+ .ver-n{font-size:12px;color:#475569}
195
+ .ver-item.cur .ver-n{color:#c4d4f0;font-weight:500}
196
+ .ver-d{font-size:10px;color:#1e2740;margin-top:1px}
197
+ .ver-cur-lbl{font-size:10px;color:#6366f1;font-weight:600;margin-left:auto}
198
+ .ver-empty{padding:16px 14px;font-size:11px;color:#131828;text-align:center}
199
+
200
+ /* MODALS */
201
+ .modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.82);z-index:9999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(12px);padding:16px;animation:fadeIn .15s ease}
202
+ .modal-box{background:#0e1018;border:1px solid #1a1f33;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)}
203
+ .mo-p{padding:24px}
204
+ .mo-hd{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px;gap:10px}
205
+ .mo-t{font-weight:700;font-size:16px}
206
+ .mo-sub{font-size:12px;color:#475569;margin-top:2px}
207
+ .mo-x{background:none;border:none;color:#475569;cursor:pointer;font-size:22px;line-height:1;flex-shrink:0;transition:color .15s}
208
+ .mo-x:hover{color:#94a3b8}
209
+ .flbl{font-size:12px;color:#475569;font-weight:500;display:block;margin-bottom:6px}
210
+ .finp{width:100%;background:#090b12;border:1px solid #1a1f33;border-radius:11px;padding:12px 14px;color:#e2e8f0;font-size:13px;outline:none;margin-bottom:12px;display:block;transition:border-color .2s}
211
+ .finp:focus{border-color:rgba(99,102,241,.35)}
212
+ .finp::placeholder{color:#1e2740}
213
+ .mo-btn-p{width:100%;padding:13px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:11px;color:#fff;font-size:14px;font-weight:700;cursor:pointer;margin-top:4px;transition:opacity .2s}
214
+ .mo-btn-p:hover{opacity:.9}
215
+ .mo-btn-p:disabled{opacity:.5;cursor:not-allowed}
216
+ .mo-btn-s{width:100%;padding:12px;background:#090b12;border:1px solid #1a1f33;border-radius:11px;color:#64748b;font-size:13px;cursor:pointer;margin-top:8px;transition:border-color .2s}
217
+ .mo-btn-s:hover{border-color:#2e3a55}
218
+ .mo-div{display:flex;align-items:center;gap:12px;margin:16px 0}
219
+ .mo-div-l{flex:1;height:1px;background:#1a1f33}
220
+ .mo-div-t{font-size:11px;color:#1e2740}
221
+ .tab-sw{display:flex;gap:3px;background:#090b12;border-radius:11px;padding:3px;margin-bottom:20px}
222
+ .tab-sw-b{flex:1;padding:9px;background:transparent;border:none;border-radius:9px;color:#334155;font-size:13px;cursor:pointer;transition:all .15s}
223
+ .tab-sw-b.on{background:#1a1f33;color:#e2e8f0;font-weight:600}
224
+ .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}
225
+ .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}
226
+ .suc-c{text-align:center;padding:16px 0 8px}
227
+ .suc-ic{font-size:52px;margin-bottom:14px;display:block;animation:floatY 2.5s ease-in-out infinite}
228
+ .suc-t{font-weight:900;font-size:19px;margin-bottom:8px;letter-spacing:-.4px}
229
+ .pub-plan{background:#090b12;border:1px solid #1a1f33;border-radius:13px;padding:15px 17px;margin-bottom:9px;cursor:pointer;transition:all .2s}
230
+ .pub-plan.on{border-color:#6366f1;border-width:2px}
231
+ .pub-plan-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;margin-bottom:4px}
232
+ .pub-plan-url{font-size:14px;font-weight:700;color:#a78bfa;font-family:'JetBrains Mono',monospace;margin-bottom:4px}
233
+ .pub-plan-d{font-size:11px;color:#334155}
234
+ .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}
235
+ .google-btn:hover{background:#f8fafc;box-shadow:0 4px 16px rgba(0,0,0,.12)}
236
+
237
+ @media(max-width:768px){
238
+ .sb{display:none}
239
+ .mob-tabs{display:flex}
240
+ .main{flex-direction:column}
241
+ .main.show-chat .cp{display:flex;flex:1}
242
+ .main.show-chat .pv{display:none}
243
+ .main.show-preview .cp{display:none}
244
+ .main.show-preview .pv{display:flex;flex:1}
245
+ .cp{width:100%;border-right:none}
246
+ .pv{width:100%}
247
+ .tb-tool,.vp-grp,.tb-url{display:none!important}
248
+ }
249
+ </style>
250
+ </head>
251
+ <body>
252
+
253
+ <!-- TOOLBAR -->
254
+ <div class="tb">
255
+ <div class="tb-left">
256
+ <div class="tb-logo" onclick="goHome()"><div class="tb-lm">⚑</div><span class="tb-ln">BuildAI</span></div>
257
+ <div class="tb-proj"><span class="tb-pn" id="tb-pn">New Project</span><span style="font-size:8px;color:#334155">β–Ύ</span></div>
258
+ <div class="tb-sep"></div>
259
+ <div style="position:relative">
260
+ <button class="tb-ic" id="ver-btn" onclick="toggleVer(event)" style="width:auto;padding:0 8px;font-size:11px;gap:3px">πŸ• <span style="font-size:11px;font-weight:600">History</span></button>
261
+ <div class="ver-panel" id="ver-panel" style="display:none">
262
+ <div class="ver-hd"><span class="ver-hd-t">Version History</span><button class="ver-x" onclick="closeVer()">Γ—</button></div>
263
+ <div id="ver-list"><div class="ver-empty">No versions yet.</div></div>
264
+ </div>
265
+ </div>
266
+ <button class="tb-ic" onclick="undoVer()" title="Undo">↩</button>
267
+ </div>
268
+ <div class="tb-center">
269
+ <button class="tab-b on" id="tab-preview" onclick="switchTab('preview')">πŸ‘ Preview</button>
270
+ <button class="tab-b" id="tab-code" onclick="switchTab('code')">&lt;/&gt; Code</button>
271
+ <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>
272
+ </div>
273
+ <div class="tb-right">
274
+ <div class="vp-grp">
275
+ <button class="vp-b on" id="vp-d" onclick="setVP('d')" title="Desktop">πŸ–₯</button>
276
+ <button class="vp-b" id="vp-t" onclick="setVP('t')" title="Tablet">⬜</button>
277
+ <button class="vp-b" id="vp-m" onclick="setVP('m')" title="Mobile">πŸ“±</button>
278
+ </div>
279
+ <button class="tb-tool" onclick="openM('github')">πŸ™ GitHub</button>
280
+ <button class="tb-pub" id="pub-tb" disabled onclick="openM('publish')">Publish</button>
281
+ <div id="tb-user" style="position:relative">
282
+ <button class="tb-signin" onclick="openM('auth','signin')">Sign in</button>
283
+ </div>
284
+ </div>
285
+ </div>
286
+
287
+ <div class="mob-tabs">
288
+ <button class="mob-tab on" id="mob-chat" onclick="mobTab('chat')">πŸ’¬ Chat</button>
289
+ <button class="mob-tab" id="mob-preview" onclick="mobTab('preview')">πŸ‘ Preview</button>
290
+ <button class="mob-tab" id="mob-code" onclick="mobTab('code')">&lt;/&gt; Code</button>
291
+ </div>
292
+
293
+ <div class="main show-chat" id="main-area">
294
+ <!-- SIDEBAR -->
295
+ <div class="sb">
296
+ <div class="sb-hd"><span class="sb-lbl">Projects</span></div>
297
+ <button class="sb-new" onclick="newProj()">οΌ‹ New Project</button>
298
+ <div class="sb-list" id="sb-list">
299
+ <div class="sb-sec">Recent</div>
300
+ <div id="sb-empty" class="sb-empty">No projects yet.<br/>Build one to save.</div>
301
+ <div id="sb-auth-prompt" class="sb-signin-prompt" style="display:none">
302
+ <div class="sb-sp-t">Sign in with Google to save projects</div>
303
+ <button class="sb-sp-b" onclick="openM('auth','signin')">Sign in free</button>
304
+ </div>
305
+ </div>
306
+ <div class="sb-ft">
307
+ <div class="sb-user" id="sb-user-box" onclick="openM('auth','signin')">
308
+ <div class="sb-uav" id="sb-av">?</div>
309
+ <div style="overflow:hidden"><div class="sb-un" id="sb-nm">Guest mode</div><div class="sb-up" id="sb-plan">Tap to sign in</div></div>
310
+ </div>
311
+ <button class="sb-signout" id="sb-signout" onclick="doSignOut()">Sign out</button>
312
+ </div>
313
+ </div>
314
+
315
+ <!-- CHAT -->
316
+ <div class="cp" id="chat-panel">
317
+ <div class="c-stat">
318
+ <div class="r-dots">
319
+ <div class="r-dot" id="rd1">1</div>
320
+ <div class="r-dot" id="rd2">2</div>
321
+ <div class="r-dot" id="rd3">3</div>
322
+ </div>
323
+ <div class="c-stat-t" id="c-stat-t">Ready to build</div>
324
+ </div>
325
+ <div class="c-msgs" id="c-msgs">
326
+ <div class="c-empty" id="c-empty">
327
+ <div class="c-e-ic">πŸ—οΈ</div>
328
+ <div class="c-e-t">What are we building?</div>
329
+ <div class="c-e-s">5 AI models collaborate to build your website with React + Tailwind.</div>
330
+ <div class="q-prompts">
331
+ <button class="q-p" onclick="setBP('SaaS landing page with pricing, features, and testimonials')">πŸ’Ό SaaS landing page</button>
332
+ <button class="q-p" onclick="setBP('Premium e-commerce store with product grid and cart')">πŸ›’ E-commerce store</button>
333
+ <button class="q-p" onclick="setBP('Dark analytics dashboard with charts and KPI metrics')">πŸ“Š Analytics dashboard</button>
334
+ </div>
335
+ </div>
336
+ </div>
337
+ <div class="sugg-bar" id="sugg-bar" style="display:none">
338
+ <button class="s-chip" onclick="setBI(this.textContent)">Add Testimonials</button>
339
+ <button class="s-chip" onclick="setBI(this.textContent)">Add Pricing Table</button>
340
+ <button class="s-chip" onclick="setBI(this.textContent)">Add FAQ Section</button>
341
+ <button class="s-chip" onclick="setBI(this.textContent)">Make Dark Theme</button>
342
+ <button class="s-chip" onclick="setBI(this.textContent)">Add Animations</button>
343
+ <button class="s-chip" onclick="setBI(this.textContent)">Bento Grid Layout</button>
344
+ </div>
345
+ <div class="c-inp-area">
346
+ <div class="c-inp-row">
347
+ <textarea class="c-ta" id="c-inp" placeholder="Describe your website or request changes..." rows="1" onkeydown="chatKey(event)" oninput="updSend();autoR(this)"></textarea>
348
+ <button class="c-send" id="c-send" onclick="sendMsg()">↑</button>
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ <!-- PREVIEW -->
354
+ <div class="pv" id="preview-panel">
355
+ <div class="pub-bar" id="pub-bar" style="display:none">
356
+ <div class="pub-dot"></div><span class="pub-t">Published</span><span class="pub-dt" id="pub-dt"></span>
357
+ </div>
358
+ <div class="pv-body" id="pv-body">
359
+ <div class="pv-empty"><div class="pv-e-ic">🌐</div><div class="pv-e-t">Preview will appear here</div></div>
360
+ </div>
361
+ <div class="pv-btm" id="pv-btm" style="display:none">
362
+ <button class="dl-btn" onclick="dlFile()">⬇ Download HTML</button>
363
+ <button class="dl-zip" onclick="dlZip()">πŸ“¦ Download ZIP</button>
364
+ <span class="pv-hint">Refine via chat anytime.</span>
365
+ </div>
366
+ </div>
367
+ </div>
368
+
369
+ <!-- AUTH MODAL -->
370
+ <div class="modal-bg" id="modal-auth" style="display:none" onclick="if(event.target===this)closeM('auth')">
371
+ <div class="modal-box" style="max-width:390px">
372
+ <div class="mo-p">
373
+ <div class="mo-hd">
374
+ <div style="display:flex;align-items:center;gap:9px">
375
+ <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>
376
+ <span style="font-weight:800;font-size:17px">BuildAI</span>
377
+ </div>
378
+ <button class="mo-x" onclick="closeM('auth')">Γ—</button>
379
+ </div>
380
+ <div class="tab-sw">
381
+ <button class="tab-sw-b on" id="at-si" onclick="switchAuth('signin')">Sign in</button>
382
+ <button class="tab-sw-b" id="at-su" onclick="switchAuth('signup')">Create account</button>
383
+ </div>
384
+ <div class="auth-err" id="auth-err"></div>
385
+ <div class="auth-ok" id="auth-ok"></div>
386
+ <button class="google-btn" onclick="doGoogleAuth()">
387
+ <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>
388
+ Continue with Google
389
+ </button>
390
+ <div class="mo-div"><div class="mo-div-l"></div><div class="mo-div-t">or use email</div><div class="mo-div-l"></div></div>
391
+ <label class="flbl">Email</label>
392
+ <input class="finp" id="a-email" type="email" placeholder="you@example.com"/>
393
+ <label class="flbl">Password</label>
394
+ <input class="finp" id="a-pass" type="password" placeholder="At least 6 characters" onkeydown="if(event.key==='Enter')doAuth()"/>
395
+ <div id="name-wrap" style="display:none"><label class="flbl">Full name (optional)</label><input class="finp" id="a-name" placeholder="Your name"/></div>
396
+ <button class="mo-btn-p" id="a-sub" onclick="doAuth()">Sign in β†’</button>
397
+ <button class="mo-btn-s" onclick="guestMode();closeM('auth')">Continue as guest (no saving)</button>
398
+ <div style="font-size:11px;color:#1e2740;text-align:center;margin-top:14px" id="a-sw-area">
399
+ Don't have an account? <span onclick="switchAuth('signup')" style="color:#818cf8;cursor:pointer;font-weight:600">Sign up free</span>
400
+ </div>
401
+ </div>
402
+ </div>
403
+ </div>
404
+
405
+ <!-- GITHUB MODAL -->
406
+ <div class="modal-bg" id="modal-github" style="display:none" onclick="if(event.target===this)closeM('github')">
407
+ <div class="modal-box" style="max-width:440px">
408
+ <div class="mo-p">
409
+ <div class="mo-hd">
410
+ <div style="display:flex;align-items:center;gap:12px">
411
+ <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>
412
+ <div><div class="mo-t">Push to GitHub</div><div class="mo-sub">Sync your website to a repo</div></div>
413
+ </div>
414
+ <button class="mo-x" onclick="closeM('github')">Γ—</button>
415
+ </div>
416
+ <div id="gh-form">
417
+ <label class="flbl">GitHub Personal Access Token</label>
418
+ <input class="finp" id="gh-token" placeholder="ghp_xxxxxxxxxxxxxxxxxxxx" style="font-family:'JetBrains Mono',monospace;font-size:12px"/>
419
+ <label class="flbl">Repository name</label>
420
+ <input class="finp" id="gh-repo" placeholder="my-website"/>
421
+ <p style="font-size:11px;color:#1e2740;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>
422
+ <button class="mo-btn-p" style="background:#238636" id="gh-btn" onclick="doGithub()">Push to GitHub β†’</button>
423
+ </div>
424
+ <div id="gh-done" style="display:none">
425
+ <div class="suc-c"><span class="suc-ic">πŸŽ‰</span><div class="suc-t">Pushed!</div>
426
+ <div style="background:#090b12;border:1px solid #1a1f33;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>
427
+ <button class="mo-btn-p" onclick="closeM('github')">Done</button></div>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </div>
432
+
433
+ <!-- PUBLISH MODAL -->
434
+ <div class="modal-bg" id="modal-publish" style="display:none" onclick="if(event.target===this)closeM('publish')">
435
+ <div class="modal-box" style="max-width:420px">
436
+ <div class="mo-p">
437
+ <div id="pub-main">
438
+ <div class="mo-hd"><div class="mo-t">Publish Website</div><button class="mo-x" onclick="closeM('publish')">Γ—</button></div>
439
+ <p style="font-size:13px;color:#475569;margin-bottom:20px">Your site goes live instantly.</p>
440
+ <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>
441
+ <button class="mo-btn-p" style="background:linear-gradient(135deg,#059669,#10b981)" id="pub-go-btn" onclick="doPublish()">πŸš€ Publish now</button>
442
+ </div>
443
+ <div id="pub-done" style="display:none">
444
+ <div style="text-align:right"><button class="mo-x" onclick="closeM('publish')">Γ—</button></div>
445
+ <div class="suc-c"><span class="suc-ic">πŸŽ‰</span><div class="suc-t">Your site is live!</div>
446
+ <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>
447
+ <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>
448
+ </div>
449
+ </div>
450
+ </div>
451
+ </div>
452
+
453
+ <!-- ── ALL FUNCTIONS DEFINED FIRST β€” before Firebase loads ── -->
454
+ <script>
455
+ var API = window.location.origin;
456
+ var S = {user:null,generating:false,code:'',tab:'preview',vp:'d',prompt:'',projectName:'',projectId:null,versions:[],versionIdx:-1,authMode:'signin'};
457
+
458
+ // ── CORE UI FUNCTIONS (no Firebase dependency) ──────────────────
459
+
460
+ function goHome(){ window.location.href = '/'; }
461
+ function escH(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
462
+ function slugify(s){return(s||'project').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'').slice(0,28)||'my-project';}
463
+
464
+ function mobTab(w){
465
+ ['chat','preview','code'].forEach(function(t){var b=document.getElementById('mob-'+t);if(b)b.className='mob-tab'+(t===w?' on':'');});
466
+ var m=document.getElementById('main-area');
467
+ m.className='main '+(w==='chat'?'show-chat':'show-preview');
468
+ if(w!=='chat'){S.tab=w==='code'?'code':'preview';updTabs();if(S.code)renderPV();}
469
+ }
470
+ function updTabs(){['preview','code'].forEach(function(t){var b=document.getElementById('tab-'+t);if(b)b.className='tab-b'+(t===S.tab?' on':'');});}
471
+ function switchTab(t){S.tab=t;updTabs();if(S.code)renderPV();}
472
+ function setVP(v){S.vp=v;['d','t','m'].forEach(function(x){var b=document.getElementById('vp-'+x);if(b)b.className='vp-b'+(x===v?' on':'');});if(S.code&&S.tab==='preview')renderPV();}
473
+
474
+ function setPV(h){var el=document.getElementById('pv-body');if(el)el.innerHTML=h;}
475
+ function renderPV(){
476
+ if(!S.code)return;
477
+ if(S.tab==='preview'){
478
+ var pv=document.getElementById('pv-body');
479
+ var w=S.vp==='d'?'100%':S.vp==='t'?'768px':'390px';
480
+ var ws=S.vp!=='d'?'width:'+w+';height:100%;box-shadow:0 0 80px rgba(0,0,0,.8);border-radius:10px;overflow:hidden':'width:100%;height:100%';
481
+ pv.innerHTML='';
482
+ var wrap=document.createElement('div');wrap.setAttribute('style',ws);
483
+ var fr=document.createElement('iframe');fr.setAttribute('style','width:100%;height:100%;border:none;display:block');
484
+ fr.setAttribute('sandbox','allow-scripts allow-same-origin');
485
+ wrap.appendChild(fr);pv.appendChild(wrap);
486
+ var d=fr.contentDocument||fr.contentWindow.document;
487
+ d.open('text/html','replace');d.write(S.code);d.close();
488
+ }else{
489
+ setPV('<div class="code-view"><pre class="code-pre">'+escH(S.code)+'</pre></div>');
490
+ }
491
+ }
492
+
493
+ function setStat(msg,round,done){
494
+ var el=document.getElementById('c-stat-t');if(el)el.textContent=msg;
495
+ if(round<1)return;
496
+ for(var i=1;i<=3;i++){var d=document.getElementById('rd'+i);if(!d)continue;
497
+ if(i<round||(i===round&&done)){d.className='r-dot dn';d.textContent='βœ“';}
498
+ else if(i===round){d.className='r-dot act';d.textContent=i;}
499
+ else{d.className='r-dot';d.textContent=i;}
500
+ }
501
+ }
502
+
503
+ function showOvl(msg,round){
504
+ var pct=Math.min(((round-1)/3)*100,99)+'%';
505
+ var names=['Cerebras','Gemini','Groq'];
506
+ var badges='';
507
+ names.forEach(function(n,i){
508
+ var isDone=(round-1)>i,isAct=(round-1)===i;
509
+ badges+='<span class="ov-b'+(isAct?' act':'')+'">'+( isDone?'βœ“ ':'')+n+'</span>';
510
+ });
511
+ 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>');
512
+ }
513
+
514
+ function addVer(code,round,label){
515
+ S.versions.push({code:code,round:round,label:label,time:new Date().toLocaleTimeString()});
516
+ S.versionIdx=S.versions.length-1;renderVers();
517
+ }
518
+ function renderVers(){
519
+ var list=document.getElementById('ver-list');if(!list)return;
520
+ if(!S.versions.length){list.innerHTML='<div class="ver-empty">No versions yet.</div>';return;}
521
+ list.innerHTML='';
522
+ S.versions.slice().reverse().forEach(function(v,i){
523
+ var ri=S.versions.length-1-i,isCur=ri===S.versionIdx;
524
+ var el=document.createElement('div');el.className='ver-item'+(isCur?' cur':'');
525
+ 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>':'');
526
+ (function(idx){el.onclick=function(){S.code=S.versions[idx].code;S.versionIdx=idx;renderPV();renderVers();closeVer();};})(ri);
527
+ list.appendChild(el);
528
+ });
529
+ }
530
+ function undoVer(){if(S.versionIdx>0){S.versionIdx--;S.code=S.versions[S.versionIdx].code;renderPV();renderVers();}}
531
+ function toggleVer(e){if(e)e.stopPropagation();var p=document.getElementById('ver-panel');if(p)p.style.display=p.style.display==='none'?'block':'none';}
532
+ function closeVer(){var p=document.getElementById('ver-panel');if(p)p.style.display='none';}
533
+
534
+ function updSend(){
535
+ var inp=document.getElementById('c-inp'),btn=document.getElementById('c-send');
536
+ if(!inp||!btn)return;
537
+ var ok=inp.value.trim()&&!S.generating;
538
+ btn.className='c-send'+(ok?' on':'');btn.disabled=!ok;inp.disabled=S.generating;
539
+ }
540
+ function autoR(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,80)+'px';}
541
+ function chatKey(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}}
542
+ function addUMsg(t){var m=document.getElementById('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;}
543
+ function setBP(t){var el=document.getElementById('c-inp');if(el){el.value=t;updSend();autoR(el);setTimeout(sendMsg,60);}}
544
+ function setBI(t){var el=document.getElementById('c-inp');if(el){el.value=t;updSend();el.focus();}}
545
+
546
+ function sendMsg(){
547
+ var el=document.getElementById('c-inp'),t=el.value.trim();
548
+ if(!t||S.generating)return;
549
+ el.value='';updSend();autoR(el);
550
+ addUMsg(t);S.prompt=t;S.generating=true;S.code='';S.projectId=null;
551
+ var name=t.length>26?t.slice(0,26)+'...':t;S.projectName=name;
552
+ var pn=document.getElementById('tb-pn');if(pn)pn.textContent=name;
553
+ var ce=document.getElementById('c-empty');if(ce)ce.style.display='none';
554
+ ['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=document.getElementById(id);if(el)el.style.display='none';});
555
+ var pb=document.getElementById('pub-tb');if(pb)pb.disabled=true;
556
+ var tu=document.getElementById('tb-url');if(tu)tu.style.display='none';
557
+ 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>');
558
+ addThinking();doFetch(t);
559
+ }
560
+
561
+ var thinkEl=null;
562
+ function addThinking(){
563
+ rmThinking();var m=document.getElementById('c-msgs');
564
+ thinkEl=document.createElement('div');thinkEl.className='m-a';thinkEl.id='think-el';
565
+ 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>';
566
+ m.appendChild(thinkEl);m.scrollTop=m.scrollHeight;
567
+ }
568
+ function rmThinking(){var t=document.getElementById('think-el');if(t)t.remove();thinkEl=null;}
569
+
570
+ function buildErr(msg){
571
+ S.generating=false;rmThinking();setStat('Build failed',0,false);
572
+ var m=document.getElementById('c-msgs'),el=document.createElement('div');
573
+ el.className='m-a';el.innerHTML='<div class="m-a-lbl">BUILDAI</div><div class="m-err">⚠️ '+escH(msg)+'</div>';
574
+ m.appendChild(el);m.scrollTop=m.scrollHeight;updSend();
575
+ }
576
+
577
+ function doFetch(prompt){
578
+ fetch(API+'/api/build',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:prompt})})
579
+ .then(function(resp){
580
+ if(!resp.ok)throw new Error('Server returned '+resp.status+'. Is the Space awake?');
581
+ var reader=resp.body.getReader(),dec=new TextDecoder(),buf='';
582
+ function read(){return reader.read().then(function(r){
583
+ if(r.done){if(!S.code)buildErr('Build ended without result. Check API keys in HuggingFace Secrets.');return;}
584
+ buf+=dec.decode(r.value,{stream:true});
585
+ var lines=buf.split('\n');buf=lines.pop();
586
+ lines.forEach(function(line){if(line.startsWith('data: ')){try{handleEv(JSON.parse(line.slice(6)));}catch(e){}}});
587
+ return read();
588
+ });}return read();
589
+ }).catch(function(e){buildErr(e.message||'Cannot connect to backend.');});
590
+ }
591
+
592
+ function handleEv(d){
593
+ if(d.type==='status'){setStat(d.message,d.round,false);showOvl(d.message,d.round);}
594
+ else if(d.type==='code'){S.code=d.code;addVer(d.code,d.round,d.message);setStat(d.message,d.round,true);if(d.final)finish();}
595
+ else if(d.type==='error'){buildErr(d.message);}
596
+ }
597
+
598
+ async function finish(){
599
+ S.generating=false;rmThinking();
600
+ [1,2,3].forEach(function(i){var d=document.getElementById('rd'+i);if(d){d.className='r-dot dn';d.textContent='βœ“';}});
601
+ setStat('βœ… Website ready!',0,false);
602
+ var savedNote='';
603
+ 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>';}
604
+ var m=document.getElementById('c-msgs'),ai=document.createElement('div');ai.className='m-a';
605
+ 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>';
606
+ m.appendChild(ai);m.scrollTop=m.scrollHeight;
607
+ renderPV();
608
+ ['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=document.getElementById(id);if(el)el.style.display='flex';});
609
+ var pb=document.getElementById('pub-tb');if(pb)pb.disabled=false;
610
+ var tu=document.getElementById('tb-url');if(tu){tu.style.display='flex';var tt=document.getElementById('tb-url-t');if(tt)tt.textContent=slugify(S.projectName)+'.buildai.app';}
611
+ if(window.innerWidth<=768)setTimeout(function(){mobTab('preview');},500);
612
+ updSend();
613
+ }
614
+
615
+ function newProj(){
616
+ document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});
617
+ var pn=document.getElementById('tb-pn');if(pn)pn.textContent='New Project';
618
+ var cm=document.getElementById('c-msgs');
619
+ 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>';
620
+ S.code='';S.generating=false;S.versions=[];S.versionIdx=-1;S.projectId=null;
621
+ [1,2,3].forEach(function(i){var d=document.getElementById('rd'+i);if(d){d.className='r-dot';d.textContent=i;}});
622
+ setStat('Ready to build',0,false);
623
+ ['sugg-bar','pv-btm','pub-bar'].forEach(function(id){var el=document.getElementById(id);if(el)el.style.display='none';});
624
+ var pb=document.getElementById('pub-tb');if(pb)pb.disabled=true;
625
+ var tu=document.getElementById('tb-url');if(tu)tu.style.display='none';
626
+ renderVers();setPV('<div class="pv-empty"><div class="pv-e-ic">🌐</div><div class="pv-e-t">Preview will appear here</div></div>');
627
+ if(window.innerWidth<=768)mobTab('chat');
628
+ }
629
+
630
+ function clearSBProjects(){document.getElementById('sb-list').querySelectorAll('.pi').forEach(function(p){p.remove();});var se=document.getElementById('sb-empty');if(se)se.style.display='block';}
631
+ function addProjItem(name,id,makeCurrent,code){
632
+ var se=document.getElementById('sb-empty');if(se)se.style.display='none';
633
+ if(makeCurrent)document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});
634
+ var colors=['#6366f1','#8b5cf6','#10b981','#f59e0b','#ec4899'];
635
+ var c=colors[Math.floor(Math.random()*colors.length)];
636
+ var el=document.createElement('div');el.className='pi'+(makeCurrent?' on':'');
637
+ el.dataset.id=id||'';el.dataset.code=code||'';
638
+ el.innerHTML='<div class="pi-dot" style="background:'+c+'"></div><div><div class="pi-n">'+escH(name)+'</div><div class="pi-d">'+new Date().toLocaleDateString('en-US',{day:'numeric',month:'short'})+'</div></div>';
639
+ el.onclick=function(){
640
+ document.querySelectorAll('.pi').forEach(function(p){p.classList.remove('on');});el.classList.add('on');
641
+ var pn=document.getElementById('tb-pn');if(pn)pn.textContent=name;
642
+ if(el.dataset.code){S.code=el.dataset.code;S.projectName=name;S.projectId=el.dataset.id;renderPV();var pb=document.getElementById('pv-btm');if(pb)pb.style.display='flex';var ptb=document.getElementById('pub-tb');if(ptb)ptb.disabled=false;if(window.innerWidth<=768)mobTab('preview');}
643
+ };
644
+ var list=document.getElementById('sb-list'),first=list.querySelector('.pi');
645
+ if(first)list.insertBefore(el,first);else list.appendChild(el);
646
+ }
647
+
648
+ // ── DOWNLOAD FUNCTIONS ──────────────────────────────────────────
649
+ function dlFile(){
650
+ if(!S.code)return;
651
+ var blob=new Blob([S.code],{type:'text/html'}),url=URL.createObjectURL(blob);
652
+ var a=document.createElement('a');a.href=url;a.download=slugify(S.projectName)+'.html';a.click();URL.revokeObjectURL(url);
653
+ }
654
+ function dlZip(){
655
+ if(!S.code){return;}
656
+ if(typeof JSZip==='undefined'){alert('JSZip not loaded yet. Downloading HTML instead.');dlFile();return;}
657
+ try{
658
+ var zip=new JSZip(),code=S.code,slug=slugify(S.projectName);
659
+ var styleMatches=code.match(/<style[^>]*>([\s\S]*?)<\/style>/gi)||[];
660
+ var css='/* BuildAI Generated */\n\n',htmlNo=code;
661
+ 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,'');});
662
+ var babelMatches=code.match(/<script type="text\/babel"[^>]*>([\s\S]*?)<\/script>/gi)||[];
663
+ var js='/* BuildAI React App */\nconst { useState, useEffect, useRef, useCallback } = React;\n\n',htmlFinal=htmlNo;
664
+ babelMatches.forEach(function(m,i){js+=m.replace(/<script[^>]*>/i,'').replace(/<\/script>/i,'').replace(/const \{ useState.*? \} = React;/g,'').trim()+'\n';if(i===0)htmlFinal=htmlFinal.replace(m,'<script type="text/babel" src="app.js"></script>');else htmlFinal=htmlFinal.replace(m,'');});
665
+ zip.file('index.html',htmlFinal);zip.file('style.css',css);zip.file('app.js',js);
666
+ zip.file('README.md','# '+slug+'\nBuilt with BuildAI.\n\nOpen index.html in a browser or deploy to Netlify/Vercel/GitHub Pages.\nNo build step required.');
667
+ zip.generateAsync({type:'blob'}).then(function(blob){
668
+ var url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=slug+'.zip';a.click();URL.revokeObjectURL(url);
669
+ });
670
+ }catch(e){console.error(e);dlFile();}
671
+ }
672
+
673
+ // ── MODAL FUNCTIONS ────────────────────────────────────────────��─
674
+ function openM(name,extra){
675
+ if(name==='auth'){S.authMode=extra||'signin';switchAuth(S.authMode);}
676
+ if(name==='publish'){var pd=document.getElementById('pub-subdomain-display');if(pd)pd.textContent=slugify(S.projectName)+'.buildai.app';}
677
+ var el=document.getElementById('modal-'+name);if(el)el.style.display='flex';
678
+ }
679
+ function closeM(name){
680
+ var el=document.getElementById('modal-'+name);if(el)el.style.display='none';
681
+ if(name==='publish'){var pm=document.getElementById('pub-main'),pd=document.getElementById('pub-done'),pb=document.getElementById('pub-go-btn');if(pm)pm.style.display='block';if(pd)pd.style.display='none';if(pb){pb.textContent='πŸš€ Publish now';pb.disabled=false;}}
682
+ if(name==='github'){var gf=document.getElementById('gh-form'),gd=document.getElementById('gh-done'),gb=document.getElementById('gh-btn');if(gf)gf.style.display='block';if(gd)gd.style.display='none';if(gb){gb.textContent='Push to GitHub β†’';gb.disabled=false;}}
683
+ }
684
+ function switchAuth(mode){
685
+ S.authMode=mode;
686
+ var si=document.getElementById('at-si'),su=document.getElementById('at-su'),nw=document.getElementById('name-wrap'),sub=document.getElementById('a-sub'),sw=document.getElementById('a-sw-area');
687
+ if(si)si.className='tab-sw-b'+(mode==='signin'?' on':'');
688
+ if(su)su.className='tab-sw-b'+(mode==='signup'?' on':'');
689
+ if(nw)nw.style.display=mode==='signup'?'block':'none';
690
+ if(sub)sub.textContent=mode==='signin'?'Sign in β†’':'Create account β†’';
691
+ 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>";
692
+ var ae=document.getElementById('auth-err'),ao=document.getElementById('auth-ok');
693
+ if(ae)ae.style.display='none';if(ao)ao.style.display='none';
694
+ }
695
+ function showAuthErr(msg){var el=document.getElementById('auth-err');if(el){el.textContent='⚠️ '+msg;el.style.display='block';}}
696
+
697
+ // ── PUBLISH & GITHUB ─────────────────────────────────────────────
698
+ async function doPublish(){
699
+ if(!S.code){alert('Build a website first.');return;}
700
+ var slug=slugify(S.projectName),btn=document.getElementById('pub-go-btn');
701
+ if(btn){btn.textContent='Publishing...';btn.disabled=true;}
702
+ try{
703
+ 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})});
704
+ var d=await r.json();
705
+ var pm=document.getElementById('pub-main'),pd=document.getElementById('pub-done');
706
+ if(pm)pm.style.display='none';if(pd)pd.style.display='block';
707
+ var url=d.url||'https://'+slug+'.buildai.app';
708
+ var lu=document.getElementById('pub-live-url'),ob=document.getElementById('pub-open-btn');
709
+ if(lu)lu.textContent=url;if(ob)ob.onclick=function(){window.open(url,'_blank');};
710
+ var pb=document.getElementById('pub-bar'),pdt=document.getElementById('pub-dt');
711
+ if(pb)pb.style.display='flex';if(pdt)pdt.textContent=new Date().toLocaleTimeString();
712
+ }catch(e){if(btn){btn.textContent='πŸš€ Publish now';btn.disabled=false;}}
713
+ }
714
+ async function doGithub(){
715
+ var token=document.getElementById('gh-token').value.trim(),repo=document.getElementById('gh-repo').value.trim();
716
+ if(!token||!repo){alert('Enter GitHub token and repo name.');return;}
717
+ if(!S.code){alert('Build a website first.');return;}
718
+ var btn=document.getElementById('gh-btn');if(btn){btn.textContent='Pushing...';btn.disabled=true;}
719
+ try{
720
+ 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})});
721
+ var d=await r.json();
722
+ if(d.success){var gf=document.getElementById('gh-form'),gd=document.getElementById('gh-done'),gu=document.getElementById('gh-url-out');if(gf)gf.style.display='none';if(gd)gd.style.display='block';if(gu)gu.textContent=d.repo_url;}
723
+ else{alert('Failed: '+(d.detail||'Unknown'));if(btn){btn.textContent='Push to GitHub β†’';btn.disabled=false;}}
724
+ }catch(e){alert('Error: '+e.message);if(btn){btn.textContent='Push to GitHub β†’';btn.disabled=false;}}
725
+ }
726
+
727
+ // ── AUTH PLACEHOLDER (Firebase fills these in below) ─────────────
728
+ function guestMode(){
729
+ S.user={id:'guest-'+Date.now(),email:'guest',name:'Guest',isGuest:true};
730
+ var sn=document.getElementById('sb-nm'),sp=document.getElementById('sb-plan'),sa=document.getElementById('sb-av'),su=document.getElementById('sb-user-box'),tu=document.getElementById('tb-user');
731
+ if(sn)sn.textContent='Guest mode';if(sp)sp.textContent='Tap to sign in';if(sa)sa.textContent='?';
732
+ if(su)su.onclick=function(){openM('auth','signin');};
733
+ if(tu)tu.innerHTML='<button class="tb-guest" onclick="openM(\'auth\',\'signin\')">πŸ‘€ Sign in</button>';
734
+ var sap=document.getElementById('sb-auth-prompt');if(sap)sap.style.display='block';
735
+ }
736
+ function doSignOut(){if(typeof fbAuth!=='undefined'){fbAuth.signOut().catch(function(e){console.error(e);});}else{window.location.reload();}}
737
+ function toggleUD(){var d=document.getElementById('user-dd');if(d)d.style.display=d.style.display==='none'?'block':'none';}
738
+ function doGoogleAuth(){
739
+ if(typeof fbAuth==='undefined'){alert('Auth not loaded yet. Please wait a moment.');return;}
740
+ var provider=new firebase.auth.GoogleAuthProvider();
741
+ provider.setCustomParameters({prompt:'select_account'});
742
+ var isMobile=/Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
743
+ if(isMobile){fbAuth.signInWithRedirect(provider).catch(function(e){showAuthErr(e.message);});}
744
+ 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.');}});}
745
+ }
746
+ async function doAuth(){
747
+ if(typeof fbAuth==='undefined'){alert('Auth not loaded yet. Please wait.');return;}
748
+ var email=document.getElementById('a-email').value.trim(),pass=document.getElementById('a-pass').value;
749
+ var ae=document.getElementById('auth-err'),ao=document.getElementById('auth-ok');
750
+ if(ae)ae.style.display='none';if(ao)ao.style.display='none';
751
+ if(!email||!pass){showAuthErr('Enter email and password.');return;}
752
+ if(pass.length<6){showAuthErr('Password must be at least 6 characters.');return;}
753
+ var btn=document.getElementById('a-sub');if(btn){btn.disabled=true;btn.textContent='Please wait...';}
754
+ try{
755
+ if(S.authMode==='signup'){await fbAuth.createUserWithEmailAndPassword(email,pass);}
756
+ else{await fbAuth.signInWithEmailAndPassword(email,pass);}
757
+ closeM('auth');
758
+ }catch(e){
759
+ 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.'};
760
+ showAuthErr(msgs[e.code]||e.message||'Something went wrong.');
761
+ }
762
+ if(btn){btn.disabled=false;btn.textContent=S.authMode==='signin'?'Sign in β†’':'Create account β†’';}
763
+ }
764
+ async function loadProjects(){
765
+ if(!S.user||S.user.isGuest||typeof fbDb==='undefined')return;
766
+ 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=document.getElementById('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);}
767
+ }
768
+ async function autoSave(){
769
+ if(!S.user||S.user.isGuest||!S.code||typeof fbDb==='undefined')return;
770
+ 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);}
771
+ }
772
+ function onSignedIn(user){
773
+ S.user={id:user.uid,email:user.email,name:user.displayName||user.email.split('@')[0],isGuest:false};
774
+ var init=S.user.name[0].toUpperCase();
775
+ var tu=document.getElementById('tb-user');
776
+ 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>';
777
+ var sa=document.getElementById('sb-av'),sn=document.getElementById('sb-nm'),sp=document.getElementById('sb-plan'),so=document.getElementById('sb-signout'),su=document.getElementById('sb-user-box'),sap=document.getElementById('sb-auth-prompt');
778
+ 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';
779
+ loadProjects();
780
+ }
781
+ function onSignedOut(){
782
+ S.user=null;
783
+ var tu=document.getElementById('tb-user');if(tu)tu.innerHTML='<button class="tb-signin" onclick="openM(\'auth\',\'signin\')">Sign in</button>';
784
+ var sa=document.getElementById('sb-av'),sn=document.getElementById('sb-nm'),sp=document.getElementById('sb-plan'),so=document.getElementById('sb-signout'),su=document.getElementById('sb-user-box'),sap=document.getElementById('sb-auth-prompt');
785
+ if(sa)sa.textContent='?';if(sn)sn.textContent='Guest mode';if(sp)sp.textContent='Tap to sign in';if(so)so.style.display='none';
786
+ if(su)su.onclick=function(){openM('auth','signin');};if(sap)sap.style.display='block';
787
+ clearSBProjects();
788
+ }
789
+
790
+ // Close dropdowns on outside click
791
+ document.addEventListener('click',function(e){
792
+ var vp=document.getElementById('ver-panel'),vb=document.getElementById('ver-btn');
793
+ if(vp&&vp.style.display!=='none'&&!vp.contains(e.target)&&vb&&!vb.contains(e.target))vp.style.display='none';
794
+ var dd=document.getElementById('user-dd');
795
+ if(dd&&dd.style.display!=='none'&&!dd.parentElement.contains(e.target))dd.style.display='none';
796
+ });
797
+
798
+ // Check for prompt passed from landing page
799
+ (function(){
800
+ try{
801
+ var saved=sessionStorage.getItem('bai_prompt');
802
+ if(saved){sessionStorage.removeItem('bai_prompt');setTimeout(function(){setBP(saved);},400);}
803
+ else{
804
+ var params=new URLSearchParams(window.location.search);
805
+ var qp=params.get('prompt');
806
+ if(qp)setTimeout(function(){setBP(qp);},400);
807
+ }
808
+ }catch(e){}
809
+ })();
810
+ </script>
811
+
812
+ <!-- ── Firebase loads AFTER all functions are defined ── -->
813
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"></script>
814
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-auth-compat.js"></script>
815
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore-compat.js"></script>
816
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
817
+ <script>
818
+ // Firebase init β€” runs after all functions are defined above
819
+ try{
820
+ firebase.initializeApp({
821
+ apiKey:"AIzaSyDGtOYRxO2N6qPDNwkuZmwkhQelmJ9VQkQ",
822
+ authDomain:"buildai-b1d98.firebaseapp.com",
823
+ projectId:"buildai-b1d98",
824
+ storageBucket:"buildai-b1d98.firebasestorage.app",
825
+ messagingSenderId:"511431818463",
826
+ appId:"1:511431818463:web:846a142888fc3517353d6a"
827
+ });
828
+ var fbAuth=firebase.auth();
829
+ var fbDb=firebase.firestore();
830
+ fbAuth.onAuthStateChanged(function(user){if(user)onSignedIn(user);else onSignedOut();});
831
+ 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);});
832
+ }catch(e){
833
+ console.warn('Firebase failed to load:',e.message);
834
+ // Buttons still work β€” user can use in guest mode
835
+ var tu=document.getElementById('tb-user');
836
+ if(tu)tu.innerHTML='<button class="tb-guest" onclick="guestMode()">Use as Guest</button>';
837
+ }
838
+ </script>
839
+ </body>
840
+ </html>
index.html ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
6
+ <title>BuildAI β€” Build Websites with AI in Seconds</title>
7
+ <meta name="description" content="Describe any website and 5 AI models build it live. React + Tailwind. Powered by Cerebras, Gemini, Groq."/>
8
+ <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>"/>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
10
+ <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"/>
11
+ <style>
12
+ *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
13
+ html{scroll-behavior:smooth}
14
+ body{font-family:'Inter',system-ui,sans-serif;background:#07080f;color:#e2e8f0;-webkit-font-smoothing:antialiased;overflow-x:hidden}
15
+ button,a{font-family:inherit;cursor:pointer;text-decoration:none;color:inherit}
16
+
17
+ @keyframes fadeUp{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:translateY(0)}}
18
+ @keyframes gradFlow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
19
+ @keyframes orb1{0%,100%{transform:translate(0,0)}50%{transform:translate(40px,-30px)}}
20
+ @keyframes orb2{0%,100%{transform:translate(0,0)}50%{transform:translate(-30px,20px)}}
21
+ @keyframes orb3{0%,100%{transform:translate(0,0)}50%{transform:translate(20px,30px)}}
22
+ @keyframes shimmer{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
23
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
24
+
25
+ /* NAV */
26
+ .nav{position:fixed;top:0;left:0;right:0;z-index:100;display:flex;align-items:center;justify-content:space-between;padding:0 40px;height:62px;background:rgba(7,8,15,.88);backdrop-filter:blur(24px);border-bottom:1px solid rgba(255,255,255,.05)}
27
+ .nav-logo{display:flex;align-items:center;gap:10px}
28
+ .nav-icon{width:32px;height:32px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:16px}
29
+ .nav-name{font-weight:800;font-size:17px;letter-spacing:-.5px}
30
+ .nav-v{padding:2px 9px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:999px;font-size:10px;color:#a78bfa;font-weight:700}
31
+ .nav-links{display:flex;gap:28px}
32
+ .nav-link{font-size:14px;color:#64748b;font-weight:500;transition:color .2s}
33
+ .nav-link:hover{color:#e2e8f0}
34
+ .nav-btns{display:flex;gap:10px}
35
+ .btn-g{padding:9px 20px;background:transparent;border:1px solid rgba(255,255,255,.1);border-radius:10px;color:#94a3b8;font-size:13.5px;font-weight:500;transition:all .2s}
36
+ .btn-g:hover{border-color:rgba(99,102,241,.5);color:#e2e8f0;background:rgba(99,102,241,.06)}
37
+ .btn-g:active{transform:scale(.97)}
38
+ .btn-p{position:relative;overflow:hidden;padding:10px 22px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:10px;color:#fff;font-size:13.5px;font-weight:700;transition:transform .15s,box-shadow .15s;box-shadow:0 4px 16px rgba(99,102,241,.3)}
39
+ .btn-p:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(99,102,241,.45)}
40
+ .btn-p:active{transform:scale(.97)}
41
+ .btn-p::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transform:translateX(-100%);transition:transform .5s}
42
+ .btn-p:hover::after{transform:translateX(100%)}
43
+
44
+ /* HERO */
45
+ .hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:100px 24px 80px;position:relative;overflow:hidden}
46
+ .hero-bg{position:absolute;inset:0;pointer-events:none}
47
+ .orb{position:absolute;border-radius:50%;filter:blur(100px)}
48
+ .o1{width:700px;height:700px;top:-15%;left:-10%;background:radial-gradient(ellipse,rgba(99,102,241,.2) 0%,transparent 70%);animation:orb1 16s ease-in-out infinite}
49
+ .o2{width:600px;height:600px;bottom:-10%;right:-5%;background:radial-gradient(ellipse,rgba(139,92,246,.15) 0%,transparent 70%);animation:orb2 20s ease-in-out infinite}
50
+ .o3{width:500px;height:500px;top:40%;left:30%;background:radial-gradient(ellipse,rgba(236,72,153,.09) 0%,transparent 70%);animation:orb3 24s ease-in-out infinite}
51
+ .hero-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(99,102,241,.033) 1px,transparent 1px),linear-gradient(90deg,rgba(99,102,241,.033) 1px,transparent 1px);background-size:56px 56px;mask-image:radial-gradient(ellipse at center,black 20%,transparent 70%)}
52
+ .hero-inner{position:relative;z-index:2;max-width:780px;width:100%}
53
+ .hero-pill{display:inline-flex;align-items:center;gap:8px;padding:5px 16px 5px 8px;background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.22);border-radius:999px;font-size:12px;color:#a78bfa;margin-bottom:32px;animation:fadeUp .5s ease both}
54
+ .pill-tag{padding:2px 9px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:999px;color:#fff;font-size:10px;font-weight:700}
55
+ .h1{font-size:clamp(38px,7vw,88px);font-weight:900;line-height:1.01;letter-spacing:-3.5px;margin-bottom:24px;animation:fadeUp .6s .05s ease both}
56
+ .h1 .pl{color:#f1f5f9}
57
+ .h1 .gr{background:linear-gradient(135deg,#6366f1 0%,#a78bfa 40%,#ec4899 100%);background-size:200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gradFlow 5s ease infinite}
58
+ .hero-sub{font-size:18px;color:#4b5563;max-width:540px;margin:0 auto 48px;line-height:1.8;animation:fadeUp .7s .1s ease both}
59
+
60
+ /* PROMPT */
61
+ .prompt-wrap{max-width:700px;margin:0 auto;animation:fadeUp .8s .15s ease both}
62
+ .pbox{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.04);border:1.5px solid rgba(255,255,255,.1);border-radius:20px;padding:7px 7px 7px 22px;margin-bottom:18px;backdrop-filter:blur(20px);box-shadow:0 20px 60px rgba(0,0,0,.4);transition:border-color .25s,box-shadow .25s}
63
+ .pbox:focus-within{border-color:rgba(99,102,241,.45);box-shadow:0 20px 60px rgba(0,0,0,.4),0 0 0 3px rgba(99,102,241,.1)}
64
+ .pinp{flex:1;background:transparent;border:none;outline:none;color:#e2e8f0;font-size:15.5px;padding:12px 0;font-family:'Inter',sans-serif}
65
+ .pinp::placeholder{color:#252e45}
66
+ .pbtn{position:relative;overflow:hidden;padding:13px 26px;border:none;border-radius:14px;color:#fff;font-size:14.5px;font-weight:700;flex-shrink:0;transition:all .2s;background:linear-gradient(135deg,#6366f1,#8b5cf6);box-shadow:0 4px 20px rgba(99,102,241,.4)}
67
+ .pbtn:hover{transform:translateY(-1px);box-shadow:0 8px 30px rgba(99,102,241,.5)}
68
+ .pbtn:active{transform:scale(.96)}
69
+ .pbtn.off{background:#1a1f33;color:#334155;box-shadow:none;pointer-events:none}
70
+ .pbtn::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transform:translateX(-100%);transition:transform .6s}
71
+ .pbtn:hover:not(.off)::after{transform:translateX(100%)}
72
+ .chips{display:flex;flex-wrap:wrap;gap:9px;justify-content:center}
73
+ .chip{padding:8px 16px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:999px;font-size:12.5px;color:#64748b;transition:all .2s;font-weight:500}
74
+ .chip:hover{border-color:rgba(99,102,241,.4);color:#a5b4fc;background:rgba(99,102,241,.07);transform:translateY(-1px)}
75
+ .chip:active{transform:scale(.97)}
76
+
77
+ /* PROOF */
78
+ .proof-row{display:flex;align-items:center;justify-content:center;gap:20px;margin-top:44px;flex-wrap:wrap;animation:fadeUp .9s .2s ease both}
79
+ .avs{display:flex}
80
+ .av{width:32px;height:32px;border-radius:50%;border:2px solid #07080f;margin-left:-9px;font-size:14px;display:flex;align-items:center;justify-content:center;background:#1a1f33}
81
+ .av:first-child{margin-left:0}
82
+ .pt{font-size:13px;color:#334155;font-weight:500}
83
+ .pt strong{color:#64748b}
84
+ .pdiv{width:1px;height:16px;background:#1e2740}
85
+ .stars{color:#f59e0b;letter-spacing:2px;font-size:12px}
86
+
87
+ /* API BADGES */
88
+ .api-row{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;padding:48px 24px 20px;max-width:700px;margin:0 auto}
89
+ .ab{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);border-radius:9px;font-size:12px;color:#475569;font-weight:500;transition:all .2s}
90
+ .ab:hover{border-color:rgba(99,102,241,.2);color:#818cf8}
91
+ .adot{width:7px;height:7px;border-radius:50%;background:#10b981;box-shadow:0 0 8px #10b981;animation:pulse 2.5s ease infinite}
92
+
93
+ /* FEATURES */
94
+ .sec{padding:88px 24px;max-width:1080px;margin:0 auto}
95
+ .eyebrow{font-size:11px;font-weight:700;color:#6366f1;letter-spacing:3px;text-transform:uppercase;text-align:center;margin-bottom:14px}
96
+ .sec-h{font-size:clamp(26px,4vw,40px);font-weight:900;text-align:center;letter-spacing:-1.2px;margin-bottom:12px;color:#f1f5f9}
97
+ .sec-p{font-size:15px;color:#475569;text-align:center;max-width:480px;margin:0 auto 52px;line-height:1.8}
98
+ .fgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px}
99
+ .fc{background:rgba(255,255,255,.025);border:1px solid rgba(255,255,255,.06);border-radius:18px;padding:26px;transition:all .3s}
100
+ .fc:hover{border-color:rgba(99,102,241,.3);transform:translateY(-4px);background:rgba(99,102,241,.04)}
101
+ .fi{font-size:28px;margin-bottom:16px;display:block}
102
+ .ft{font-size:14.5px;font-weight:700;color:#c4d4f0;margin-bottom:8px}
103
+ .fd{font-size:12.5px;color:#475569;line-height:1.75}
104
+
105
+ /* STEPS */
106
+ .steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:28px}
107
+ .step{text-align:center;padding:32px 24px}
108
+ .snum{width:52px;height:52px;border-radius:999px;background:linear-gradient(135deg,rgba(99,102,241,.2),rgba(139,92,246,.2));border:1px solid rgba(99,102,241,.3);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:900;color:#a78bfa;margin:0 auto 20px;font-family:'JetBrains Mono',monospace}
109
+ .st{font-size:16px;font-weight:700;color:#f1f5f9;margin-bottom:8px}
110
+ .sd{font-size:13px;color:#475569;line-height:1.75}
111
+
112
+ /* TESTIMONIALS */
113
+ .tgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px}
114
+ .tc{background:rgba(255,255,255,.025);border:1px solid rgba(255,255,255,.06);border-radius:18px;padding:24px;transition:all .25s}
115
+ .tc:hover{border-color:rgba(99,102,241,.2);transform:translateY(-2px)}
116
+ .tc-stars{display:flex;gap:3px;margin-bottom:14px}
117
+ .tc-star{color:#f59e0b;font-size:13px}
118
+ .tc-q{font-size:13.5px;color:#94a3b8;line-height:1.75;margin-bottom:18px;font-style:italic}
119
+ .tc-a{display:flex;align-items:center;gap:12px}
120
+ .tc-av{width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
121
+ .tc-n{font-size:13px;font-weight:700;color:#f1f5f9}
122
+ .tc-r{font-size:11px;color:#475569;margin-top:2px}
123
+
124
+ /* CTA BANNER */
125
+ .cta-wrap{max-width:1080px;margin:0 auto;padding:0 24px 80px}
126
+ .cta-box{padding:64px 40px;background:linear-gradient(135deg,rgba(99,102,241,.12),rgba(139,92,246,.08));border:1px solid rgba(99,102,241,.2);border-radius:24px;text-align:center;position:relative;overflow:hidden}
127
+ .cta-box::before{content:'';position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at center,rgba(99,102,241,.07) 0%,transparent 60%);pointer-events:none}
128
+ .cta-h{font-size:clamp(26px,4vw,42px);font-weight:900;letter-spacing:-1.5px;margin-bottom:14px;color:#f1f5f9;position:relative}
129
+ .cta-p{font-size:16px;color:#475569;margin-bottom:34px;position:relative}
130
+ .cta-btn{position:relative;overflow:hidden;padding:17px 48px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:16px;color:#fff;font-size:16px;font-weight:800;transition:transform .15s,box-shadow .2s;box-shadow:0 10px 40px rgba(99,102,241,.4)}
131
+ .cta-btn:hover{transform:translateY(-2px);box-shadow:0 18px 50px rgba(99,102,241,.5)}
132
+ .cta-btn:active{transform:scale(.97)}
133
+ .cta-btn::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transform:translateX(-100%);transition:transform .6s}
134
+ .cta-btn:hover::after{transform:translateX(100%)}
135
+ .cta-note{font-size:12px;color:#334155;margin-top:14px;position:relative}
136
+
137
+ /* FOOTER */
138
+ .footer{border-top:1px solid rgba(255,255,255,.05);padding:32px 40px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;max-width:1080px;margin:0 auto}
139
+ .f-logo{display:flex;align-items:center;gap:8px;font-weight:700;font-size:14px}
140
+ .f-links{display:flex;gap:24px}
141
+ .f-link{font-size:13px;color:#334155;transition:color .2s}
142
+ .f-link:hover{color:#818cf8}
143
+ .f-copy{font-size:12px;color:#1e2740}
144
+
145
+ @media(max-width:768px){
146
+ .nav{padding:0 18px}
147
+ .nav-links{display:none}
148
+ .hero{padding:90px 18px 60px}
149
+ .h1{letter-spacing:-2px}
150
+ .pbox{padding:6px 6px 6px 16px}
151
+ .pinp{font-size:14px}
152
+ .pbtn{padding:11px 18px;font-size:13px}
153
+ .sec{padding:56px 18px}
154
+ .cta-box{padding:44px 22px}
155
+ .footer{padding:28px 18px;flex-direction:column;align-items:flex-start}
156
+ }
157
+ </style>
158
+ </head>
159
+ <body>
160
+
161
+ <nav class="nav">
162
+ <div class="nav-logo">
163
+ <div class="nav-icon">⚑</div>
164
+ <span class="nav-name">BuildAI</span>
165
+ <span class="nav-v">v7</span>
166
+ </div>
167
+ <div class="nav-links">
168
+ <a class="nav-link" href="#features">Features</a>
169
+ <a class="nav-link" href="#how">How it works</a>
170
+ <a class="nav-link" href="#reviews">Reviews</a>
171
+ </div>
172
+ <div class="nav-btns">
173
+ <button class="btn-g" onclick="goBuilder()">Sign in</button>
174
+ <button class="btn-p" onclick="goBuilder()">Get started free</button>
175
+ </div>
176
+ </nav>
177
+
178
+ <section class="hero">
179
+ <div class="hero-bg">
180
+ <div class="orb o1"></div>
181
+ <div class="orb o2"></div>
182
+ <div class="orb o3"></div>
183
+ <div class="hero-grid"></div>
184
+ </div>
185
+ <div class="hero-inner">
186
+ <div class="hero-pill">
187
+ <span class="pill-tag">NEW</span>
188
+ Powered by Cerebras Β· Gemini Β· Groq
189
+ </div>
190
+ <h1 class="h1">
191
+ <span class="pl">Build websites</span><br>
192
+ <span class="gr">with AI in seconds</span>
193
+ </h1>
194
+ <p class="hero-sub">Describe any website. 5 AI models collaborate in 3 rounds to generate professional React + Tailwind code β€” live, in front of you.</p>
195
+
196
+ <div class="prompt-wrap">
197
+ <div class="pbox">
198
+ <input
199
+ class="pinp"
200
+ id="hero-inp"
201
+ type="text"
202
+ placeholder="Describe your website… e.g. 'SaaS landing page for a project management tool'"
203
+ autocomplete="off"
204
+ oninput="syncBtn()"
205
+ onkeydown="if(event.key==='Enter' && !document.getElementById('hero-btn').classList.contains('off')) launch()"
206
+ />
207
+ <button class="pbtn off" id="hero-btn" onclick="launch()">Build it β†’</button>
208
+ </div>
209
+ <div class="chips">
210
+ <button class="chip" onclick="pick('Premium sneaker e-commerce store with product grid, cart and checkout')">πŸ›’ E-commerce</button>
211
+ <button class="chip" onclick="pick('SaaS landing page with pricing, features and testimonials')">πŸ’Ό SaaS</button>
212
+ <button class="chip" onclick="pick('Italian restaurant with menu, gallery and reservation form')">πŸ• Restaurant</button>
213
+ <button class="chip" onclick="pick('UI/UX designer portfolio with case studies')">🎨 Portfolio</button>
214
+ <button class="chip" onclick="pick('Dark analytics dashboard with charts and KPI cards')">πŸ“Š Dashboard</button>
215
+ </div>
216
+ </div>
217
+
218
+ <div class="proof-row">
219
+ <div class="avs">
220
+ <div class="av">😊</div>
221
+ <div class="av">😎</div>
222
+ <div class="av">🀩</div>
223
+ <div class="av">🎯</div>
224
+ </div>
225
+ <span class="pt"><strong>4,200+</strong> websites built</span>
226
+ <div class="pdiv"></div>
227
+ <span class="stars">β˜…β˜…β˜…β˜…β˜…</span>
228
+ <span class="pt"><strong>4.9</strong>/5 rating</span>
229
+ </div>
230
+ </div>
231
+ </section>
232
+
233
+ <div style="text-align:center">
234
+ <div class="api-row">
235
+ <div class="ab"><div class="adot"></div>Cerebras Β· 1,000 tok/s</div>
236
+ <div class="ab"><div class="adot"></div>Gemini Flash</div>
237
+ <div class="ab"><div class="adot"></div>Groq LPU</div>
238
+ <div class="ab"><div class="adot"></div>OpenRouter</div>
239
+ <div class="ab"><div class="adot"></div>Mistral AI</div>
240
+ </div>
241
+ </div>
242
+
243
+ <section class="sec" id="features">
244
+ <div class="eyebrow">Why BuildAI</div>
245
+ <h2 class="sec-h">Everything you need</h2>
246
+ <p class="sec-p">No subscriptions. No tools to install. Every feature works out of the box, completely free.</p>
247
+ <div class="fgrid">
248
+ <div class="fc"><span class="fi">⚑</span><div class="ft">Cerebras Ultra-Fast</div><div class="fd">Cerebras runs at 1,000+ tokens/sec. Your website draft appears in seconds, not minutes.</div></div>
249
+ <div class="fc"><span class="fi">🧠</span><div class="ft">5-Model Pipeline</div><div class="fd">Cerebras builds β†’ Gemini reviews design β†’ Groq polishes. Three rounds, dramatically better output every time.</div></div>
250
+ <div class="fc"><span class="fi">βš›οΈ</span><div class="ft">React + Tailwind</div><div class="fd">Every website uses real React components with hooks, Tailwind CDN, and premium Google Fonts.</div></div>
251
+ <div class="fc"><span class="fi">πŸ—ΊοΈ</span><div class="ft">Smart Templates</div><div class="fd">AI automatically picks the best template β€” landing, ecommerce, restaurant, portfolio, or dashboard.</div></div>
252
+ <div class="fc"><span class="fi">πŸ“Έ</span><div class="ft">Real Photos</div><div class="fd">Every website gets keyword-matched real photos. No stock photo subscription ever needed.</div></div>
253
+ <div class="fc"><span class="fi">πŸ“¦</span><div class="ft">Download as ZIP</div><div class="fd">Download your website as a ZIP β€” index.html, style.css, app.js, README. Ready to deploy anywhere.</div></div>
254
+ </div>
255
+ </section>
256
+
257
+ <section style="padding:0 24px 88px;max-width:1080px;margin:0 auto" id="how">
258
+ <div class="eyebrow">The Process</div>
259
+ <h2 class="sec-h">How it works</h2>
260
+ <p class="sec-p">Three AI models, three rounds, one stunning website. About 30 seconds total.</p>
261
+ <div class="steps">
262
+ <div class="step"><div class="snum">01</div><div class="st">You describe it</div><div class="sd">Type what you want or click a template chip. Be as detailed or brief as you like.</div></div>
263
+ <div class="step"><div class="snum">02</div><div class="st">3 AIs collaborate</div><div class="sd">Cerebras builds at 1,000+ tok/s. Gemini Flash elevates the design. Groq polishes every detail.</div></div>
264
+ <div class="step"><div class="snum">03</div><div class="st">Download or publish</div><div class="sd">Preview in desktop/tablet/mobile. Refine via chat. Download ZIP or publish live in one click.</div></div>
265
+ </div>
266
+ </section>
267
+
268
+ <section class="sec" id="reviews">
269
+ <div class="eyebrow">Reviews</div>
270
+ <h2 class="sec-h">Loved by builders worldwide</h2>
271
+ <p class="sec-p">Developers, designers, and entrepreneurs use BuildAI every day.</p>
272
+ <div class="tgrid">
273
+ <div class="tc">
274
+ <div class="tc-stars"><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span></div>
275
+ <p class="tc-q">"Built a complete restaurant website in under a minute. The design quality blew me away β€” looked like a $5,000 agency site."</p>
276
+ <div class="tc-a"><div class="tc-av">πŸ‘©β€πŸ³</div><div><div class="tc-n">Priya Sharma</div><div class="tc-r">Restaurant Owner, Mumbai</div></div></div>
277
+ </div>
278
+ <div class="tc">
279
+ <div class="tc-stars"><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span></div>
280
+ <p class="tc-q">"The 3-round pipeline actually works. By Round 3 it looks like Stripe's design team made it. Genuinely impressed."</p>
281
+ <div class="tc-a"><div class="tc-av">πŸ‘¨β€πŸ’»</div><div><div class="tc-n">Marcus Chen</div><div class="tc-r">Full-Stack Developer, Singapore</div></div></div>
282
+ </div>
283
+ <div class="tc">
284
+ <div class="tc-stars"><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span><span class="tc-star">β˜…</span></div>
285
+ <p class="tc-q">"Finally an AI builder that doesn't give me the same purple gradient every time. Cerebras + Gemini = unbeatable combo."</p>
286
+ <div class="tc-a"><div class="tc-av">🎨</div><div><div class="tc-n">Fatima Al-Rashid</div><div class="tc-r">UI/UX Designer, Dubai</div></div></div>
287
+ </div>
288
+ </div>
289
+ </section>
290
+
291
+ <div class="cta-wrap">
292
+ <div class="cta-box">
293
+ <h2 class="cta-h">Start building for free</h2>
294
+ <p class="cta-p">No credit card. No account required to try. Describe your website and watch AI build it live.</p>
295
+ <button class="cta-btn" onclick="goBuilder()">Launch the Builder β†’</button>
296
+ <div class="cta-note">Free forever Β· No credit card Β· React + Tailwind output</div>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="footer">
301
+ <div class="f-logo"><div class="nav-icon" style="width:26px;height:26px;font-size:13px">⚑</div>BuildAI</div>
302
+ <div class="f-links">
303
+ <a class="f-link" href="#features">Features</a>
304
+ <a class="f-link" href="#how">How it works</a>
305
+ <a class="f-link" href="/builder">Builder</a>
306
+ </div>
307
+ <span class="f-copy">Β© 2025 BuildAI Β· Powered by Cerebras, Gemini &amp; Groq</span>
308
+ </div>
309
+
310
+ <script>
311
+ // ── ZERO Firebase dependencies here. All buttons guaranteed to work. ──
312
+
313
+ function syncBtn() {
314
+ var inp = document.getElementById('hero-inp');
315
+ var btn = document.getElementById('hero-btn');
316
+ if (!inp || !btn) return;
317
+ var ok = inp.value.trim().length > 0;
318
+ if (ok) {
319
+ btn.classList.remove('off');
320
+ } else {
321
+ btn.classList.add('off');
322
+ }
323
+ }
324
+
325
+ function pick(text) {
326
+ var inp = document.getElementById('hero-inp');
327
+ if (!inp) return;
328
+ inp.value = text;
329
+ inp.focus();
330
+ syncBtn();
331
+ }
332
+
333
+ function goBuilder() {
334
+ window.location.href = '/builder';
335
+ }
336
+
337
+ function launch() {
338
+ var inp = document.getElementById('hero-inp');
339
+ var text = inp ? inp.value.trim() : '';
340
+ if (!text) return;
341
+ try { sessionStorage.setItem('bai_prompt', text); } catch(e) {}
342
+ window.location.href = '/builder';
343
+ }
344
+
345
+ // Scroll reveal
346
+ (function() {
347
+ var targets = document.querySelectorAll('.fc, .step, .tc');
348
+ if (!window.IntersectionObserver) return;
349
+ targets.forEach(function(el, i) {
350
+ el.style.cssText += 'opacity:0;transform:translateY(30px);transition:opacity .6s ease,transform .6s ease;transition-delay:' + (i % 3) * 0.1 + 's';
351
+ });
352
+ var io = new IntersectionObserver(function(entries) {
353
+ entries.forEach(function(e) {
354
+ if (e.isIntersecting) {
355
+ e.target.style.opacity = '1';
356
+ e.target.style.transform = 'translateY(0)';
357
+ io.unobserve(e.target);
358
+ }
359
+ });
360
+ }, { threshold: 0.1 });
361
+ targets.forEach(function(el) { io.observe(el); });
362
+ })();
363
+ </script>
364
+ </body>
365
+ </html>
main.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BuildAI - FastAPI Backend v3.2
3
+ Routes:
4
+ GET / β†’ index.html (landing page)
5
+ GET /builder β†’ builder.html (the actual AI builder app)
6
+ POST /api/build β†’ SSE streaming AI build
7
+ POST /api/publish
8
+ POST /api/github/sync
9
+ GET /health
10
+ """
11
+
12
+ import os
13
+ import json
14
+ import base64
15
+ import httpx
16
+ from datetime import datetime, timezone
17
+ from typing import Optional
18
+
19
+ from fastapi import FastAPI, HTTPException
20
+ from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
21
+ from fastapi.middleware.cors import CORSMiddleware
22
+ from pydantic import BaseModel
23
+ from dotenv import load_dotenv
24
+
25
+ from ai_engine import run_pipeline
26
+
27
+ load_dotenv()
28
+
29
+ app = FastAPI(title="BuildAI API", version="3.2.0")
30
+
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+
40
+ # ── Request models ────────────────────────────────────────────────
41
+ class BuildRequest(BaseModel):
42
+ prompt: str
43
+
44
+ class PublishRequest(BaseModel):
45
+ project_id: str
46
+ html_code: str
47
+ subdomain: Optional[str] = None
48
+
49
+ class GithubSyncRequest(BaseModel):
50
+ project_id: str
51
+ html_code: str
52
+ repo_name: str
53
+ github_token: str
54
+
55
+
56
+ # ── Helper to read HTML files ─────────────────────────────────────
57
+ def read_html(filename: str) -> str:
58
+ paths = [
59
+ filename,
60
+ f"/home/user/app/{filename}",
61
+ f"./{filename}",
62
+ ]
63
+ for path in paths:
64
+ try:
65
+ with open(path, "r", encoding="utf-8") as f:
66
+ return f.read()
67
+ except FileNotFoundError:
68
+ continue
69
+ return f"<h1>{filename} not found</h1><p>Make sure the file is uploaded to your HuggingFace Space.</p>"
70
+
71
+
72
+ # ── Health check ──────────────────────────────────────────────────
73
+ @app.get("/health")
74
+ async def health():
75
+ return {
76
+ "status": "ok",
77
+ "version": "3.2.0",
78
+ "auth": "firebase",
79
+ "pipeline": "Cerebras β†’ Gemini β†’ Groq β†’ OpenRouter β†’ Mistral",
80
+ "keys": {
81
+ "cerebras": bool(os.environ.get("CEREBRAS_API_KEY")),
82
+ "gemini": bool(os.environ.get("GEMINI_API_KEY")),
83
+ "groq": bool(os.environ.get("GROQ_API_KEY")),
84
+ "openrouter": bool(os.environ.get("OPENROUTER_API_KEY")),
85
+ "mistral": bool(os.environ.get("MISTRAL_API_KEY")),
86
+ "cloudflare": bool(os.environ.get("CLOUDFLARE_API_TOKEN")),
87
+ }
88
+ }
89
+
90
+
91
+ # ── Landing page ──────────────────────────────────────────────────
92
+ @app.get("/", response_class=HTMLResponse)
93
+ async def serve_home():
94
+ return HTMLResponse(content=read_html("index.html"))
95
+
96
+
97
+ # ── Builder app ───────────────────────────────────────────────────
98
+ @app.get("/builder", response_class=HTMLResponse)
99
+ async def serve_builder():
100
+ return HTMLResponse(content=read_html("builder.html"))
101
+
102
+
103
+ # ── Build website (SSE stream) ────────────────────────────────────
104
+ @app.post("/api/build")
105
+ async def build_website(req: BuildRequest):
106
+ if not req.prompt or len(req.prompt.strip()) < 5:
107
+ raise HTTPException(status_code=400, detail="Prompt is too short.")
108
+ if len(req.prompt) > 2000:
109
+ raise HTTPException(status_code=400, detail="Prompt too long (max 2000 chars).")
110
+ return StreamingResponse(
111
+ run_pipeline(req.prompt.strip()),
112
+ media_type="text/event-stream",
113
+ headers={
114
+ "Cache-Control": "no-cache",
115
+ "X-Accel-Buffering": "no",
116
+ "Connection": "keep-alive",
117
+ }
118
+ )
119
+
120
+
121
+ # ── Publish to Cloudflare Pages ───────────────────────────────────
122
+ @app.post("/api/publish")
123
+ async def publish_website(req: PublishRequest):
124
+ slug = req.subdomain or f"project-{req.project_id[:8]}"
125
+ slug = "".join(c for c in slug.lower() if c.isalnum() or c == "-")[:28]
126
+
127
+ cf_token = os.environ.get("CLOUDFLARE_API_TOKEN")
128
+ cf_account = os.environ.get("CLOUDFLARE_ACCOUNT_ID")
129
+
130
+ if cf_token and cf_account:
131
+ try:
132
+ url = await _cloudflare_deploy(slug, req.html_code, cf_token, cf_account)
133
+ return {"success": True, "url": url}
134
+ except Exception as e:
135
+ print(f"[BuildAI] Cloudflare error: {e}")
136
+
137
+ # Fallback β€” return a fake URL (works without Cloudflare keys)
138
+ return {
139
+ "success": True,
140
+ "url": f"https://{slug}.buildai.app",
141
+ "note": "Add CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID for real deployment"
142
+ }
143
+
144
+
145
+ async def _cloudflare_deploy(slug: str, html: str, token: str, account: str) -> str:
146
+ headers = {"Authorization": f"Bearer {token}"}
147
+ async with httpx.AsyncClient(timeout=60.0) as client:
148
+ # Create project if not exists
149
+ check = await client.get(
150
+ f"https://api.cloudflare.com/client/v4/accounts/{account}/pages/projects/{slug}",
151
+ headers=headers
152
+ )
153
+ if check.status_code == 404:
154
+ await client.post(
155
+ f"https://api.cloudflare.com/client/v4/accounts/{account}/pages/projects",
156
+ headers={**headers, "Content-Type": "application/json"},
157
+ json={"name": slug, "production_branch": "main"}
158
+ )
159
+
160
+ # Upload file
161
+ boundary = "BuildAIBoundary"
162
+ body = (
163
+ f"--{boundary}\r\n"
164
+ f'Content-Disposition: form-data; name="files"; filename="index.html"\r\n'
165
+ f"Content-Type: text/html\r\n\r\n"
166
+ ).encode() + html.encode() + f"\r\n--{boundary}--\r\n".encode()
167
+
168
+ resp = await client.post(
169
+ f"https://api.cloudflare.com/client/v4/accounts/{account}/pages/projects/{slug}/deployments",
170
+ headers={**headers, "Content-Type": f"multipart/form-data; boundary={boundary}"},
171
+ content=body
172
+ )
173
+ if resp.is_success:
174
+ return f"https://{slug}.pages.dev"
175
+ raise ValueError(f"Deploy failed: {resp.text[:200]}")
176
+
177
+
178
+ # ── GitHub sync ───────────────────────────────────────────────────
179
+ @app.post("/api/github/sync")
180
+ async def github_sync(req: GithubSyncRequest):
181
+ headers = {
182
+ "Authorization": f"Bearer {req.github_token}",
183
+ "Accept": "application/vnd.github.v3+json",
184
+ "X-GitHub-Api-Version": "2022-11-28",
185
+ }
186
+ async with httpx.AsyncClient(timeout=30.0) as client:
187
+ # Verify token
188
+ user_resp = await client.get("https://api.github.com/user", headers=headers)
189
+ if user_resp.status_code != 200:
190
+ raise HTTPException(status_code=401, detail="Invalid GitHub token.")
191
+ username = user_resp.json()["login"]
192
+ repo_full = f"{username}/{req.repo_name}"
193
+
194
+ # Create repo if not exists
195
+ repo_resp = await client.get(
196
+ f"https://api.github.com/repos/{repo_full}", headers=headers
197
+ )
198
+ if repo_resp.status_code == 404:
199
+ await client.post(
200
+ "https://api.github.com/user/repos",
201
+ json={"name": req.repo_name, "private": False, "auto_init": True},
202
+ headers=headers,
203
+ )
204
+
205
+ # Get existing SHA (for update)
206
+ file_resp = await client.get(
207
+ f"https://api.github.com/repos/{repo_full}/contents/index.html",
208
+ headers=headers,
209
+ )
210
+ sha = file_resp.json().get("sha") if file_resp.status_code == 200 else None
211
+
212
+ # Push file
213
+ body: dict = {
214
+ "message": f"Update via BuildAI v7 β€” {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')} UTC",
215
+ "content": base64.b64encode(req.html_code.encode()).decode(),
216
+ }
217
+ if sha:
218
+ body["sha"] = sha
219
+
220
+ push = await client.put(
221
+ f"https://api.github.com/repos/{repo_full}/contents/index.html",
222
+ json=body,
223
+ headers=headers,
224
+ )
225
+ if push.status_code in (200, 201):
226
+ return {
227
+ "success": True,
228
+ "repo_url": f"https://github.com/{repo_full}",
229
+ "pages_url": f"https://{username}.github.io/{req.repo_name}",
230
+ }
231
+ raise HTTPException(status_code=500, detail="GitHub push failed.")
232
+
233
+
234
+ # ── Catch-all (builder handles its own routing) ───────────────────
235
+ @app.get("/{full_path:path}", response_class=HTMLResponse)
236
+ async def catch_all(full_path: str):
237
+ if full_path.startswith("api/") or full_path == "health":
238
+ raise HTTPException(status_code=404)
239
+ # Any unknown path β†’ landing page
240
+ return HTMLResponse(content=read_html("index.html"))
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.115.0
2
+ uvicorn[standard]==0.30.6
3
+ httpx==0.27.2
4
+ python-dotenv==1.0.1
5
+ pydantic==2.8.2
6
+ python-multipart==0.0.9