cyrus-studio 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1330 @@
1
+ import{r as e}from"./rolldown-runtime-W7wSyTde.js";import{t}from"./themes-CNabH9CH.js";import{t as n}from"./vendor-jszip-Cv4gVObP.js";var r=e(n(),1);async function i(e,t,n=`react`){let i=new r.default;for(let e of t)i.file(e.path,e.content);let a=await i.generateAsync({type:`blob`}),o=URL.createObjectURL(a),s=document.createElement(`a`);s.href=o,s.download=`${e.toLowerCase().replace(/[^a-z0-9]/g,`_`)}_${n}_app.zip`,s.click(),URL.revokeObjectURL(o)}function a(e,n=`nextjs`){let r=(e.components||[]).map(e=>{let t=o(e.templateElement),n=l(e.name);return{name:n,fileName:`${n}.tsx`,propsSchema:[{name:`title`,type:`string`,defaultValue:e.templateElement.content?.title},{name:`className`,type:`string`,defaultValue:``}],rootNode:t}}),i=e.pages.map(t=>{let n=s(t.elements);return{id:t.id,name:t.name,slug:t.slug,route:t.slug===`home`||t.slug===`index`?`/`:`/${t.slug}`,sections:n,metaTitle:t.settings?.metaTitle||`${t.name} | ${e.name}`,metaDescription:t.settings?.metaDescription||`${t.name} page for ${e.name}`,favicon:t.settings?.favicon||e.settings?.favicon,canonicalUrl:t.settings?.canonicalUrl,socialImage:t.settings?.socialImage||e.settings?.defaultOgImage,ogType:t.settings?.ogType||`website`,twitterCard:t.settings?.twitterCard||`summary_large_image`,noIndex:t.settings?.noIndex||!1,keywords:t.settings?.keywords||[]}}),a=[];return e.pages.forEach(e=>{e.elements.forEach(e=>{let t=e.content?.src||e.content?.url;t&&typeof t==`string`&&a.push({id:e.id,name:e.name||`Asset`,url:t})})}),{id:e.id,name:e.name,description:e.description,target:n,designTokens:e.designSystem||t,pages:i,components:r,assets:a,variables:e.variables||[],dataSources:e.dataSources||[],siteUrl:e.settings?.siteUrl||`https://example.com`,defaultOgImage:e.settings?.defaultOgImage,defaultFavicon:e.settings?.favicon,sitemapEnabled:e.settings?.sitemapEnabled??!0,robotsTxtEnabled:e.settings?.robotsTxtEnabled??!0}}function o(e){let t=c(e.type);return{id:e.id,type:e.type,semanticRole:t,name:e.name,props:{...e.content},styles:{...e.styles},responsive:e.responsive,bindings:e.bindings,collectionBinding:e.collectionBinding,formConfig:e.formConfig,interactions:(e.interactions||[]).map(e=>({trigger:e.trigger,action:e.action,target:e.target,targetName:e.targetName})),animations:(e.animations||[]).map(e=>({trigger:e.trigger===`hover`?`hover`:`load`,type:e.type||`none`,duration:e.duration,delay:e.delay,easing:e.easing})),componentName:e.isComponentInstance?l(e.name):void 0,accessibility:e.accessibility}}function s(e){if(e.length===0)return[];let t=[...e].sort((e,t)=>e.y-t.y),n=[],r=0;for(;r<t.length;){let e=t[r];if(e.type===`navbar`||e.type===`hero`||e.type===`heroSection`||e.type===`footer`||e.type===`cta`||e.type===`sidebar`||e.type===`bottomNav`||e.type===`loginForm`){n.push(o(e)),r++;continue}if(e.type.includes(`Card`)||e.type===`card`){let i=[e],a=r+1;for(;a<t.length;){let n=t[a];if((n.type.includes(`Card`)||n.type===`card`)&&Math.abs(n.y-e.y)<=60)i.push(n),a++;else break}if(i.length>1){let t={id:`grid_${e.id}`,type:`grid`,semanticRole:e.type===`pricingCard`?`pricing`:e.type===`metricCard`?`stats`:`features`,name:`${e.name} Grid (${i.length} items)`,props:{columns:i.length},styles:{display:`grid`,gap:24},children:i.map(o)};n.push(t),r=a;continue}}n.push(o(e)),r++}return n}function c(e){return e===`navbar`?`header`:e===`hero`||e===`heroSection`?`hero`:e===`pricingCard`?`pricing`:e===`metricCard`||e===`chart`||e===`dataTable`?`stats`:e===`featureCard`||e===`card`?`features`:e===`loginForm`?`auth`:e===`footer`?`footer`:`content`}function l(e){return e.replace(/[^a-zA-Z0-9]/g,` `).split(` `).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`Component`}function u(e){let t=0,n=0,r=0,i=[];e.pages.forEach(e=>{(!e.metaTitle||e.metaTitle.length<5)&&i.push(`${e.name} (Missing or short title)`),(!e.metaDescription||e.metaDescription.length<10)&&i.push(`${e.name} (Missing meta description)`),(e.sections||[]).forEach(e=>{r++,(e.type===`image`||e.props?.src)&&(t++,!e.props?.alt&&!e.accessibility?.altText&&n++)})});let a=Math.max(0,Math.round(100-n*10)),o=Math.max(0,Math.round(100-i.length*15)),s=[];return n>0&&s.push(`Add descriptive alt text to ${n} image elements.`),i.length>0&&s.push(`Provide custom meta descriptions for ${i.length} pages.`),s.length===0&&s.push(`All core WCAG 2.1 AA and SEO standards are met!`),{accessibilityReport:`
2
+ # Accessibility Compliance Report (WCAG 2.1 AA)
3
+
4
+ **Project:** ${e.name}
5
+ **Audit Date:** ${new Date().toISOString().split(`T`)[0]}
6
+ **Target:** ${(e.target||`react`).toUpperCase()}
7
+ **Compliance Score:** ${a}%
8
+
9
+ ---
10
+
11
+ ## 1. Landmark & Semantic Structure
12
+ - **Landmark Regions Verified:** ${r} landmarks across ${e.pages.length} pages.
13
+ - **Semantic Roles:** \`<header>\`, \`<nav>\`, \`<main>\`, \`<section>\`, \`<article>\`, \`<aside>\`, \`<footer>\`.
14
+ - **Keyboard Navigation:** Native interactive elements (buttons, links, inputs) with accessible focus ring styles.
15
+
16
+ ## 2. Image & Media Accessibility
17
+ - **Total Images Audited:** ${t}
18
+ - **Images with Alt Text:** ${t-n}
19
+ - **Decorative Images:** Marked with \`aria-hidden="true"\` or empty \`alt=""\`.
20
+
21
+ ## 3. WCAG AA Color Contrast
22
+ - Primary text on surface meets minimum 4.5:1 ratio for normal text and 3:1 for large text.
23
+ - Form inputs include explicit labels and high-visibility focus borders.
24
+
25
+ ---
26
+ *Generated automatically by Cyrus Production Quality Engine.*
27
+ `.trim(),seoAuditReport:`
28
+ # Production SEO & Open Graph Audit
29
+
30
+ **Project:** ${e.name}
31
+ **Site URL:** ${e.siteUrl||`https://example.com`}
32
+ **SEO Health Score:** ${o}%
33
+
34
+ ---
35
+
36
+ ## 1. Page Index & Metadata Coverage
37
+
38
+ | Route | Title Tag | Meta Description | Canonical URL | Open Graph Image |
39
+ | :--- | :--- | :--- | :--- | :--- |
40
+ ${e.pages.map(t=>`| \`${t.route}\` | ${t.metaTitle?`✅ `+t.metaTitle.slice(0,30)+`...`:`⚠️ Default`} | ${t.metaDescription?`✅ Provided`:`⚠️ Missing`} | \`${t.canonicalUrl||(e.siteUrl||``)+t.route}\` | ${t.socialImage||e.defaultOgImage?`✅ Configured`:`ℹ️ None`} |`).join(`
41
+ `)}
42
+
43
+ ---
44
+
45
+ ## 2. Search Engine Indexing
46
+ - **Sitemap XML:** Generated at \`public/sitemap.xml\` (${e.pages.length} URLs indexed).
47
+ - **Robots.txt:** Generated at \`public/robots.txt\` with crawler allow policies.
48
+ - **Font Optimization:** Preconnect links for Google Fonts with \`display=swap\`.
49
+ - **Image Performance:** Native \`loading="lazy"\` and \`decoding="async"\` applied across all media.
50
+
51
+ ---
52
+ *Generated automatically by Cyrus Production Quality Engine.*
53
+ `.trim(),summary:{wcagComplianceScore:a,seoScore:o,totalLandmarks:r,totalImagesChecked:t,lazyLoadedImagesCount:t,seoMetadataPagesCount:e.pages.length,recommendations:s}}}function d(e){let t=[],n=e.name.toLowerCase().replace(/[^a-z0-9]/g,`-`).replace(/-+/g,`-`),r=e.designTokens;t.push({path:`package.json`,language:`json`,content:JSON.stringify({name:n,private:!0,version:`0.1.0`,type:`module`,scripts:{dev:`vite`,build:`tsc -b && vite build`,preview:`vite preview`},dependencies:{clsx:`^2.1.1`,"lucide-react":`^1.16.0`,react:`^18.3.1`,"react-dom":`^18.3.1`},devDependencies:{"@types/react":`^18.3.3`,"@types/react-dom":`^18.3.0`,"@vitejs/plugin-react":`^4.3.1`,typescript:`^5.5.3`,vite:`^5.4.0`}},null,2)}),t.push({path:`tsconfig.json`,language:`json`,content:JSON.stringify({compilerOptions:{target:`ES2020`,useDefineForClassFields:!0,lib:[`ES2020`,`DOM`,`DOM.Iterable`],module:`ESNext`,skipLibCheck:!0,moduleResolution:`bundler`,allowImportingTsExtensions:!0,resolveJsonModule:!0,isolatedModules:!0,noEmit:!0,jsx:`react-jsx`,strict:!0,noUnusedLocals:!0,noUnusedParameters:!0,noFallthroughCasesInSwitch:!0},include:[`src`]},null,2)}),t.push({path:`vite.config.ts`,language:`typescript`,content:`import { defineConfig } from 'vite';
54
+ import react from '@vitejs/plugin-react';
55
+
56
+ export default defineConfig({
57
+ plugins: [react()],
58
+ });
59
+ `});let i=e.pages[0],a=e.siteUrl||`https://example.com`,o=i?.metaTitle||e.name,s=i?.metaDescription||e.description||`Modern web application built with Cyrus`,c=i?.socialImage||e.defaultOgImage||``;if(t.push({path:`index.html`,language:`html`,content:`<!DOCTYPE html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="UTF-8" />
63
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
64
+ <title>${o}</title>
65
+ <meta name="description" content="${s}" />
66
+ ${i?.keywords&&i.keywords.length>0?`<meta name="keywords" content="${i.keywords.join(`, `)}" />`:``}
67
+ <link rel="canonical" href="${i?.canonicalUrl||a+(i?.route||`/`)}" />
68
+ ${e.defaultFavicon||i?.favicon?`<link rel="icon" type="image/svg+xml" href="${i?.favicon||e.defaultFavicon}" />`:`<link rel="icon" type="image/svg+xml" href="/favicon.svg" />`}
69
+
70
+ <!-- Open Graph / Facebook -->
71
+ <meta property="og:type" content="${i?.ogType||`website`}" />
72
+ <meta property="og:url" content="${a}${i?.route||`/`}" />
73
+ <meta property="og:title" content="${o}" />
74
+ <meta property="og:description" content="${s}" />
75
+ ${c?`<meta property="og:image" content="${c}" />`:``}
76
+
77
+ <!-- Twitter Cards -->
78
+ <meta name="twitter:card" content="${i?.twitterCard||`summary_large_image`}" />
79
+ <meta name="twitter:title" content="${o}" />
80
+ <meta name="twitter:description" content="${s}" />
81
+ ${c?`<meta name="twitter:image" content="${c}" />`:``}
82
+
83
+ <!-- Security: Referrer Policy & Sniff Protection -->
84
+ <meta http-equiv="X-Content-Type-Options" content="nosniff" />
85
+ <meta name="referrer" content="strict-origin-when-cross-origin" />
86
+
87
+ <!-- Performance: Preconnect Google Fonts with swap -->
88
+ <link rel="preconnect" href="https://fonts.googleapis.com">
89
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
90
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
91
+ </head>
92
+ <body>
93
+ <div id="root"></div>
94
+ <script type="module" src="/src/main.tsx"><\/script>
95
+ </body>
96
+ </html>
97
+ `}),e.sitemapEnabled!==!1){let n=new Date().toISOString().split(`T`)[0],r=`<?xml version="1.0" encoding="UTF-8"?>
98
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
99
+ ${e.pages.map(e=>` <url>
100
+ <loc>${a}${e.route}</loc>
101
+ <lastmod>${n}</lastmod>
102
+ <changefreq>${e.route===`/`?`daily`:`weekly`}</changefreq>
103
+ <priority>${e.route===`/`?`1.0`:`0.8`}</priority>
104
+ </url>`).join(`
105
+ `)}
106
+ </urlset>
107
+ `.trim();t.push({path:`public/sitemap.xml`,language:`html`,content:r})}e.robotsTxtEnabled!==!1&&t.push({path:`public/robots.txt`,language:`javascript`,content:`# Robots.txt generated by Cyrus Production Engine
108
+ User-agent: *
109
+ Allow: /
110
+
111
+ Sitemap: ${a}/sitemap.xml
112
+ `});let{accessibilityReport:l,seoAuditReport:d}=u(e);t.push({path:`ACCESSIBILITY_REPORT.md`,language:`javascript`,content:l}),t.push({path:`SEO_AUDIT.md`,language:`javascript`,content:d}),t.push({path:`src/components/SeoHead.tsx`,language:`typescript`,content:`import { useEffect } from 'react';
113
+
114
+ export interface SeoHeadProps {
115
+ title: string;
116
+ description?: string;
117
+ canonicalUrl?: string;
118
+ socialImage?: string;
119
+ ogType?: string;
120
+ }
121
+
122
+ export function SeoHead({
123
+ title,
124
+ description,
125
+ canonicalUrl,
126
+ socialImage,
127
+ ogType = 'website',
128
+ }: SeoHeadProps) {
129
+ useEffect(() => {
130
+ document.title = title;
131
+
132
+ if (description) {
133
+ let metaDesc = document.querySelector('meta[name="description"]');
134
+ if (!metaDesc) {
135
+ metaDesc = document.createElement('meta');
136
+ metaDesc.setAttribute('name', 'description');
137
+ document.head.appendChild(metaDesc);
138
+ }
139
+ metaDesc.setAttribute('content', description);
140
+ }
141
+
142
+ if (canonicalUrl) {
143
+ let canonical = document.querySelector('link[rel="canonical"]');
144
+ if (!canonical) {
145
+ canonical = document.createElement('link');
146
+ canonical.setAttribute('rel', 'canonical');
147
+ document.head.appendChild(canonical);
148
+ }
149
+ canonical.setAttribute('href', canonicalUrl);
150
+ }
151
+ }, [title, description, canonicalUrl, socialImage, ogType]);
152
+
153
+ return null;
154
+ }
155
+ `}),t.push({path:`src/theme/tokens.ts`,language:`typescript`,content:`// Production 3-Tier Design Tokens generated by Cyrus
156
+ export const themeTokens = ${JSON.stringify(r,null,2)};
157
+ `}),t.push({path:`src/components/ThemeToggle.tsx`,language:`typescript`,content:`import React, { useEffect, useState } from 'react';
158
+ import { Sun, Moon } from 'lucide-react';
159
+
160
+ export function ThemeToggle() {
161
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
162
+
163
+ useEffect(() => {
164
+ const root = document.documentElement;
165
+ const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
166
+ const initial = isDark ? 'dark' : 'light';
167
+ setTheme(initial);
168
+ root.setAttribute('data-theme', initial);
169
+ }, []);
170
+
171
+ const toggleTheme = () => {
172
+ const next = theme === 'light' ? 'dark' : 'light';
173
+ setTheme(next);
174
+ document.documentElement.setAttribute('data-theme', next);
175
+ if (next === 'dark') {
176
+ document.documentElement.classList.add('dark');
177
+ } else {
178
+ document.documentElement.classList.remove('dark');
179
+ }
180
+ };
181
+
182
+ return (
183
+ <button
184
+ type="button"
185
+ onClick={toggleTheme}
186
+ aria-label="Toggle Light/Dark Theme"
187
+ style={{
188
+ display: 'inline-flex',
189
+ alignItems: 'center',
190
+ gap: '0.375rem',
191
+ padding: '0.375rem 0.625rem',
192
+ borderRadius: '8px',
193
+ backgroundColor: 'var(--color-surface-secondary, #f4f4f6)',
194
+ border: '1px solid var(--color-border, #e5e5ea)',
195
+ color: 'var(--color-text, #1d1d1f)',
196
+ fontSize: '0.8125rem',
197
+ fontWeight: 500,
198
+ cursor: 'pointer',
199
+ transition: 'all 0.15s ease',
200
+ }}
201
+ >
202
+ {theme === 'light' ? (
203
+ <>
204
+ <Moon style={{ width: '14px', height: '14px', color: 'var(--color-primary)' }} />
205
+ <span>Dark Mode</span>
206
+ </>
207
+ ) : (
208
+ <>
209
+ <Sun style={{ width: '14px', height: '14px', color: '#ff9500' }} />
210
+ <span>Light Mode</span>
211
+ </>
212
+ )}
213
+ </button>
214
+ );
215
+ }
216
+ `}),t.push({path:`src/styles/globals.css`,language:`css`,content:`/* Generated by Cyrus — Production 3-Tier Design System & Theme Engine */
217
+ :root {
218
+ /* 1. Primitive Tokens */
219
+ --primitive-blue-500: #0071e3;
220
+ --primitive-gray-50: #fbfbfd;
221
+ --primitive-gray-100: #f5f5f7;
222
+ --primitive-gray-200: #e5e5ea;
223
+ --primitive-gray-800: #2c2c2e;
224
+ --primitive-gray-900: #1c1c1e;
225
+
226
+ /* 2. Semantic Tokens (Light Default) */
227
+ --color-primary: ${r?.colors?.primary||`#0071e3`};
228
+ --color-primary-hover: #0077ed;
229
+ --color-secondary: ${r?.colors?.secondary||`#5856d6`};
230
+ --color-background: ${r?.colors?.background||`#f5f5f7`};
231
+ --color-surface: ${r?.colors?.surface||`#ffffff`};
232
+ --color-surface-secondary: ${r?.colors?.surfaceSecondary||`#f4f4f6`};
233
+ --color-text: ${r?.colors?.text||`#1d1d1f`};
234
+ --color-text-secondary: ${r?.colors?.textSecondary||`#86868b`};
235
+ --color-border: ${r?.colors?.border||`#e5e5ea`};
236
+ --color-success: ${r?.colors?.success||`#34c759`};
237
+ --color-error: ${r?.colors?.error||`#ff3b30`};
238
+
239
+ /* 3. Component Tokens */
240
+ --button-primary-bg: var(--color-primary);
241
+ --button-primary-text: #ffffff;
242
+ --button-radius: ${r?.radius?.medium||10}px;
243
+ --card-bg: var(--color-surface);
244
+ --card-border: var(--color-border);
245
+ --card-radius: ${r?.radius?.large||16}px;
246
+ --input-bg: var(--color-surface-secondary);
247
+ --input-border: var(--color-border);
248
+
249
+ --font-family: '${r?.typography?.body?.fontFamily||`Inter`}', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
250
+ }
251
+
252
+ /* Dark Mode Theme Tokens */
253
+ [data-theme="dark"], .dark {
254
+ --color-primary: #0a84ff;
255
+ --color-primary-hover: #409cff;
256
+ --color-secondary: #5e5ce6;
257
+ --color-background: #000000;
258
+ --color-surface: #1c1c1e;
259
+ --color-surface-secondary: #2c2c2e;
260
+ --color-text: #ffffff;
261
+ --color-text-secondary: #8e8e93;
262
+ --color-border: #38383a;
263
+ --button-primary-bg: #0a84ff;
264
+ --button-primary-text: #ffffff;
265
+ --card-bg: #1c1c1e;
266
+ --card-border: #38383a;
267
+ --input-bg: #1c1c1e;
268
+ --input-border: #38383a;
269
+ }
270
+
271
+ * {
272
+ box-sizing: border-box;
273
+ margin: 0;
274
+ padding: 0;
275
+ }
276
+
277
+ body {
278
+ font-family: var(--font-family);
279
+ background-color: var(--color-background);
280
+ color: var(--color-text);
281
+ line-height: 1.5;
282
+ -webkit-font-smoothing: antialiased;
283
+ transition: background-color 0.2s ease, color 0.2s ease;
284
+ }
285
+
286
+ button, input, select, textarea {
287
+ font-family: inherit;
288
+ }
289
+
290
+ button:focus-visible, a:focus-visible, input:focus-visible {
291
+ outline: 2px solid var(--color-primary);
292
+ outline-offset: 2px;
293
+ }
294
+
295
+ .container {
296
+ max-width: 1200px;
297
+ margin-left: auto;
298
+ margin-right: auto;
299
+ padding-left: 1.5rem;
300
+ padding-right: 1.5rem;
301
+ }
302
+
303
+ .grid-responsive {
304
+ display: grid;
305
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
306
+ gap: 1.5rem;
307
+ }
308
+
309
+ @media (max-width: 768px) {
310
+ .grid-responsive {
311
+ grid-template-columns: 1fr;
312
+ }
313
+ }
314
+ `}),t.push({path:`src/main.tsx`,language:`typescript`,content:`import React from 'react';
315
+ import ReactDOM from 'react-dom/client';
316
+ import { App } from './App';
317
+ import './styles/globals.css';
318
+
319
+ ReactDOM.createRoot(document.getElementById('root')!).render(
320
+ <React.StrictMode>
321
+ <App />
322
+ </React.StrictMode>
323
+ );
324
+ `}),t.push({path:`src/components/Navbar.tsx`,language:`typescript`,content:`import React, { useState } from 'react';
325
+ import { Menu, X } from 'lucide-react';
326
+
327
+ interface NavbarProps {
328
+ brandName?: string;
329
+ links?: string[];
330
+ activePage: string;
331
+ onNavigate: (pageSlug: string) => void;
332
+ onOpenAuthModal?: () => void;
333
+ }
334
+
335
+ export function Navbar({
336
+ brandName = '${e.name}',
337
+ links = ['Home', 'Pricing', 'Login'],
338
+ activePage,
339
+ onNavigate,
340
+ onOpenAuthModal,
341
+ }: NavbarProps) {
342
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
343
+
344
+ return (
345
+ <header className="sticky top-0 z-40 w-full bg-white/90 backdrop-blur-md border-b border-[#e5e5ea] px-6 py-3.5">
346
+ <div className="max-w-6xl mx-auto flex items-center justify-between">
347
+ <div className="flex items-center gap-8">
348
+ <button
349
+ type="button"
350
+ onClick={() => onNavigate('home')}
351
+ className="text-[16px] font-bold tracking-tight text-[#1d1d1f] hover:opacity-80 transition-opacity bg-transparent border-0 cursor-pointer"
352
+ >
353
+ {brandName}
354
+ </button>
355
+ <nav className="hidden md:flex items-center gap-6 text-[13.5px]">
356
+ {links.map((link) => {
357
+ const slug = link.toLowerCase();
358
+ const isCurrent = activePage === slug;
359
+ return (
360
+ <button
361
+ key={link}
362
+ type="button"
363
+ onClick={() => onNavigate(slug)}
364
+ className={\`font-medium transition-colors bg-transparent border-0 cursor-pointer \${
365
+ isCurrent ? 'text-[#0071e3] font-semibold' : 'text-[#6e6e73] hover:text-[#1d1d1f]'
366
+ }\`}
367
+ >
368
+ {link}
369
+ </button>
370
+ );
371
+ })}
372
+ </nav>
373
+ </div>
374
+
375
+ <div className="hidden md:flex items-center gap-3">
376
+ <button
377
+ type="button"
378
+ onClick={() => onOpenAuthModal ? onOpenAuthModal() : onNavigate('login')}
379
+ className="px-4 py-1.5 rounded-[8px] bg-[#0071e3] hover:bg-[#0077ed] text-white text-[13px] font-medium transition-all shadow-sm cursor-pointer border-0"
380
+ >
381
+ Get Started
382
+ </button>
383
+ </div>
384
+
385
+ <button
386
+ type="button"
387
+ onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
388
+ aria-label="Toggle Navigation"
389
+ className="md:hidden p-1.5 text-[#1d1d1f] bg-transparent border-0 cursor-pointer"
390
+ >
391
+ {mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
392
+ </button>
393
+ </div>
394
+
395
+ {mobileMenuOpen && (
396
+ <div className="md:hidden pt-3 pb-2 border-t border-[#e5e5ea] mt-2 flex flex-col gap-2">
397
+ {links.map((link) => (
398
+ <button
399
+ key={link}
400
+ type="button"
401
+ onClick={() => {
402
+ onNavigate(link.toLowerCase());
403
+ setMobileMenuOpen(false);
404
+ }}
405
+ className="text-left px-2 py-1.5 text-[14px] text-[#1d1d1f] font-medium bg-transparent border-0 cursor-pointer"
406
+ >
407
+ {link}
408
+ </button>
409
+ ))}
410
+ </div>
411
+ )}
412
+ </header>
413
+ );
414
+ }
415
+ `}),t.push({path:`src/components/Hero.tsx`,language:`typescript`,content:`import React from 'react';
416
+ import { Sparkles, ArrowRight } from 'lucide-react';
417
+
418
+ interface HeroProps {
419
+ badge?: string;
420
+ headline?: string;
421
+ subhead?: string;
422
+ primaryBtnText?: string;
423
+ onPrimaryClick?: () => void;
424
+ secondaryBtnText?: string;
425
+ onSecondaryClick?: () => void;
426
+ }
427
+
428
+ export function Hero({
429
+ badge = 'Engineering Precision',
430
+ headline = 'Transform ideas into shipped software.',
431
+ subhead = 'The complete visual product engine for developers and design teams.',
432
+ primaryBtnText = 'Start Free Trial',
433
+ onPrimaryClick,
434
+ secondaryBtnText = 'View Pricing',
435
+ onSecondaryClick,
436
+ }: HeroProps) {
437
+ return (
438
+ <section className="w-full py-16 md:py-24 px-6 flex flex-col items-center justify-center text-center">
439
+ {badge && (
440
+ <span className="inline-flex items-center gap-1.5 px-3.5 py-1 rounded-full bg-[#e8f2fc] text-[#0071e3] text-[12px] font-medium border border-[#d0e5fa] mb-6">
441
+ <Sparkles className="w-3.5 h-3.5" />
442
+ <span>{badge}</span>
443
+ </span>
444
+ )}
445
+ <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight text-[#1d1d1f] max-w-3xl leading-[1.1] mb-5">
446
+ {headline}
447
+ </h1>
448
+ <p className="text-base md:text-lg text-[#6e6e73] max-w-xl leading-relaxed mb-8">
449
+ {subhead}
450
+ </p>
451
+ <div className="flex flex-col sm:flex-row items-center gap-3 w-full sm:w-auto">
452
+ <button
453
+ type="button"
454
+ onClick={onPrimaryClick}
455
+ className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-[10px] bg-[#0071e3] hover:bg-[#0077ed] text-white text-[14.5px] font-medium shadow-sm transition-all cursor-pointer border-0"
456
+ >
457
+ <span>{primaryBtnText}</span>
458
+ <ArrowRight className="w-4 h-4" />
459
+ </button>
460
+ {secondaryBtnText && (
461
+ <button
462
+ type="button"
463
+ onClick={onSecondaryClick}
464
+ className="w-full sm:w-auto px-6 py-2.5 rounded-[10px] bg-white border border-[#e5e5ea] hover:bg-[#f4f4f6] text-[#1d1d1f] text-[14.5px] font-medium shadow-sm transition-all cursor-pointer"
465
+ >
466
+ {secondaryBtnText}
467
+ </button>
468
+ )}
469
+ </div>
470
+ </section>
471
+ );
472
+ }
473
+ `}),t.push({path:`src/components/PricingCard.tsx`,language:`typescript`,content:`import React from 'react';
474
+ import { Check } from 'lucide-react';
475
+
476
+ interface PricingCardProps {
477
+ planName: string;
478
+ price: string;
479
+ period?: string;
480
+ description?: string;
481
+ buttonText?: string;
482
+ features?: string[];
483
+ isPopular?: boolean;
484
+ onSelect?: () => void;
485
+ }
486
+
487
+ export function PricingCard({
488
+ planName,
489
+ price,
490
+ period = '/month',
491
+ description,
492
+ buttonText = 'Get Started',
493
+ features = [
494
+ 'Full visual design workbench',
495
+ 'High-resolution code export',
496
+ 'Responsive mobile layouts',
497
+ 'Interactive runtime prototype',
498
+ ],
499
+ isPopular = false,
500
+ onSelect,
501
+ }: PricingCardProps) {
502
+ return (
503
+ <article
504
+ style={{
505
+ display: 'flex',
506
+ flexDirection: 'column',
507
+ justifyContent: 'space-between',
508
+ padding: '1.75rem',
509
+ borderRadius: '16px',
510
+ backgroundColor: '#ffffff',
511
+ border: isPopular ? '2px solid #0071e3' : '1px solid #e5e5ea',
512
+ boxShadow: isPopular ? '0 8px 24px rgba(0, 113, 227, 0.12)' : '0 2px 8px rgba(0,0,0,0.04)',
513
+ }}
514
+ >
515
+ <div>
516
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.5rem' }}>
517
+ <h3 style={{ fontSize: '1.125rem', fontWeight: 600, color: '#1d1d1f' }}>{planName}</h3>
518
+ {isPopular && (
519
+ <span style={{ fontSize: '0.6875rem', fontWeight: 600, backgroundColor: '#0071e3', color: '#ffffff', padding: '0.2rem 0.6rem', borderRadius: '999px' }}>
520
+ Popular
521
+ </span>
522
+ )}
523
+ </div>
524
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: '0.25rem', marginBottom: '0.75rem' }}>
525
+ <span style={{ fontSize: '2.25rem', fontWeight: 700, letterSpacing: '-0.02em', color: '#1d1d1f' }}>{price}</span>
526
+ <span style={{ fontSize: '0.8125rem', color: '#86868b' }}>{period}</span>
527
+ </div>
528
+ {description && (
529
+ <p style={{ fontSize: '0.875rem', color: '#6e6e73', lineHeight: 1.5, marginBottom: '1.5rem' }}>
530
+ {description}
531
+ </p>
532
+ )}
533
+
534
+ <ul style={{ listStyle: 'none', padding: 0, margin: '0 0 1.5rem 0', display: 'flex', flexDirection: 'column', gap: '0.625rem' }}>
535
+ {features.map((feat, idx) => (
536
+ <li key={idx} style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.8125rem', color: '#1d1d1f' }}>
537
+ <Check style={{ width: '1rem', height: '1rem', color: '#0071e3', flexShrink: 0 }} />
538
+ <span>{feat}</span>
539
+ </li>
540
+ ))}
541
+ </ul>
542
+ </div>
543
+
544
+ <button
545
+ type="button"
546
+ onClick={onSelect}
547
+ style={{
548
+ width: '100%',
549
+ padding: '0.625rem 1rem',
550
+ borderRadius: '10px',
551
+ fontSize: '0.875rem',
552
+ fontWeight: 600,
553
+ border: 'none',
554
+ cursor: 'pointer',
555
+ transition: 'all 0.15s ease',
556
+ backgroundColor: isPopular ? '#0071e3' : '#f4f4f6',
557
+ color: isPopular ? '#ffffff' : '#1d1d1f',
558
+ }}
559
+ >
560
+ {buttonText}
561
+ </button>
562
+ </article>
563
+ );
564
+ }
565
+ `}),t.push({path:`src/components/MetricCard.tsx`,language:`typescript`,content:`import React from 'react';
566
+
567
+ interface MetricCardProps {
568
+ title: string;
569
+ value: string;
570
+ change?: string;
571
+ isPositive?: boolean;
572
+ timeframe?: string;
573
+ }
574
+
575
+ export function MetricCard({
576
+ title,
577
+ value,
578
+ change,
579
+ isPositive = true,
580
+ timeframe,
581
+ }: MetricCardProps) {
582
+ return (
583
+ <div style={{
584
+ padding: '1.25rem',
585
+ borderRadius: '14px',
586
+ backgroundColor: '#ffffff',
587
+ border: '1px solid #e5e5ea',
588
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
589
+ display: 'flex',
590
+ flexDirection: 'column',
591
+ justifyContent: 'space-between',
592
+ }}>
593
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.75rem' }}>
594
+ <span style={{ fontSize: '0.8125rem', fontWeight: 500, color: '#86868b' }}>{title}</span>
595
+ {change && (
596
+ <span style={{ fontSize: '0.75rem', fontWeight: 600, color: isPositive ? '#34c759' : '#ff3b30' }}>
597
+ {change}
598
+ </span>
599
+ )}
600
+ </div>
601
+ <div>
602
+ <div style={{ fontSize: '1.5rem', fontWeight: 700, letterSpacing: '-0.02em', color: '#1d1d1f' }}>{value}</div>
603
+ {timeframe && <div style={{ fontSize: '0.6875rem', color: '#86868b', marginTop: '0.125rem' }}>{timeframe}</div>}
604
+ </div>
605
+ </div>
606
+ );
607
+ }
608
+ `}),t.push({path:`src/components/DashboardCharts.tsx`,language:`typescript`,content:`import React from 'react';
609
+ import { TrendingUp, TrendingDown, ArrowRight, Sparkles, Check } from 'lucide-react';
610
+
611
+ export interface StatCardProps {
612
+ title: string;
613
+ value: string;
614
+ change?: string;
615
+ isPositive?: boolean;
616
+ timeframe?: string;
617
+ }
618
+
619
+ export function StatCard({
620
+ title,
621
+ value,
622
+ change = '+14.2%',
623
+ isPositive = true,
624
+ timeframe = 'vs last month',
625
+ }: StatCardProps) {
626
+ return (
627
+ <div style={{
628
+ padding: '1.25rem',
629
+ borderRadius: '16px',
630
+ backgroundColor: '#ffffff',
631
+ border: '1px solid #e5e5ea',
632
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
633
+ display: 'flex',
634
+ flexDirection: 'column',
635
+ justifyContent: 'space-between',
636
+ }}>
637
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.75rem' }}>
638
+ <span style={{ fontSize: '0.75rem', fontWeight: 600, color: '#86868b' }}>{title}</span>
639
+ <div style={{
640
+ width: '28px',
641
+ height: '28px',
642
+ borderRadius: '8px',
643
+ backgroundColor: 'rgba(0, 113, 227, 0.08)',
644
+ color: '#0071e3',
645
+ display: 'flex',
646
+ alignItems: 'center',
647
+ justifyContent: 'center',
648
+ }}>
649
+ <TrendingUp style={{ width: '14px', height: '14px' }} />
650
+ </div>
651
+ </div>
652
+ <div>
653
+ <div style={{ fontSize: '1.5rem', fontWeight: 800, letterSpacing: '-0.02em', color: '#1d1d1f' }}>{value}</div>
654
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem', marginTop: '0.25rem', fontSize: '0.6875rem' }}>
655
+ <span style={{
656
+ fontWeight: 700,
657
+ padding: '0.125rem 0.375rem',
658
+ borderRadius: '9999px',
659
+ backgroundColor: isPositive ? 'rgba(52, 199, 89, 0.12)' : 'rgba(255, 59, 48, 0.12)',
660
+ color: isPositive ? '#34c759' : '#ff3b30',
661
+ }}>
662
+ {change}
663
+ </span>
664
+ <span style={{ color: '#86868b' }}>{timeframe}</span>
665
+ </div>
666
+ </div>
667
+ </div>
668
+ );
669
+ }
670
+
671
+ export interface LineChartProps {
672
+ title?: string;
673
+ subtitle?: string;
674
+ data?: Array<{ label: string; value: number }>;
675
+ color?: string;
676
+ }
677
+
678
+ export function LineChart({
679
+ title = 'Revenue Growth',
680
+ subtitle = 'Monthly MRR performance',
681
+ data = [
682
+ { label: 'Jan', value: 24000 },
683
+ { label: 'Feb', value: 32500 },
684
+ { label: 'Mar', value: 28900 },
685
+ { label: 'Apr', value: 46200 },
686
+ { label: 'May', value: 54100 },
687
+ { label: 'Jun', value: 78900 },
688
+ ],
689
+ color = '#0071e3',
690
+ }: LineChartProps) {
691
+ const maxVal = Math.max(...data.map(d => d.value), 1);
692
+ const w = 400;
693
+ const h = 180;
694
+ const padL = 40;
695
+ const padR = 20;
696
+ const padT = 20;
697
+ const padB = 30;
698
+ const chartW = w - padL - padR;
699
+ const chartH = h - padT - padB;
700
+
701
+ const points = data.map((d, i) => ({
702
+ x: padL + (i / (data.length - 1)) * chartW,
703
+ y: padT + chartH - (d.value / maxVal) * chartH,
704
+ ...d,
705
+ }));
706
+
707
+ const path = points.reduce((acc, p, i) => {
708
+ if (i === 0) return \`M \${p.x} \${p.y}\`;
709
+ const prev = points[i - 1];
710
+ const cpX1 = prev.x + (p.x - prev.x) / 3;
711
+ const cpX2 = prev.x + ((p.x - prev.x) * 2) / 3;
712
+ return \`\${acc} C \${cpX1} \${prev.y}, \${cpX2} \${p.y}, \${p.x} \${p.y}\`;
713
+ }, '');
714
+
715
+ return (
716
+ <div style={{
717
+ padding: '1.25rem',
718
+ borderRadius: '16px',
719
+ backgroundColor: '#ffffff',
720
+ border: '1px solid #e5e5ea',
721
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
722
+ }}>
723
+ <div style={{ marginBottom: '0.75rem' }}>
724
+ <h4 style={{ fontSize: '0.875rem', fontWeight: 700, color: '#1d1d1f', margin: 0 }}>{title}</h4>
725
+ {subtitle && <p style={{ fontSize: '0.6875rem', color: '#86868b', margin: '0.25rem 0 0 0' }}>{subtitle}</p>}
726
+ </div>
727
+ <svg width="100%" height={h} viewBox={\`0 0 \${w} \${h}\`} style={{ overflow: 'visible' }}>
728
+ {[0, 0.5, 1].map((step, idx) => (
729
+ <line
730
+ key={idx}
731
+ x1={padL}
732
+ y1={padT + chartH * (1 - step)}
733
+ x2={padL + chartW}
734
+ y2={padT + chartH * (1 - step)}
735
+ stroke="#e5e5ea"
736
+ strokeDasharray="3 3"
737
+ />
738
+ ))}
739
+ <path d={path} fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" />
740
+ {points.map((p, i) => (
741
+ <g key={i}>
742
+ <circle cx={p.x} cy={p.y} r="4" fill="#ffffff" stroke={color} strokeWidth="2" />
743
+ <text x={p.x} y={h - 8} textAnchor="middle" fontSize="10" fill="#86868b">
744
+ {p.label}
745
+ </text>
746
+ </g>
747
+ ))}
748
+ </svg>
749
+ </div>
750
+ );
751
+ }
752
+
753
+ export interface DataTableProps {
754
+ title?: string;
755
+ columns?: string[];
756
+ rows?: Array<{ [key: string]: any }>;
757
+ }
758
+
759
+ export function DataTable({
760
+ title = 'Recent Activity',
761
+ columns = ['Customer', 'Plan', 'Amount', 'Status', 'Date'],
762
+ rows = [
763
+ { customer: 'Acme Corp', plan: 'Enterprise', amount: '$4,800.00', status: 'Completed', date: 'Just now' },
764
+ { customer: 'Linear Labs', plan: 'Pro Studio', amount: '$590.00', status: 'Completed', date: '5m ago' },
765
+ { customer: 'Vercel Edge', plan: 'Enterprise', amount: '$12,400.00', status: 'Processing', date: '22m ago' },
766
+ ],
767
+ }: DataTableProps) {
768
+ return (
769
+ <div style={{
770
+ padding: '1.25rem',
771
+ borderRadius: '16px',
772
+ backgroundColor: '#ffffff',
773
+ border: '1px solid #e5e5ea',
774
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
775
+ }}>
776
+ {title && <h4 style={{ fontSize: '0.875rem', fontWeight: 700, color: '#1d1d1f', marginBottom: '0.75rem' }}>{title}</h4>}
777
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.75rem' }}>
778
+ <thead>
779
+ <tr style={{ borderBottom: '1px solid #f4f4f6', color: '#86868b', textAlign: 'left' }}>
780
+ {columns.map((c, i) => (
781
+ <th key={i} style={{ padding: '0.5rem', fontWeight: 600 }}>{c}</th>
782
+ ))}
783
+ </tr>
784
+ </thead>
785
+ <tbody>
786
+ {rows.map((r, i) => (
787
+ <tr key={i} style={{ borderBottom: '1px solid #f4f4f6' }}>
788
+ <td style={{ padding: '0.5rem', fontWeight: 600, color: '#1d1d1f' }}>{r.customer || r.name}</td>
789
+ <td style={{ padding: '0.5rem', color: '#6e6e73' }}>{r.plan || r.category}</td>
790
+ <td style={{ padding: '0.5rem', fontFamily: 'monospace', fontWeight: 600 }}>{r.amount}</td>
791
+ <td style={{ padding: '0.5rem' }}>
792
+ <span style={{
793
+ padding: '0.125rem 0.5rem',
794
+ borderRadius: '9999px',
795
+ fontSize: '0.625rem',
796
+ fontWeight: 700,
797
+ backgroundColor: r.status === 'Completed' ? 'rgba(52, 199, 89, 0.12)' : 'rgba(255, 149, 0, 0.12)',
798
+ color: r.status === 'Completed' ? '#34c759' : '#ff9500',
799
+ }}>
800
+ {r.status}
801
+ </span>
802
+ </td>
803
+ <td style={{ padding: '0.5rem', color: '#86868b' }}>{r.date}</td>
804
+ </tr>
805
+ ))}
806
+ </tbody>
807
+ </table>
808
+ </div>
809
+ );
810
+ }
811
+ `});let m=(e.variables||[]).map(e=>`export const ${e.name} = ${JSON.stringify(e.sampleValue??e.defaultValue,null,2)};`).join(`
812
+
813
+ `);t.push({path:`src/data/variables.ts`,language:`typescript`,content:`// Auto-generated Cyrus Project Data & Variables\n\n${m||`export const defaultState = {};`}\n`}),t.push({path:`src/components/ProductCard.tsx`,language:`typescript`,content:`import React from 'react';
814
+ import { Star, ShoppingBag } from 'lucide-react';
815
+
816
+ export interface ProductCardProps {
817
+ title: string;
818
+ price: string;
819
+ image?: string;
820
+ rating?: number;
821
+ badge?: string;
822
+ onAddToCart?: () => void;
823
+ }
824
+
825
+ export function ProductCard({
826
+ title,
827
+ price,
828
+ image = 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=600&auto=format&fit=crop&q=80',
829
+ rating = 4.9,
830
+ badge,
831
+ onAddToCart,
832
+ }: ProductCardProps) {
833
+ return (
834
+ <div style={{
835
+ borderRadius: '16px',
836
+ backgroundColor: '#ffffff',
837
+ border: '1px solid #e5e5ea',
838
+ overflow: 'hidden',
839
+ boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
840
+ display: 'flex',
841
+ flexDirection: 'column',
842
+ }}>
843
+ <div style={{ position: 'relative', width: '100%', height: '180px', backgroundColor: '#f4f4f6' }}>
844
+ <img src={image} alt={title} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
845
+ {badge && (
846
+ <span style={{
847
+ position: 'absolute',
848
+ top: '10px',
849
+ left: '10px',
850
+ backgroundColor: '#0071e3',
851
+ color: '#fff',
852
+ fontSize: '10px',
853
+ fontWeight: 600,
854
+ padding: '2px 8px',
855
+ borderRadius: '99px',
856
+ }}>
857
+ {badge}
858
+ </span>
859
+ )}
860
+ </div>
861
+
862
+ <div style={{ padding: '16px', display: 'flex', flexDirection: 'column', flex: 1, justifyContent: 'space-between' }}>
863
+ <div>
864
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '6px' }}>
865
+ <Star style={{ width: '12px', height: '12px', fill: '#ff9500', color: '#ff9500' }} />
866
+ <span style={{ fontSize: '11px', fontWeight: 600, color: '#1d1d1f' }}>{rating}</span>
867
+ </div>
868
+ <h4 style={{ fontSize: '14px', fontWeight: 600, color: '#1d1d1f', marginBottom: '4px' }}>{title}</h4>
869
+ <div style={{ fontSize: '15px', fontWeight: 700, color: '#0071e3' }}>{price}</div>
870
+ </div>
871
+
872
+ <button
873
+ type="button"
874
+ onClick={onAddToCart}
875
+ style={{
876
+ marginTop: '12px',
877
+ width: '100%',
878
+ padding: '8px 12px',
879
+ borderRadius: '8px',
880
+ backgroundColor: '#f4f4f6',
881
+ color: '#1d1d1f',
882
+ fontSize: '12px',
883
+ fontWeight: 600,
884
+ border: 'none',
885
+ display: 'flex',
886
+ alignItems: 'center',
887
+ justifyContent: 'center',
888
+ gap: '6px',
889
+ cursor: 'pointer',
890
+ }}
891
+ >
892
+ <ShoppingBag style={{ width: '13px', height: '13px' }} />
893
+ <span>Add to Bag</span>
894
+ </button>
895
+ </div>
896
+ </div>
897
+ );
898
+ }
899
+ `}),t.push({path:`src/components/LoginForm.tsx`,language:`typescript`,content:`import React, { useState } from 'react';
900
+ import { Lock } from 'lucide-react';
901
+
902
+ export interface LoginFormProps {
903
+ title?: string;
904
+ subtitle?: string;
905
+ onSubmit?: (values: { email: string; pass: string }) => void;
906
+ }
907
+
908
+ export function LoginForm({
909
+ title = 'Sign In to Your Account',
910
+ subtitle = 'Enter your credentials to access workspace',
911
+ onSubmit,
912
+ }: LoginFormProps) {
913
+ const [email, setEmail] = useState('');
914
+ const [pass, setPass] = useState('');
915
+ const [isSubmitting, setIsSubmitting] = useState(false);
916
+
917
+ const handleSubmit = (e: React.FormEvent) => {
918
+ e.preventDefault();
919
+ setIsSubmitting(true);
920
+ setTimeout(() => {
921
+ setIsSubmitting(false);
922
+ onSubmit?.({ email, pass });
923
+ }, 600);
924
+ };
925
+
926
+ return (
927
+ <form onSubmit={handleSubmit} style={{
928
+ maxWidth: '400px',
929
+ margin: '0 auto',
930
+ padding: '32px',
931
+ borderRadius: '18px',
932
+ backgroundColor: '#ffffff',
933
+ border: '1px solid #e5e5ea',
934
+ boxShadow: '0 8px 30px rgba(0,0,0,0.06)',
935
+ display: 'flex',
936
+ flexDirection: 'column',
937
+ gap: '16px',
938
+ }}>
939
+ <div style={{ textAlign: 'center' }}>
940
+ <div style={{
941
+ width: '36px',
942
+ height: '36px',
943
+ borderRadius: '10px',
944
+ backgroundColor: '#0071e3',
945
+ color: '#ffffff',
946
+ display: 'inline-flex',
947
+ alignItems: 'center',
948
+ justifyContent: 'center',
949
+ marginBottom: '12px',
950
+ }}>
951
+ <Lock style={{ width: '18px', height: '18px' }} />
952
+ </div>
953
+ <h3 style={{ fontSize: '18px', fontWeight: 700, color: '#1d1d1f' }}>{title}</h3>
954
+ <p style={{ fontSize: '12px', color: '#86868b', marginTop: '4px' }}>{subtitle}</p>
955
+ </div>
956
+
957
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
958
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
959
+ <label style={{ fontSize: '11.5px', fontWeight: 600, color: '#6e6e73' }}>Email</label>
960
+ <input
961
+ type="email"
962
+ required
963
+ value={email}
964
+ onChange={(e) => setEmail(e.target.value)}
965
+ placeholder="name@company.com"
966
+ style={{
967
+ height: '36px',
968
+ padding: '0 12px',
969
+ borderRadius: '8px',
970
+ border: '1px solid #e5e5ea',
971
+ fontSize: '13px',
972
+ outline: 'none',
973
+ }}
974
+ />
975
+ </div>
976
+
977
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
978
+ <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '11.5px' }}>
979
+ <label style={{ fontWeight: 600, color: '#6e6e73' }}>Password</label>
980
+ <span style={{ color: '#0071e3', cursor: 'pointer' }}>Forgot?</span>
981
+ </div>
982
+ <input
983
+ type="password"
984
+ required
985
+ value={pass}
986
+ onChange={(e) => setPass(e.target.value)}
987
+ placeholder="••••••••••••"
988
+ style={{
989
+ height: '36px',
990
+ padding: '0 12px',
991
+ borderRadius: '8px',
992
+ border: '1px solid #e5e5ea',
993
+ fontSize: '13px',
994
+ outline: 'none',
995
+ }}
996
+ />
997
+ </div>
998
+ </div>
999
+
1000
+ <button
1001
+ type="submit"
1002
+ disabled={isSubmitting}
1003
+ style={{
1004
+ width: '100%',
1005
+ padding: '10px',
1006
+ borderRadius: '10px',
1007
+ backgroundColor: '#0071e3',
1008
+ color: '#ffffff',
1009
+ fontWeight: 600,
1010
+ fontSize: '13.5px',
1011
+ border: 'none',
1012
+ cursor: isSubmitting ? 'not-allowed' : 'pointer',
1013
+ opacity: isSubmitting ? 0.7 : 1,
1014
+ }}
1015
+ >
1016
+ {isSubmitting ? 'Signing In...' : 'Sign In'}
1017
+ </button>
1018
+ </form>
1019
+ );
1020
+ }
1021
+ `}),t.push({path:`src/components/Modal.tsx`,language:`typescript`,content:`import React, { useEffect } from 'react';
1022
+ import { X } from 'lucide-react';
1023
+
1024
+ interface ModalProps {
1025
+ isOpen: boolean;
1026
+ onClose: () => void;
1027
+ title: string;
1028
+ children: React.ReactNode;
1029
+ }
1030
+
1031
+ export function Modal({ isOpen, onClose, title, children }: ModalProps) {
1032
+ useEffect(() => {
1033
+ const handleKeyDown = (e: KeyboardEvent) => {
1034
+ if (e.key === 'Escape') onClose();
1035
+ };
1036
+ if (isOpen) window.addEventListener('keydown', handleKeyDown);
1037
+ return () => window.removeEventListener('keydown', handleKeyDown);
1038
+ }, [isOpen, onClose]);
1039
+
1040
+ if (!isOpen) return null;
1041
+
1042
+ return (
1043
+ <div
1044
+ onClick={onClose}
1045
+ style={{
1046
+ position: 'fixed',
1047
+ inset: 0,
1048
+ backgroundColor: 'rgba(0,0,0,0.45)',
1049
+ backdropFilter: 'blur(4px)',
1050
+ display: 'flex',
1051
+ alignItems: 'center',
1052
+ justifyContent: 'center',
1053
+ padding: '1.5rem',
1054
+ zIndex: 9999,
1055
+ }}
1056
+ >
1057
+ <div
1058
+ onClick={(e) => e.stopPropagation()}
1059
+ style={{
1060
+ width: '100%',
1061
+ maxWidth: '440px',
1062
+ backgroundColor: '#ffffff',
1063
+ borderRadius: '16px',
1064
+ border: '1px solid #e5e5ea',
1065
+ boxShadow: '0 20px 40px rgba(0,0,0,0.15)',
1066
+ overflow: 'hidden',
1067
+ }}
1068
+ >
1069
+ <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid #f4f4f6', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1070
+ <h3 style={{ fontSize: '0.9375rem', fontWeight: 600, color: '#1d1d1f' }}>{title}</h3>
1071
+ <button
1072
+ type="button"
1073
+ onClick={onClose}
1074
+ style={{
1075
+ width: '1.75rem',
1076
+ height: '1.75rem',
1077
+ borderRadius: '50%',
1078
+ backgroundColor: '#f4f4f6',
1079
+ border: 'none',
1080
+ cursor: 'pointer',
1081
+ display: 'flex',
1082
+ alignItems: 'center',
1083
+ justifyContent: 'center',
1084
+ color: '#86868b',
1085
+ }}
1086
+ >
1087
+ <X style={{ width: '1rem', height: '1rem' }} />
1088
+ </button>
1089
+ </div>
1090
+ <div style={{ padding: '1.25rem' }}>{children}</div>
1091
+ </div>
1092
+ </div>
1093
+ );
1094
+ }
1095
+ `}),t.push({path:`src/components/Footer.tsx`,language:`typescript`,content:`import React from 'react';
1096
+
1097
+ export function Footer() {
1098
+ return (
1099
+ <footer style={{
1100
+ width: '100%',
1101
+ borderTop: '1px solid #e5e5ea',
1102
+ backgroundColor: '#ffffff',
1103
+ padding: '2rem 1.5rem',
1104
+ marginTop: '4rem',
1105
+ fontSize: '0.8125rem',
1106
+ color: '#86868b',
1107
+ textAlign: 'center',
1108
+ }}>
1109
+ <div style={{ maxWidth: '1000px', margin: '0 auto', display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center' }}>
1110
+ <span style={{ fontWeight: 600, color: '#1d1d1f' }}>${e.name}</span>
1111
+ <span>© 2026 ${e.name}. All rights reserved. Generated with Cyrus.</span>
1112
+ </div>
1113
+ </footer>
1114
+ );
1115
+ }
1116
+ `}),e.pages.forEach(e=>{let n=p(e.name);t.push({path:`src/pages/${n}Page.tsx`,language:`typescript`,content:`import React from 'react';
1117
+ import { Hero } from '../components/Hero';
1118
+ import { PricingCard } from '../components/PricingCard';
1119
+ import { MetricCard } from '../components/MetricCard';
1120
+
1121
+ interface ${n}PageProps {
1122
+ onNavigate: (page: string) => void;
1123
+ onOpenModal: (title: string) => void;
1124
+ }
1125
+
1126
+ export function ${n}Page({ onNavigate, onOpenModal }: ${n}PageProps) {
1127
+ return (
1128
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '3rem' }}>
1129
+ ${f(e.sections)}
1130
+ </div>
1131
+ );
1132
+ }
1133
+ `})});let h=e.pages[0]?.slug||`home`;t.push({path:`src/App.tsx`,language:`typescript`,content:`import React, { useState } from 'react';
1134
+ import { Navbar } from './components/Navbar';
1135
+ import { Footer } from './components/Footer';
1136
+ import { Modal } from './components/Modal';
1137
+ ${e.pages.map(e=>`import { ${p(e.name)}Page } from './pages/${p(e.name)}Page';`).join(`
1138
+ `)}
1139
+
1140
+ export function App() {
1141
+ const [activePage, setActivePage] = useState('${h}');
1142
+ const [modalState, setModalState] = useState<{ isOpen: boolean; title: string }>({
1143
+ isOpen: false,
1144
+ title: '',
1145
+ });
1146
+
1147
+ const handleOpenModal = (title: string) => {
1148
+ setModalState({ isOpen: true, title });
1149
+ };
1150
+
1151
+ const handleCloseModal = () => {
1152
+ setModalState({ isOpen: false, title: '' });
1153
+ };
1154
+
1155
+ return (
1156
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
1157
+ <Navbar
1158
+ brandName="${e.name}"
1159
+ links={[${e.pages.map(e=>`'${e.name}'`).join(`, `)}]}
1160
+ activePage={activePage}
1161
+ onNavigate={(slug) => setActivePage(slug)}
1162
+ onOpenAuthModal={() => handleOpenModal('Sign in to ${e.name}')}
1163
+ />
1164
+
1165
+ <main style={{ flex: 1, maxWidth: '1200px', width: '100%', margin: '0 auto', padding: '2rem 1.5rem' }}>
1166
+ ${e.pages.map(e=>`{activePage === '${e.slug}' && <${p(e.name)}Page onNavigate={setActivePage} onOpenModal={handleOpenModal} />}`).join(`
1167
+ `)}
1168
+ </main>
1169
+
1170
+ <Footer />
1171
+
1172
+ <Modal
1173
+ isOpen={modalState.isOpen}
1174
+ onClose={handleCloseModal}
1175
+ title={modalState.title}
1176
+ >
1177
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
1178
+ <p style={{ fontSize: '0.875rem', color: '#6e6e73' }}>
1179
+ Enter your work email address to continue to your workspace.
1180
+ </p>
1181
+ <input
1182
+ type="email"
1183
+ placeholder="you@company.com"
1184
+ style={{
1185
+ width: '100%',
1186
+ height: '2.5rem',
1187
+ padding: '0 0.75rem',
1188
+ borderRadius: '8px',
1189
+ border: '1px solid #e5e5ea',
1190
+ fontSize: '0.875rem',
1191
+ outline: 'none',
1192
+ }}
1193
+ />
1194
+ <button
1195
+ type="button"
1196
+ onClick={handleCloseModal}
1197
+ style={{
1198
+ width: '100%',
1199
+ padding: '0.625rem',
1200
+ backgroundColor: '#0071e3',
1201
+ color: '#ffffff',
1202
+ borderRadius: '8px',
1203
+ border: 'none',
1204
+ fontWeight: 600,
1205
+ cursor: 'pointer',
1206
+ }}
1207
+ >
1208
+ Continue with Email
1209
+ </button>
1210
+ </div>
1211
+ </Modal>
1212
+ </div>
1213
+ );
1214
+ }
1215
+ `}),t.push({path:`README.md`,language:`json`,content:`# ${e.name}
1216
+
1217
+ Production application generated by **Cyrus** visual product builder.
1218
+
1219
+ ## Getting Started
1220
+
1221
+ 1. Install dependencies:
1222
+ \`\`\`bash
1223
+ npm install
1224
+ \`\`\`
1225
+
1226
+ 2. Start the local development server:
1227
+ \`\`\`bash
1228
+ npm run dev
1229
+ \`\`\`
1230
+
1231
+ 3. Build for production:
1232
+ \`\`\`bash
1233
+ npm run build
1234
+ \`\`\`
1235
+ `});for(let n of e.pages){let e=`${p(n.name)}Page`;t.push({path:`src/pages/${e}.tsx`,language:`typescript`,content:`import React from 'react';
1236
+ import { SeoHead } from '../components/SeoHead';
1237
+ import { Hero } from '../components/Hero';
1238
+ import { PricingCard } from '../components/PricingCard';
1239
+ import { MetricCard } from '../components/MetricCard';
1240
+ import { LoginForm } from '../components/LoginForm';
1241
+
1242
+ interface ${e}Props {
1243
+ onNavigate: (slug: string) => void;
1244
+ onOpenModal: (title: string) => void;
1245
+ }
1246
+
1247
+ export function ${e}({ onNavigate, onOpenModal }: ${e}Props) {
1248
+ return (
1249
+ <>
1250
+ <SeoHead title="${n.metaTitle||n.name}" description="${n.metaDescription||``}" />
1251
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '2.5rem' }}>
1252
+ ${f(n.sections)}
1253
+ </div>
1254
+ </>
1255
+ );
1256
+ }
1257
+ `})}return t}function f(e){return!e||e.length===0?` <div className="p-8 text-center text-gray-500">Page Content Container</div>`:e.map(e=>{if(e.type===`hero`)return`<Hero
1258
+ badge="${e.props.badge||`Engineering`}"
1259
+ headline="${e.props.headline||`Transform ideas into shipped software.`}"
1260
+ subhead="${e.props.subhead||``}"
1261
+ primaryBtnText="${e.props.primaryBtnText||`Start Free Trial`}"
1262
+ onPrimaryClick={() => onNavigate('pricing')}
1263
+ secondaryBtnText="${e.props.secondaryBtnText||`View Pricing`}"
1264
+ onSecondaryClick={() => onNavigate('pricing')}
1265
+ />`;if(e.type===`grid`)return`<div className="grid-responsive">
1266
+ ${(e.children||[]).map(e=>e.type===`pricingCard`?` <PricingCard
1267
+ planName="${e.props.planName||`Pro`}"
1268
+ price="${e.props.price||`$29`}"
1269
+ period="${e.props.period||`/month`}"
1270
+ description="${e.props.description||``}"
1271
+ buttonText="${e.props.buttonText||`Subscribe`}"
1272
+ onSelect={() => onOpenModal('Subscribe to ${e.props.planName||`Plan`}')}
1273
+ />`:e.type===`metricCard`?` <MetricCard
1274
+ title="${e.props.title||`Metric`}"
1275
+ value="${e.props.value||`$0`}"
1276
+ change="${e.props.change||``}"
1277
+ timeframe="${e.props.timeframe||``}"
1278
+ />`:` <div style={{ padding: '1.5rem', backgroundColor: '#fff', border: '1px solid #e5e5ea', borderRadius: '14px' }}>
1279
+ <h4>${e.name}</h4>
1280
+ </div>`).join(`
1281
+ `)}
1282
+ </div>`;if(e.type===`pricingCard`)return`<div style={{ maxWidth: '400px', margin: '0 auto', width: '100%' }}>
1283
+ <PricingCard
1284
+ planName="${e.props.planName||`Pro`}"
1285
+ price="${e.props.price||`$29`}"
1286
+ period="${e.props.period||`/month`}"
1287
+ description="${e.props.description||``}"
1288
+ buttonText="${e.props.buttonText||`Get Started`}"
1289
+ onSelect={() => onOpenModal('Subscribe to ${e.props.planName||`Plan`}')}
1290
+ />
1291
+ </div>`;if(e.collectionBinding){let{variableKey:t=`products`,columns:n=3}=e.collectionBinding;return`<div style={{ display: 'grid', gridTemplateColumns: 'repeat(${n}, 1fr)', gap: '1.5rem' }}>
1292
+ {${t}.map((item, idx) => (
1293
+ <ProductCard
1294
+ key={idx}
1295
+ title={item.name || item.title || 'Product Item'}
1296
+ price={item.price || '$0.00'}
1297
+ image={item.image}
1298
+ rating={item.rating}
1299
+ badge={item.badge}
1300
+ onAddToCart={() => onOpenModal('Added ' + (item.name || 'item') + ' to bag')}
1301
+ />
1302
+ ))}
1303
+ </div>`}if(e.type===`loginForm`)return`<div style={{ maxWidth: '420px', margin: '2rem auto', width: '100%' }}>
1304
+ <LoginForm
1305
+ title="${e.props.title||`Sign In to Your Account`}"
1306
+ subtitle="${e.props.subtitle||`Enter your email and password`}"
1307
+ onSubmit={(creds) => onOpenModal('Signed in successfully as ' + creds.email)}
1308
+ />
1309
+ </div>`;if(e.type===`signupForm`)return`<div style={{ maxWidth: '420px', margin: '2rem auto', width: '100%' }}>
1310
+ <LoginForm
1311
+ title="${e.props.title||`Create Your Account`}"
1312
+ subtitle="${e.props.subtitle||`Start building with Cyrus`}"
1313
+ onSubmit={(creds) => onOpenModal('Account created for ' + creds.email)}
1314
+ />
1315
+ </div>`;if(e.type===`contactForm`)return`<div style={{ maxWidth: '460px', margin: '2rem auto', width: '100%', padding: '2rem', backgroundColor: '#fff', borderRadius: '18px', border: '1px solid #e5e5ea' }}>
1316
+ <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: '#1d1d1f', marginBottom: '0.5rem' }}>${e.props.title||`Contact Our Team`}</h3>
1317
+ <p style={{ fontSize: '0.875rem', color: '#86868b', marginBottom: '1.5rem' }}>${e.props.subtitle||`We respond within 24 hours.`}</p>
1318
+ <form onSubmit={(e) => { e.preventDefault(); onOpenModal('Message sent successfully!'); }} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
1319
+ <input required type="text" placeholder="Your Name" style={{ height: '38px', padding: '0 12px', borderRadius: '8px', border: '1px solid #e5e5ea', fontSize: '13px' }} />
1320
+ <input required type="email" placeholder="Your Email" style={{ height: '38px', padding: '0 12px', borderRadius: '8px', border: '1px solid #e5e5ea', fontSize: '13px' }} />
1321
+ <textarea required rows={3} placeholder="How can we help?" style={{ padding: '10px 12px', borderRadius: '8px', border: '1px solid #e5e5ea', fontSize: '13px', resize: 'none' }} />
1322
+ <button type="submit" style={{ padding: '10px', borderRadius: '10px', backgroundColor: '#0071e3', color: '#fff', fontWeight: 600, border: 'none', cursor: 'pointer' }}>
1323
+ ${e.props.submitLabel||`Send Message`}
1324
+ </button>
1325
+ </form>
1326
+ </div>`;let t=e.accessibility||{},n=t.semanticTag&&t.semanticTag!==`div`?t.semanticTag:`div`;return`<${n}${t.ariaRole&&t.ariaRole!==`presentation`?` role="${t.ariaRole}"`:``}${t.ariaLabel?` aria-label="${t.ariaLabel}"`:``} style={{ padding: '1.5rem', backgroundColor: '#ffffff', borderRadius: '14px', border: '1px solid #e5e5ea' }}>
1327
+ <h3 style={{ fontSize: '1.125rem', fontWeight: 600, color: '#1d1d1f' }}>${e.name}</h3>
1328
+ </${n}>`}).join(`
1329
+
1330
+ `)}function p(e){return e.replace(/[^a-zA-Z0-9]/g,` `).split(` `).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(``)||`Page`}export{i,u as n,a as r,d as t};