Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- Dockerfile +17 -0
- ai_engine.py +993 -0
- builder.html +840 -0
- index.html +365 -0
- main.py +240 -0
- 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')"></> 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')"></> 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
| 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 & 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
|