Spaces:
Sleeping
Sleeping
| <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 ; } | |
| .panel-left { width: 100% ; height: 45% ; border-right: none ; border-bottom: 1px solid var(--border) ; } | |
| .panel-right { height: 55% ; } | |
| } | |
| </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} →</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;">✓</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;">“</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> |