faizee07's picture
Create templates/index.html
2d8b8ba verified
Raw
History Blame Contribute Delete
52.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SiteForge — AI Website Builder</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
display: ['Space Grotesk', 'sans-serif'],
body: ['DM Sans', 'sans-serif'],
},
colors: {
bg: '#0c0c0f',
surface: '#16161a',
card: '#1e1e24',
border: '#2a2a32',
fg: '#ececef',
muted: '#72727e',
accent: '#e8a838',
accentHover: '#f0b84d',
success: '#3dd68c',
danger: '#f05f5f',
}
}
}
}
</script>
<style>
:root {
--bg: #0c0c0f;
--surface: #16161a;
--card: #1e1e24;
--border: #2a2a32;
--fg: #ececef;
--muted: #72727e;
--accent: #e8a838;
--accent-hover: #f0b84d;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; background: var(--bg); color: var(--fg); font-family: 'DM Sans', sans-serif; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }
/* Background noise texture */
.noise-bg::before {
content: '';
position: fixed;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 0;
}
/* Glow orb */
.glow-orb {
position: fixed;
width: 500px;
height: 500px;
border-radius: 50%;
filter: blur(120px);
opacity: 0.08;
pointer-events: none;
z-index: 0;
}
.glow-orb-1 { background: var(--accent); top: -150px; left: -100px; }
.glow-orb-2 { background: #6366f1; bottom: -200px; right: -100px; opacity: 0.05; }
/* Textarea */
.prompt-input {
background: var(--card);
border: 1.5px solid var(--border);
color: var(--fg);
resize: none;
transition: border-color 0.25s, box-shadow 0.25s;
font-family: 'DM Sans', sans-serif;
}
.prompt-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(232,168,56,0.12);
}
.prompt-input::placeholder { color: var(--muted); }
/* Generate button */
.btn-generate {
background: var(--accent);
color: #0c0c0f;
font-weight: 600;
font-family: 'Space Grotesk', sans-serif;
transition: all 0.25s;
position: relative;
overflow: hidden;
}
.btn-generate:hover:not(:disabled) {
background: var(--accent-hover);
transform: translateY(-1px);
box-shadow: 0 6px 24px rgba(232,168,56,0.25);
}
.btn-generate:active:not(:disabled) { transform: translateY(0); }
.btn-generate:disabled { opacity: 0.5; cursor: not-allowed; }
/* Spinner */
.spinner {
width: 18px; height: 18px;
border: 2.5px solid rgba(12,12,15,0.2);
border-top-color: #0c0c0f;
border-radius: 50%;
animation: spin 0.7s linear infinite;
display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Preview iframe */
.preview-frame {
background: #fff;
border: none;
border-radius: 8px;
width: 100%;
height: 100%;
}
/* Action buttons */
.action-btn {
background: var(--card);
border: 1px solid var(--border);
color: var(--fg);
font-family: 'DM Sans', sans-serif;
transition: all 0.2s;
cursor: pointer;
}
.action-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(232,168,56,0.06);
}
/* Suggestion chips */
.chip {
background: var(--card);
border: 1px solid var(--border);
color: var(--muted);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.chip:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(232,168,56,0.06);
}
/* Toast */
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%) translateY(80px);
background: var(--card);
border: 1px solid var(--border);
color: var(--fg);
padding: 12px 24px;
border-radius: 10px;
font-size: 14px;
z-index: 9999;
opacity: 0;
transition: all 0.35s cubic-bezier(0.4,0,0.2,1);
pointer-events: none;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* Status indicator */
.status-dot {
width: 8px; height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-dot.idle { background: var(--muted); }
.status-dot.generating { background: var(--accent); animation: pulse-dot 1s ease infinite; }
.status-dot.done { background: #3dd68c; }
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Typing animation for status */
.typing-text::after {
content: '';
animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
0% { content: ''; }
25% { content: '.'; }
50% { content: '..'; }
75% { content: '...'; }
}
/* Fade in */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in { animation: fadeInUp 0.5s ease forwards; }
/* Panel transition */
.panel-left {
transition: width 0.3s cubic-bezier(0.4,0,0.2,1);
}
/* Responsive */
@media (max-width: 768px) {
.main-layout { flex-direction: column !important; }
.panel-left { width: 100% !important; height: 45% !important; border-right: none !important; border-bottom: 1px solid var(--border) !important; }
.panel-right { height: 55% !important; }
}
</style>
</head>
<body class="noise-bg">
<!-- Background orbs -->
<div class="glow-orb glow-orb-1"></div>
<div class="glow-orb glow-orb-2"></div>
<!-- Main layout -->
<div class="main-layout flex h-screen relative z-10">
<!-- Left panel: Controls -->
<aside class="panel-left flex flex-col" style="width: 420px; min-width: 320px; border-right: 1px solid var(--border); background: rgba(22,22,26,0.6); backdrop-filter: blur(12px);">
<!-- Header -->
<header class="px-6 pt-6 pb-4">
<div class="flex items-center gap-3 mb-1">
<div class="w-9 h-9 rounded-lg flex items-center justify-center" style="background: var(--accent);">
<i class="fa-solid fa-bolt text-sm" style="color: #0c0c0f;"></i>
</div>
<h1 class="font-display text-xl font-bold tracking-tight" style="color: var(--fg);">SiteForge</h1>
</div>
<p class="text-sm mt-2" style="color: var(--muted);">Describe your website in a sentence. The AI generates a complete, styled, responsive page.</p>
</header>
<!-- Prompt area -->
<div class="px-6 flex-1 flex flex-col min-h-0">
<label class="text-xs font-medium uppercase tracking-wider mb-2" style="color: var(--muted);">Your Prompt</label>
<textarea
id="promptInput"
class="prompt-input rounded-xl px-4 py-3 text-sm leading-relaxed flex-1 min-h-0"
placeholder="e.g. A portfolio site for a landscape photographer with a dark moody theme, gallery grid, and contact form..."
rows="4"
></textarea>
<!-- Suggestion chips -->
<div class="flex flex-wrap gap-2 mt-3 mb-4">
<button class="chip rounded-full px-3 py-1.5" onclick="setPrompt('A modern SaaS landing page for an AI writing tool with pricing table and feature cards')">SaaS Landing</button>
<button class="chip rounded-full px-3 py-1.5" onclick="setPrompt('A personal portfolio for a UI/UX designer with project showcase and dark theme')">Portfolio</button>
<button class="chip rounded-full px-3 py-1.5" onclick="setPrompt('A restaurant website with menu section, reservation form, and warm elegant colors')">Restaurant</button>
<button class="chip rounded-full px-3 py-1.5" onclick="setPrompt('A tech startup landing page with hero, features, testimonials, and CTA section')">Startup</button>
<button class="chip rounded-full px-3 py-1.5" onclick="setPrompt('A fitness gym website with class schedule, trainer profiles, and bold energetic design')">Gym</button>
</div>
<!-- Generate button -->
<button id="generateBtn" class="btn-generate rounded-xl px-6 py-3.5 text-sm w-full flex items-center justify-center gap-2" onclick="generate()">
<i class="fa-solid fa-wand-magic-sparkles text-xs"></i>
<span id="btnText">Generate Website</span>
</button>
<!-- Status -->
<div id="statusBar" class="flex items-center gap-2 mt-3 mb-2" style="min-height: 24px;">
<span id="statusDot" class="status-dot idle"></span>
<span id="statusText" class="text-xs" style="color: var(--muted);">Ready</span>
</div>
<!-- Actions -->
<div id="actionBar" class="flex gap-2 mt-auto pb-5 opacity-0 transition-opacity duration-300" style="pointer-events: none;">
<button class="action-btn rounded-lg px-4 py-2.5 text-xs flex items-center gap-2 flex-1" onclick="downloadHTML()">
<i class="fa-solid fa-download text-xs"></i> Download HTML
</button>
<button class="action-btn rounded-lg px-4 py-2.5 text-xs flex items-center gap-2 flex-1" onclick="copyHTML()">
<i class="fa-solid fa-copy text-xs"></i> Copy Code
</button>
<button class="action-btn rounded-lg px-4 py-2.5 text-xs flex items-center gap-2 flex-1" onclick="viewSource()">
<i class="fa-solid fa-code text-xs"></i> View Source
</button>
</div>
</div>
</aside>
<!-- Right panel: Preview -->
<main class="panel-right flex-1 flex flex-col min-h-0" style="background: var(--bg);">
<!-- Preview toolbar -->
<div class="flex items-center justify-between px-5 py-3" style="border-bottom: 1px solid var(--border); background: rgba(22,22,26,0.4);">
<div class="flex items-center gap-3">
<div class="flex gap-1.5">
<span class="w-3 h-3 rounded-full" style="background: #f05f5f;"></span>
<span class="w-3 h-3 rounded-full" style="background: #e8a838;"></span>
<span class="w-3 h-3 rounded-full" style="background: #3dd68c;"></span>
</div>
<span class="text-xs font-medium" style="color: var(--muted);">Preview</span>
</div>
<div class="flex items-center gap-2">
<button id="desktopBtn" class="action-btn rounded-md px-2.5 py-1 text-xs" onclick="setViewport('desktop')" title="Desktop">
<i class="fa-solid fa-desktop"></i>
</button>
<button id="tabletBtn" class="action-btn rounded-md px-2.5 py-1 text-xs" onclick="setViewport('tablet')" title="Tablet">
<i class="fa-solid fa-tablet-screen-button"></i>
</button>
<button id="mobileBtn" class="action-btn rounded-md px-2.5 py-1 text-xs" onclick="setViewport('mobile')" title="Mobile">
<i class="fa-solid fa-mobile-screen-button"></i>
</button>
</div>
</div>
<!-- Preview area -->
<div id="previewContainer" class="flex-1 flex items-start justify-center p-6 overflow-auto" style="background: #1a1a1f;">
<!-- Empty state -->
<div id="emptyState" class="flex flex-col items-center justify-center h-full text-center fade-in">
<div class="w-20 h-20 rounded-2xl flex items-center justify-center mb-6" style="background: var(--card); border: 1px solid var(--border);">
<i class="fa-solid fa-layer-group text-2xl" style="color: var(--muted);"></i>
</div>
<p class="font-display text-lg font-semibold mb-2" style="color: var(--fg);">No website yet</p>
<p class="text-sm max-w-xs" style="color: var(--muted);">Enter a prompt on the left and click Generate to create your website.</p>
</div>
<!-- Iframe (hidden initially) -->
<iframe id="previewFrame" class="preview-frame" style="display: none; transition: max-width 0.3s;" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</main>
</div>
<!-- Source modal -->
<div id="sourceModal" class="fixed inset-0 z-50 flex items-center justify-center" style="display: none; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px);" onclick="closeSourceModal(event)">
<div class="rounded-2xl p-6 w-full max-w-3xl max-h-[80vh] flex flex-col" style="background: var(--surface); border: 1px solid var(--border);" onclick="event.stopPropagation()">
<div class="flex items-center justify-between mb-4">
<h2 class="font-display text-lg font-bold">Generated HTML Source</h2>
<button class="action-btn rounded-lg w-9 h-9 flex items-center justify-center" onclick="document.getElementById('sourceModal').style.display='none'">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<pre id="sourceCode" class="flex-1 overflow-auto rounded-xl p-4 text-xs leading-relaxed" style="background: var(--bg); border: 1px solid var(--border); color: #c8c8d0; tab-size: 2; font-family: 'Courier New', monospace;"></pre>
<button class="action-btn rounded-lg px-4 py-2.5 text-xs mt-4 flex items-center gap-2 self-start" onclick="copyHTML()">
<i class="fa-solid fa-copy"></i> Copy to Clipboard
</button>
</div>
</div>
<!-- Toast -->
<div id="toast" class="toast"></div>
<script>
// ============================================================
// Global state
// ============================================================
let generatedHTML = '';
let currentViewport = 'desktop';
const promptInput = document.getElementById('promptInput');
const generateBtn = document.getElementById('generateBtn');
const btnText = document.getElementById('btnText');
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
const actionBar = document.getElementById('actionBar');
const emptyState = document.getElementById('emptyState');
const previewFrame = document.getElementById('previewFrame');
const toast = document.getElementById('toast');
// ============================================================
// Prompt chip helper
// ============================================================
function setPrompt(text) {
promptInput.value = text;
promptInput.focus();
}
// ============================================================
// Toast notification
// ============================================================
function showToast(message, duration = 2500) {
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), duration);
}
// ============================================================
// Viewport switcher
// ============================================================
function setViewport(mode) {
currentViewport = mode;
const widths = { desktop: '100%', tablet: '768px', mobile: '375px' };
previewFrame.style.maxWidth = widths[mode];
// Highlight active button
['desktopBtn', 'tabletBtn', 'mobileBtn'].forEach(id => {
const btn = document.getElementById(id);
const isActive = id === mode + 'Btn';
btn.style.borderColor = isActive ? 'var(--accent)' : 'var(--border)';
btn.style.color = isActive ? 'var(--accent)' : 'var(--fg)';
});
}
// ============================================================
// The core generation engine — client-side template builder
// ============================================================
function generateWebsite(prompt) {
const p = prompt.toLowerCase();
// ---- Detect theme ----
let dark = true;
if (p.includes('light') || p.includes('bright') || p.includes('clean white') || p.includes('minimal white')) dark = false;
if (p.includes('dark') || p.includes('moody') || p.includes('midnight')) dark = true;
// ---- Detect accent color ----
let accent = '#e8a838'; // default amber
if (p.includes('blue') || p.includes('ocean') || p.includes('sky')) accent = '#3b9cf5';
if (p.includes('green') || p.includes('nature') || p.includes('eco') || p.includes('plant')) accent = '#34d399';
if (p.includes('red') || p.includes('fire') || p.includes('bold') || p.includes('energy')) accent = '#f05f5f';
if (p.includes('purple') || p.includes('luxury') || p.includes('premium') || p.includes('elegant')) accent = '#a78bfa';
if (p.includes('pink') || p.includes('beauty') || p.includes('fashion')) accent = '#f472b6';
if (p.includes('orange') || p.includes('warm') || p.includes('restaurant') || p.includes('food') || p.includes('cafe')) accent = '#f59e42';
if (p.includes('teal') || p.includes('cyan') || p.includes('tech')) accent = '#2dd4bf';
if (p.includes('indie') || p.includes('retro') || p.includes('vintage')) accent = '#d4a574';
// ---- Detect font style ----
let fontFamily = "'Segoe UI', system-ui, -apple-system, sans-serif";
let headingFont = fontFamily;
if (p.includes('serif') || p.includes('elegant') || p.includes('luxury') || p.includes('magazine')) {
fontFamily = "Georgia, 'Times New Roman', serif";
headingFont = fontFamily;
}
if (p.includes('modern') || p.includes('tech') || p.includes('startup') || p.includes('saas')) {
fontFamily = "'Inter', 'Segoe UI', system-ui, sans-serif";
headingFont = "'Inter', 'Segoe UI', system-ui, sans-serif";
}
// ---- Background colors ----
const bg = dark ? '#0f0f13' : '#fafafa';
const bgCard = dark ? '#1a1a22' : '#ffffff';
const bgCardHover = dark ? '#22222c' : '#f5f5f5';
const textPrimary = dark ? '#ececef' : '#1a1a2e';
const textSecondary = dark ? '#8888a0' : '#6b7280';
const borderColor = dark ? '#2a2a35' : '#e5e7eb';
const subtleBg = dark ? '#141418' : '#f3f4f6';
// ---- Detect sections ----
const hasHero = true;
const hasFeatures = p.includes('feature') || p.includes('service') || p.includes('what we') || p.includes('offer') || !p.includes('simple');
const hasPricing = p.includes('pric') || p.includes('plan') || p.includes('subscription');
const hasTestimonials = p.includes('testimonial') || p.includes('review') || p.includes('feedback');
const hasGallery = p.includes('gallery') || p.includes('portfolio') || p.includes('project') || p.includes('showcase') || p.includes('photo');
const hasContact = p.includes('contact') || p.includes('form') || p.includes('reach') || p.includes('email');
const hasTeam = p.includes('team') || p.includes('trainer') || p.includes('people') || p.includes('our team');
const hasMenu = p.includes('menu') || p.includes('dish') || p.includes('food');
const hasSchedule = p.includes('schedule') || p.includes('class') || p.includes('timetable');
const hasStats = p.includes('stat') || p.includes('number') || p.includes('counter') || p.includes('achievement');
const hasFAQ = p.includes('faq') || p.includes('question') || p.includes('common');
// ---- Extract a name/brand from prompt ----
let brand = 'BrandName';
const brandPatterns = [
/(?:called|named)\s+"?([^",.]+)"?/i,
/(?:for|of)\s+([A-Z][a-zA-Z]+?)(?:\s+with|\s+that|\s+and|\s+featuring|\s+including|,|$)/,
];
for (const pat of brandPatterns) {
const m = prompt.match(pat);
if (m && m[1].length > 2 && m[1].length < 30) { brand = m[1].trim(); break; }
}
// ---- Generate tagline from prompt ----
let tagline = prompt.charAt(0).toUpperCase() + prompt.slice(1);
if (tagline.length > 80) tagline = tagline.substring(0, 77) + '...';
// ---- CTA text ----
let ctaPrimary = 'Get Started';
let ctaSecondary = 'Learn More';
if (p.includes('restaurant') || p.includes('cafe') || p.includes('food')) { ctaPrimary = 'Reserve a Table'; ctaSecondary = 'View Menu'; }
if (p.includes('portfolio') || p.includes('photographer') || p.includes('designer')) { ctaPrimary = 'View Work'; ctaSecondary = 'Contact Me'; }
if (p.includes('gym') || p.includes('fitness')) { ctaPrimary = 'Join Now'; ctaSecondary = 'See Classes'; }
if (p.includes('saas') || p.includes('app') || p.includes('tool')) { ctaPrimary = 'Start Free Trial'; ctaSecondary = 'See Pricing'; }
if (p.includes('blog')) { ctaPrimary = 'Read Articles'; ctaSecondary = 'Subscribe'; }
// ---- Build sections HTML ----
let sections = '';
// -- NAV --
const navLinks = [];
if (hasFeatures) navLinks.push('Features');
if (hasPricing) navLinks.push('Pricing');
if (hasGallery) navLinks.push('Gallery');
if (hasMenu) navLinks.push('Menu');
if (hasSchedule) navLinks.push('Schedule');
if (hasTeam) navLinks.push('Team');
if (hasTestimonials) navLinks.push('Reviews');
if (hasFAQ) navLinks.push('FAQ');
if (hasContact) navLinks.push('Contact');
if (navLinks.length === 0) navLinks.push('About', 'Services');
const navLinksHTML = navLinks.map(l => `<a href="#${l.toLowerCase()}" style="color:${textSecondary};text-decoration:none;font-size:14px;transition:color 0.2s;" onmouseover="this.style.color='${accent}'" onmouseout="this.style.color='${textSecondary}'">${l}</a>`).join('');
sections += `
<nav style="position:fixed;top:0;left:0;right:0;z-index:100;backdrop-filter:blur(16px);background:${dark ? 'rgba(15,15,19,0.8)' : 'rgba(255,255,255,0.85)'};border-bottom:1px solid ${borderColor};">
<div style="max-width:1200px;margin:0 auto;padding:16px 24px;display:flex;align-items:center;justify-content:space-between;">
<div style="font-family:${headingFont};font-size:20px;font-weight:700;color:${textPrimary};">${brand}</div>
<div style="display:flex;gap:28px;align-items:center;">${navLinksHTML}
<a href="#contact" style="background:${accent};color:#0c0c0f;padding:8px 20px;border-radius:8px;font-size:14px;font-weight:600;text-decoration:none;transition:opacity 0.2s;" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">${ctaPrimary}</a>
</div>
</div>
</nav>`;
// -- HERO --
sections += `
<section style="min-height:100vh;display:flex;align-items:center;justify-content:center;text-align:center;padding:120px 24px 80px;background:${bg};position:relative;overflow:hidden;">
<div style="position:absolute;top:-200px;right:-200px;width:500px;height:500px;border-radius:50%;background:${accent};filter:blur(150px);opacity:${dark ? '0.08' : '0.06'};pointer-events:none;"></div>
<div style="position:absolute;bottom:-150px;left:-150px;width:400px;height:400px;border-radius:50%;background:${accent};filter:blur(120px);opacity:${dark ? '0.05' : '0.04'};pointer-events:none;"></div>
<div style="max-width:720px;position:relative;z-index:1;">
<div style="display:inline-block;background:${dark ? 'rgba(232,168,56,0.1)' : 'rgba(232,168,56,0.1)'};color:${accent};padding:6px 16px;border-radius:20px;font-size:13px;font-weight:500;margin-bottom:24px;border:1px solid ${dark ? 'rgba(232,168,56,0.2)' : 'rgba(232,168,56,0.15)'};">Welcome to ${brand}</div>
<h1 style="font-family:${headingFont};font-size:clamp(36px,6vw,64px);font-weight:800;line-height:1.1;color:${textPrimary};margin-bottom:20px;letter-spacing:-0.02em;">${tagline}</h1>
<p style="font-size:18px;color:${textSecondary};line-height:1.7;max-width:560px;margin:0 auto 36px;">Crafted with precision and attention to detail. Experience the difference that quality makes.</p>
<div style="display:flex;gap:14px;justify-content:center;flex-wrap:wrap;">
<a href="#contact" style="background:${accent};color:#0c0c0f;padding:14px 32px;border-radius:10px;font-size:15px;font-weight:600;text-decoration:none;display:inline-flex;align-items:center;gap:8px;transition:transform 0.2s,box-shadow 0.2s;box-shadow:0 4px 16px ${accent}33;" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='0 8px 28px ${accent}44'" onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 4px 16px ${accent}33'">${ctaPrimary} &rarr;</a>
<a href="#features" style="background:transparent;color:${textPrimary};padding:14px 32px;border-radius:10px;font-size:15px;font-weight:500;text-decoration:none;border:1px solid ${borderColor};transition:border-color 0.2s;" onmouseover="this.style.borderColor='${accent}'" onmouseout="this.style.borderColor='${borderColor}'">${ctaSecondary}</a>
</div>
</div>
</section>`;
// -- STATS --
if (hasStats) {
const stats = [
{ num: '500+', label: 'Happy Clients' },
{ num: '1,200+', label: 'Projects Done' },
{ num: '15+', label: 'Awards Won' },
{ num: '99%', label: 'Satisfaction' },
];
sections += `
<section style="padding:60px 24px;background:${subtleBg};border-top:1px solid ${borderColor};border-bottom:1px solid ${borderColor};">
<div style="max-width:1000px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:32px;text-align:center;">
${stats.map(s => `
<div>
<div style="font-family:${headingFont};font-size:36px;font-weight:800;color:${accent};">${s.num}</div>
<div style="font-size:14px;color:${textSecondary};margin-top:4px;">${s.label}</div>
</div>`).join('')}
</div>
</section>`;
}
// -- FEATURES --
if (hasFeatures) {
const featureCount = hasPricing ? 3 : 6;
const featureIcons = ['⚡', '🎯', '🛡️', '🚀', '💡', '🔧'];
const featureTitles = ['Lightning Fast', 'Pinpoint Accuracy', 'Rock Solid', 'Scale Effortlessly', 'Smart Insights', 'Easy Integration'];
const featureDescs = [
'Optimized for speed at every level. No compromises on performance.',
'Precise results driven by intelligent algorithms and careful engineering.',
'Built with reliability in mind. Enterprise-grade stability you can count on.',
'Grows with your needs. From startup to enterprise, we handle it all.',
'Data-driven decisions made simple with powerful analytics and reporting.',
'Connect with your existing tools seamlessly through our robust API.',
];
let featuresHTML = '';
for (let i = 0; i < featureCount; i++) {
featuresHTML += `
<div style="background:${bgCard};border:1px solid ${borderColor};border-radius:14px;padding:32px;transition:transform 0.25s,border-color 0.25s;" onmouseover="this.style.transform='translateY(-4px)';this.style.borderColor='${accent}'" onmouseout="this.style.transform='translateY(0)';this.style.borderColor='${borderColor}'">
<div style="font-size:28px;margin-bottom:16px;">${featureIcons[i]}</div>
<h3 style="font-family:${headingFont};font-size:18px;font-weight:600;color:${textPrimary};margin-bottom:10px;">${featureTitles[i]}</h3>
<p style="font-size:14px;color:${textSecondary};line-height:1.7;">${featureDescs[i]}</p>
</div>`;
}
sections += `
<section id="features" style="padding:100px 24px;background:${bg};">
<div style="max-width:1100px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Features</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Everything you need</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">Powerful features designed to help you achieve more with less effort.</p>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px;">${featuresHTML}</div>
</div>
</section>`;
}
// -- GALLERY --
if (hasGallery) {
const seeds = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta'];
let galleryHTML = seeds.map((s, i) => `
<div style="border-radius:12px;overflow:hidden;position:relative;aspect-ratio:4/3;cursor:pointer;" onmouseover="this.querySelector('.overlay').style.opacity='1'" onmouseout="this.querySelector('.overlay').style.opacity='0'">
<img src="https://picsum.photos/seed/${s}${i}/600/450.jpg" alt="Project ${i+1}" style="width:100%;height:100%;object-fit:cover;display:block;">
<div class="overlay" style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,0.7),transparent);opacity:0;transition:opacity 0.3s;display:flex;align-items:flex-end;padding:20px;">
<div>
<div style="color:#fff;font-weight:600;font-size:16px;">Project ${i+1}</div>
<div style="color:rgba(255,255,255,0.7);font-size:13px;margin-top:4px;">View Details</div>
</div>
</div>
</div>`).join('');
sections += `
<section id="gallery" style="padding:100px 24px;background:${subtleBg};">
<div style="max-width:1100px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Gallery</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Selected Work</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">A curated collection of our finest projects and creations.</p>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px;">${galleryHTML}</div>
</div>
</section>`;
}
// -- MENU (restaurant) --
if (hasMenu) {
const items = [
{ name: 'Truffle Risotto', desc: 'Arborio rice, black truffle, parmesan, wild mushrooms', price: '$24' },
{ name: 'Pan-Seared Salmon', desc: 'Atlantic salmon, lemon beurre blanc, asparagus, capers', price: '$28' },
{ name: 'Wagyu Beef Tenderloin', desc: 'A5 wagyu, red wine jus, roasted garlic, truffle mash', price: '$52' },
{ name: 'Lobster Linguine', desc: 'Fresh Maine lobster, cherry tomatoes, basil, white wine', price: '$36' },
{ name: 'Burrata Salad', desc: 'Creamy burrata, heirloom tomatoes, prosciutto, aged balsamic', price: '$18' },
{ name: 'Chocolate Souffle', desc: 'Dark Valrhona chocolate, vanilla bean creme anglaise', price: '$16' },
];
let menuHTML = items.map(item => `
<div style="display:flex;justify-content:space-between;align-items:flex-start;padding:20px 0;border-bottom:1px solid ${borderColor};">
<div style="flex:1;">
<div style="font-family:${headingFont};font-size:17px;font-weight:600;color:${textPrimary};">${item.name}</div>
<div style="font-size:14px;color:${textSecondary};margin-top:4px;">${item.desc}</div>
</div>
<div style="font-family:${headingFont};font-size:17px;font-weight:700;color:${accent};margin-left:24px;white-space:nowrap;">${item.price}</div>
</div>`).join('');
sections += `
<section id="menu" style="padding:100px 24px;background:${bg};">
<div style="max-width:800px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Menu</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Our Cuisine</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">Seasonal ingredients, timeless techniques, unforgettable flavors.</p>
</div>
${menuHTML}
</div>
</section>`;
}
// -- SCHEDULE (gym) --
if (hasSchedule) {
const classes = [
{ time: '6:00 AM', name: 'Morning Yoga', trainer: 'Sarah M.', duration: '60 min', level: 'All Levels' },
{ time: '8:00 AM', name: 'HIIT Blast', trainer: 'Mike R.', duration: '45 min', level: 'Intermediate' },
{ time: '10:00 AM', name: 'Strength Training', trainer: 'James K.', duration: '60 min', level: 'Advanced' },
{ time: '12:00 PM', name: 'Spin Cycle', trainer: 'Lisa P.', duration: '45 min', level: 'All Levels' },
{ time: '5:00 PM', name: 'Boxing Fundamentals', trainer: 'David C.', duration: '60 min', level: 'Beginner' },
{ time: '7:00 PM', name: 'Pilates Core', trainer: 'Emma W.', duration: '50 min', level: 'Intermediate' },
];
let scheduleHTML = classes.map(c => `
<div style="display:grid;grid-template-columns:100px 1fr 120px 100px 100px;gap:16px;align-items:center;padding:18px 20px;background:${bgCard};border:1px solid ${borderColor};border-radius:10px;transition:border-color 0.2s;" onmouseover="this.style.borderColor='${accent}'" onmouseout="this.style.borderColor='${borderColor}'">
<div style="font-size:14px;font-weight:600;color:${accent};">${c.time}</div>
<div style="font-weight:600;color:${textPrimary};font-size:15px;">${c.name}</div>
<div style="font-size:13px;color:${textSecondary};">${c.trainer}</div>
<div style="font-size:13px;color:${textSecondary};">${c.duration}</div>
<div style="font-size:12px;padding:4px 10px;border-radius:6px;text-align:center;background:${dark ? 'rgba(232,168,56,0.1)' : 'rgba(232,168,56,0.1)'};color:${accent};font-weight:500;">${c.level}</div>
</div>`).join('');
sections += `
<section id="schedule" style="padding:100px 24px;background:${subtleBg};">
<div style="max-width:1000px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Schedule</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Class Timetable</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">Find the perfect class that fits your schedule and fitness goals.</p>
</div>
<div style="display:flex;flex-direction:column;gap:10px;">${scheduleHTML}</div>
</div>
</section>`;
}
// -- TEAM --
if (hasTeam) {
const members = [
{ name: 'Alex Johnson', role: 'Founder & CEO', seed: 'alex01' },
{ name: 'Maya Chen', role: 'Lead Designer', seed: 'maya02' },
{ name: 'Sam Patel', role: 'Head of Engineering', seed: 'sam03' },
{ name: 'Jordan Lee', role: 'Marketing Director', seed: 'jordan04' },
];
let teamHTML = members.map(m => `
<div style="text-align:center;">
<div style="width:120px;height:120px;border-radius:50%;margin:0 auto 16px;overflow:hidden;border:3px solid ${borderColor};transition:border-color 0.2s;" onmouseover="this.style.borderColor='${accent}'" onmouseout="this.style.borderColor='${borderColor}'">
<img src="https://picsum.photos/seed/${m.seed}/240/240.jpg" alt="${m.name}" style="width:100%;height:100%;object-fit:cover;">
</div>
<div style="font-family:${headingFont};font-size:16px;font-weight:600;color:${textPrimary};">${m.name}</div>
<div style="font-size:13px;color:${textSecondary};margin-top:4px;">${m.role}</div>
</div>`).join('');
sections += `
<section id="team" style="padding:100px 24px;background:${bg};">
<div style="max-width:1000px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Team</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Meet the Team</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">The talented people behind everything we do.</p>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:40px;">${teamHTML}</div>
</div>
</section>`;
}
// -- PRICING --
if (hasPricing) {
const plans = [
{ name: 'Starter', price: '$9', period: '/month', features: ['5 Projects', 'Basic Analytics', 'Email Support', '1GB Storage'], highlighted: false },
{ name: 'Professional', price: '$29', period: '/month', features: ['Unlimited Projects', 'Advanced Analytics', 'Priority Support', '50GB Storage', 'Custom Domain', 'API Access'], highlighted: true },
{ name: 'Enterprise', price: '$99', period: '/month', features: ['Everything in Pro', 'Dedicated Manager', 'SLA Guarantee', 'Unlimited Storage', 'SSO & SAML', 'Custom Integrations'], highlighted: false },
];
let pricingHTML = plans.map(pl => `
<div style="background:${pl.highlighted ? accent : bgCard};border:2px solid ${pl.highlighted ? accent : borderColor};border-radius:16px;padding:36px 28px;position:relative;transition:transform 0.25s;" onmouseover="this.style.transform='translateY(-4px)'" onmouseout="this.style.transform='translateY(0)'">
${pl.highlighted ? `<div style="position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:#0c0c0f;color:${accent};font-size:11px;font-weight:700;padding:4px 14px;border-radius:20px;text-transform:uppercase;letter-spacing:1px;">Most Popular</div>` : ''}
<div style="font-family:${headingFont};font-size:18px;font-weight:600;color:${pl.highlighted ? '#0c0c0f' : textPrimary};margin-bottom:8px;">${pl.name}</div>
<div style="display:flex;align-items:baseline;gap:2px;margin-bottom:24px;">
<span style="font-family:${headingFont};font-size:48px;font-weight:800;color:${pl.highlighted ? '#0c0c0f' : textPrimary};">${pl.price}</span>
<span style="font-size:14px;color:${pl.highlighted ? 'rgba(12,12,15,0.6)' : textSecondary};">${pl.period}</span>
</div>
<ul style="list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:12px;">
${pl.features.map(f => `<li style="font-size:14px;color:${pl.highlighted ? 'rgba(12,12,15,0.8)' : textSecondary};display:flex;align-items:center;gap:10px;"><span style="color:${pl.highlighted ? '#0c0c0f' : accent};font-weight:bold;">&#10003;</span> ${f}</li>`).join('')}
</ul>
<a href="#contact" style="display:block;text-align:center;padding:12px;border-radius:10px;font-size:14px;font-weight:600;text-decoration:none;transition:opacity 0.2s;background:${pl.highlighted ? '#0c0c0f' : 'transparent'};color:${pl.highlighted ? accent : textPrimary};border:${pl.highlighted ? 'none' : '1px solid ' + borderColor};" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">Choose Plan</a>
</div>`).join('');
sections += `
<section id="pricing" style="padding:100px 24px;background:${subtleBg};">
<div style="max-width:1000px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Pricing</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Simple, Transparent Pricing</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">No hidden fees. No surprises. Pick the plan that works for you.</p>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;align-items:start;">${pricingHTML}</div>
</div>
</section>`;
}
// -- TESTIMONIALS --
if (hasTestimonials) {
const reviews = [
{ text: 'Absolutely transformed the way we work. The results speak for themselves — a 40% increase in productivity within the first month.', name: 'Sarah Mitchell', role: 'CEO, TechFlow', seed: 'sarah10' },
{ text: 'The attention to detail is remarkable. Every interaction feels polished and intentional. Highly recommend to any serious team.', name: 'Daniel Park', role: 'CTO, Datawise', seed: 'daniel11' },
{ text: 'We evaluated a dozen options. This was the clear winner. The onboarding was seamless and the support team is outstanding.', name: 'Rachel Torres', role: 'VP Product, Nexus', seed: 'rachel12' },
];
let reviewsHTML = reviews.map(r => `
<div style="background:${bgCard};border:1px solid ${borderColor};border-radius:14px;padding:32px;transition:transform 0.25s,border-color 0.25s;" onmouseover="this.style.transform='translateY(-4px)';this.style.borderColor='${accent}'" onmouseout="this.style.transform='translateY(0)';this.style.borderColor='${borderColor}'">
<div style="color:${accent};font-size:24px;margin-bottom:16px;">&#8220;</div>
<p style="font-size:15px;color:${textSecondary};line-height:1.7;margin-bottom:24px;">${r.text}</p>
<div style="display:flex;align-items:center;gap:12px;">
<img src="https://picsum.photos/seed/${r.seed}/80/80.jpg" alt="${r.name}" style="width:44px;height:44px;border-radius:50%;object-fit:cover;">
<div>
<div style="font-size:14px;font-weight:600;color:${textPrimary};">${r.name}</div>
<div style="font-size:13px;color:${textSecondary};">${r.role}</div>
</div>
</div>
</div>`).join('');
sections += `
<section id="reviews" style="padding:100px 24px;background:${bg};">
<div style="max-width:1100px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Testimonials</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Loved by Teams</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">Don't just take our word for it. Hear from the people who use it every day.</p>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px;">${reviewsHTML}</div>
</div>
</section>`;
}
// -- FAQ --
if (hasFAQ) {
const faqs = [
{ q: 'How do I get started?', a: 'Simply sign up for a free account and follow our guided onboarding. You will be up and running in under 5 minutes.' },
{ q: 'Is there a free trial available?', a: 'Yes! We offer a 14-day free trial on all paid plans. No credit card required to start.' },
{ q: 'Can I cancel my subscription anytime?', a: 'Absolutely. There are no long-term contracts. You can cancel from your dashboard at any time.' },
{ q: 'Do you offer custom enterprise solutions?', a: 'Yes, our Enterprise plan is fully customizable. Contact our sales team to discuss your specific requirements.' },
];
let faqHTML = faqs.map((f, i) => `
<details style="background:${bgCard};border:1px solid ${borderColor};border-radius:10px;overflow:hidden;" ${i === 0 ? 'open' : ''}>
<summary style="padding:20px 24px;cursor:pointer;font-weight:600;color:${textPrimary};font-size:15px;list-style:none;display:flex;justify-content:space-between;align-items:center;transition:color 0.2s;" onmouseover="this.style.color='${accent}'" onmouseout="this.style.color='${textPrimary}'">
${f.q}
<span style="font-size:18px;color:${textSecondary};transition:transform 0.2s;">+</span>
</summary>
<div style="padding:0 24px 20px;font-size:14px;color:${textSecondary};line-height:1.7;">${f.a}</div>
</details>`).join('');
sections += `
<section id="faq" style="padding:100px 24px;background:${subtleBg};">
<div style="max-width:720px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">FAQ</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Common Questions</h2>
</div>
<div style="display:flex;flex-direction:column;gap:10px;">${faqHTML}</div>
</div>
</section>`;
}
// -- CONTACT --
if (hasContact) {
sections += `
<section id="contact" style="padding:100px 24px;background:${bg};">
<div style="max-width:600px;margin:0 auto;">
<div style="text-align:center;margin-bottom:56px;">
<div style="color:${accent};font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:2px;margin-bottom:12px;">Contact</div>
<h2 style="font-family:${headingFont};font-size:clamp(28px,4vw,42px);font-weight:700;color:${textPrimary};margin-bottom:16px;">Get in Touch</h2>
<p style="font-size:16px;color:${textSecondary};max-width:520px;margin:0 auto;">Have a question or want to work together? We would love to hear from you.</p>
</div>
<form onsubmit="event.preventDefault();this.querySelector('button').textContent='Sent!';this.querySelector('button').style.background='${accent}';" style="display:flex;flex-direction:column;gap:16px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<input type="text" placeholder="First Name" required style="background:${bgCard};border:1px solid ${borderColor};color:${textPrimary};padding:14px 16px;border-radius:10px;font-size:14px;font-family:inherit;outline:none;transition:border-color 0.2s;" onfocus="this.style.borderColor='${accent}'" onblur="this.style.borderColor='${borderColor}'">
<input type="text" placeholder="Last Name" required style="background:${bgCard};border:1px solid ${borderColor};color:${textPrimary};padding:14px 16px;border-radius:10px;font-size:14px;font-family:inherit;outline:none;transition:border-color 0.2s;" onfocus="this.style.borderColor='${accent}'" onblur="this.style.borderColor='${borderColor}'">
</div>
<input type="email" placeholder="Email Address" required style="background:${bgCard};border:1px solid ${borderColor};color:${textPrimary};padding:14px 16px;border-radius:10px;font-size:14px;font-family:inherit;outline:none;transition:border-color 0.2s;" onfocus="this.style.borderColor='${accent}'" onblur="this.style.borderColor='${borderColor}'">
<textarea placeholder="Your message..." rows="5" required style="background:${bgCard};border:1px solid ${borderColor};color:${textPrimary};padding:14px 16px;border-radius:10px;font-size:14px;font-family:inherit;outline:none;resize:vertical;transition:border-color 0.2s;" onfocus="this.style.borderColor='${accent}'" onblur="this.style.borderColor='${borderColor}'"></textarea>
<button type="submit" style="background:${accent};color:#0c0c0f;padding:14px;border-radius:10px;font-size:15px;font-weight:600;border:none;cursor:pointer;font-family:inherit;transition:opacity 0.2s;" onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">Send Message</button>
</form>
</div>
</section>`;
}
// -- FOOTER --
sections += `
<footer style="padding:40px 24px;background:${dark ? '#0a0a0d' : '#f0f0f2'};border-top:1px solid ${borderColor};">
<div style="max-width:1100px;margin:0 auto;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:16px;">
<div style="font-family:${headingFont};font-size:16px;font-weight:700;color:${textPrimary};">${brand}</div>
<div style="font-size:13px;color:${textSecondary};">Built with SiteForge. All rights reserved.</div>
</div>
</footer>`;
// ---- Assemble full HTML ----
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${brand}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { font-family: ${fontFamily}; background: ${bg}; color: ${textPrimary}; -webkit-font-smoothing: antialiased; }
img { max-width: 100%; }
@media (max-width: 768px) {
nav > div > div:last-child { display: none !important; }
}
</style>
</head>
<body>
${sections}
</body>
</html>`;
}
// ============================================================
// Generate handler
// ============================================================
async function generate() {
const prompt = promptInput.value.trim();
if (!prompt) {
showToast('Please enter a prompt first.');
promptInput.focus();
return;
}
// Set generating state
generateBtn.disabled = true;
btnText.innerHTML = '<span class="spinner"></span> Generating...';
statusDot.className = 'status-dot generating';
statusText.innerHTML = '<span class="typing-text">Building your website</span>';
actionBar.style.opacity = '0';
actionBar.style.pointerEvents = 'none';
// Simulate async generation delay (feels more natural)
await new Promise(r => setTimeout(r, 1200 + Math.random() * 800));
try {
generatedHTML = generateWebsite(prompt);
// Load into iframe
const frame = previewFrame;
frame.style.display = 'block';
emptyState.style.display = 'none';
frame.srcdoc = generatedHTML;
// Set viewport
setViewport(currentViewport);
// Update status
statusDot.className = 'status-dot done';
statusText.textContent = 'Website generated successfully';
// Show action bar
actionBar.style.opacity = '1';
actionBar.style.pointerEvents = 'auto';
showToast('Website generated!');
} catch (err) {
statusDot.className = 'status-dot idle';
statusText.textContent = 'Error: ' + err.message;
showToast('Generation failed. Try again.');
}
// Reset button
generateBtn.disabled = false;
btnText.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles text-xs"></i> Generate Website';
}
// ============================================================
// Action handlers
// ============================================================
function downloadHTML() {
if (!generatedHTML) return;
const blob = new Blob([generatedHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'website.html';
a.click();
URL.revokeObjectURL(url);
showToast('HTML file downloaded!');
}
function copyHTML() {
if (!generatedHTML) return;
navigator.clipboard.writeText(generatedHTML).then(() => {
showToast('HTML copied to clipboard!');
}).catch(() => {
// Fallback
const ta = document.createElement('textarea');
ta.value = generatedHTML;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast('HTML copied to clipboard!');
});
}
function viewSource() {
if (!generatedHTML) return;
const el = document.getElementById('sourceCode');
// Escape HTML for display
el.textContent = generatedHTML;
document.getElementById('sourceModal').style.display = 'flex';
}
function closeSourceModal(e) {
if (e.target === e.currentTarget) {
document.getElementById('sourceModal').style.display = 'none';
}
}
// ============================================================
// Keyboard shortcut: Ctrl+Enter to generate
// ============================================================
promptInput.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
generate();
}
});
// Initialize viewport
setViewport('desktop');
</script>
</body>
</html>