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.
- package/README.md +37 -0
- package/dist/assets/AuthDialog-DMFwGmtj.js +1 -0
- package/dist/assets/CanvaSaveModal-BqSV1mos.js +9 -0
- package/dist/assets/CodingWorkspace-DXrWaTKZ.js +6 -0
- package/dist/assets/CreateAppDialog-DrBGyMVl.js +1236 -0
- package/dist/assets/CreateProjectDialog-JxYrZUv2.js +1 -0
- package/dist/assets/HistoryTimelineDialog-CAe6Hwyd.js +1 -0
- package/dist/assets/ImportModal-Cqy4Yhch.js +83 -0
- package/dist/assets/KeyboardShortcutsDialog-DqFAVzC4.js +1 -0
- package/dist/assets/LiveRuntimeWorkspace-yO2gI0_O.js +1 -0
- package/dist/assets/ShareExportDialog-BKmEqa2s.js +1 -0
- package/dist/assets/UserProfileDialog-BbU1jbKF.js +1 -0
- package/dist/assets/WelcomeOnboardingModal-BhTkmyMT.js +1 -0
- package/dist/assets/index-BewtY0Hs.js +793 -0
- package/dist/assets/index-CLJJIBPe.css +2 -0
- package/dist/assets/reactTsGenerator-CkYUg1Bs.js +1330 -0
- package/dist/assets/rolldown-runtime-W7wSyTde.js +1 -0
- package/dist/assets/themes-CNabH9CH.js +1 -0
- package/dist/assets/typechecker-BbGTunMD.js +2 -0
- package/dist/assets/vendor-firebase-DHYCsgNh.js +32 -0
- package/dist/assets/vendor-icons-M_KBtuE-.js +13 -0
- package/dist/assets/vendor-jszip-Cv4gVObP.js +2 -0
- package/dist/assets/vendor-react-BTEcvIpc.js +227 -0
- package/dist/assets/vendor-utils-3OgY5Lif.js +1 -0
- package/dist/favicon.svg +1 -0
- package/dist/icons.svg +24 -0
- package/dist/index.html +50 -0
- package/dist/login-bg.png +0 -0
- package/dist/test-sdk-sandbox.html +125 -0
- package/package.json +65 -0
|
@@ -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};
|