@easybits.cloud/html-tailwind-generator 0.2.119 → 0.2.121

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.
@@ -54,9 +54,9 @@ var LANDING_THEMES = [
54
54
  colors: {
55
55
  primary: "#a78bfa",
56
56
  "primary-light": "#c4b5fd",
57
- "primary-dark": "#8b5cf6",
57
+ "primary-dark": "#7c3aed",
58
58
  secondary: "#9ca3af",
59
- accent: "#a78bfa",
59
+ accent: "#f472b6",
60
60
  surface: "#111827",
61
61
  "surface-alt": "#1f2937",
62
62
  "on-surface": "#f9fafb",
@@ -72,12 +72,12 @@ var LANDING_THEMES = [
72
72
  "primary-light": "#22c55e",
73
73
  "primary-dark": "#15803d",
74
74
  secondary: "#a3a3a3",
75
- accent: "#16a34a",
75
+ accent: "#f59e0b",
76
76
  surface: "#0a0a0a",
77
77
  "surface-alt": "#171717",
78
78
  "on-surface": "#fafafa",
79
79
  "on-surface-muted": "#a3a3a3",
80
- "on-primary": "#000000"
80
+ "on-primary": "#ffffff"
81
81
  }
82
82
  },
83
83
  {
@@ -88,7 +88,7 @@ var LANDING_THEMES = [
88
88
  "primary-light": "#db2777",
89
89
  "primary-dark": "#9d174d",
90
90
  secondary: "#6b7280",
91
- accent: "#be185d",
91
+ accent: "#8b5cf6",
92
92
  surface: "#ffffff",
93
93
  "surface-alt": "#fdf2f8",
94
94
  "on-surface": "#1f2937",
@@ -97,6 +97,29 @@ var LANDING_THEMES = [
97
97
  }
98
98
  }
99
99
  ];
100
+ var THEME_DESCRIPTIONS = {
101
+ minimal: "Clean, light, professional \u2014 white surfaces, subtle grays, minimal decoration",
102
+ calido: "Warm tones \u2014 amber/orange primary, cream surfaces, cozy and inviting feel",
103
+ oceano: "Cool blue tones \u2014 deep ocean blues, clean white surfaces, professional and fresh",
104
+ noche: "DARK theme \u2014 very dark surfaces, purple/violet accents, moody and modern. Surfaces are dark, text is light.",
105
+ bosque: "DARK nature theme \u2014 black surfaces, vibrant green primary, amber accents. Surfaces are dark, text is light.",
106
+ rosa: "Soft pink/rose tones \u2014 delicate, feminine, elegant with light surfaces"
107
+ };
108
+ function buildThemePromptContext(themeName) {
109
+ const desc = THEME_DESCRIPTIONS[themeName] || themeName;
110
+ const lines = [];
111
+ lines.push(`Active theme: "${themeName}" \u2014 ${desc}`);
112
+ lines.push("The CSS variables for this theme are already loaded. Just use the semantic classes (bg-primary, text-on-surface, bg-accent, etc.). The theme handles the actual color values.");
113
+ const themeObj = LANDING_THEMES.find((t) => t.id === themeName);
114
+ if (themeObj) {
115
+ lines.push("Actual color values for this theme:");
116
+ for (const [key, hex] of Object.entries(themeObj.colors)) {
117
+ lines.push(` - ${key}: ${hex}`);
118
+ }
119
+ lines.push("Use these values to understand the visual mood. Generate designs that COMPLEMENT these specific colors.");
120
+ }
121
+ return lines.join("\n");
122
+ }
100
123
  function parseHex(hex) {
101
124
  return {
102
125
  r: parseInt(hex.slice(1, 3), 16),
@@ -184,9 +207,10 @@ ${buildCssVars(theme.colors)}
184
207
 
185
208
  export {
186
209
  LANDING_THEMES,
210
+ buildThemePromptContext,
187
211
  buildCustomTheme,
188
212
  buildCustomThemeCss,
189
213
  buildThemeCss,
190
214
  buildSingleThemeCss
191
215
  };
192
- //# sourceMappingURL=chunk-D7YHQJET.js.map
216
+ //# sourceMappingURL=chunk-F7QKVJ7F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/themes.ts"],"sourcesContent":["export interface LandingTheme {\n id: string;\n label: string;\n colors: {\n primary: string;\n \"primary-light\": string;\n \"primary-dark\": string;\n secondary: string;\n accent: string;\n surface: string;\n \"surface-alt\": string;\n \"on-surface\": string;\n \"on-surface-muted\": string;\n \"on-primary\": string;\n };\n}\n\nexport const LANDING_THEMES: LandingTheme[] = [\n {\n id: \"minimal\",\n label: \"Minimal\",\n colors: {\n primary: \"#18181b\",\n \"primary-light\": \"#3f3f46\",\n \"primary-dark\": \"#09090b\",\n secondary: \"#71717a\",\n accent: \"#2563eb\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#f4f4f5\",\n \"on-surface\": \"#18181b\",\n \"on-surface-muted\": \"#71717a\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"calido\",\n label: \"Cálido\",\n colors: {\n primary: \"#9a3412\",\n \"primary-light\": \"#c2410c\",\n \"primary-dark\": \"#7c2d12\",\n secondary: \"#78716c\",\n accent: \"#d97706\",\n surface: \"#fafaf9\",\n \"surface-alt\": \"#f5f5f4\",\n \"on-surface\": \"#1c1917\",\n \"on-surface-muted\": \"#78716c\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"oceano\",\n label: \"Océano\",\n colors: {\n primary: \"#0f4c75\",\n \"primary-light\": \"#1a6fa0\",\n \"primary-dark\": \"#0a3555\",\n secondary: \"#6b7280\",\n accent: \"#0d9488\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#f0fdfa\",\n \"on-surface\": \"#0f172a\",\n \"on-surface-muted\": \"#64748b\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"noche\",\n label: \"Noche\",\n colors: {\n primary: \"#a78bfa\",\n \"primary-light\": \"#c4b5fd\",\n \"primary-dark\": \"#7c3aed\",\n secondary: \"#9ca3af\",\n accent: \"#f472b6\",\n surface: \"#111827\",\n \"surface-alt\": \"#1f2937\",\n \"on-surface\": \"#f9fafb\",\n \"on-surface-muted\": \"#9ca3af\",\n \"on-primary\": \"#111827\",\n },\n },\n {\n id: \"bosque\",\n label: \"Bosque\",\n colors: {\n primary: \"#16a34a\",\n \"primary-light\": \"#22c55e\",\n \"primary-dark\": \"#15803d\",\n secondary: \"#a3a3a3\",\n accent: \"#f59e0b\",\n surface: \"#0a0a0a\",\n \"surface-alt\": \"#171717\",\n \"on-surface\": \"#fafafa\",\n \"on-surface-muted\": \"#a3a3a3\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"rosa\",\n label: \"Rosa\",\n colors: {\n primary: \"#be185d\",\n \"primary-light\": \"#db2777\",\n \"primary-dark\": \"#9d174d\",\n secondary: \"#6b7280\",\n accent: \"#8b5cf6\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#fdf2f8\",\n \"on-surface\": \"#1f2937\",\n \"on-surface-muted\": \"#6b7280\",\n \"on-primary\": \"#ffffff\",\n },\n },\n];\n\nexport const THEME_DESCRIPTIONS: Record<string, string> = {\n minimal: \"Clean, light, professional — white surfaces, subtle grays, minimal decoration\",\n calido: \"Warm tones — amber/orange primary, cream surfaces, cozy and inviting feel\",\n oceano: \"Cool blue tones — deep ocean blues, clean white surfaces, professional and fresh\",\n noche: \"DARK theme — very dark surfaces, purple/violet accents, moody and modern. Surfaces are dark, text is light.\",\n bosque: \"DARK nature theme — black surfaces, vibrant green primary, amber accents. Surfaces are dark, text is light.\",\n rosa: \"Soft pink/rose tones — delicate, feminine, elegant with light surfaces\",\n};\n\n/**\n * Build a prompt context string describing the active theme + its hex values.\n * Used by both generate and refine to give the AI concrete color awareness.\n */\nexport function buildThemePromptContext(themeName: string): string {\n const desc = THEME_DESCRIPTIONS[themeName] || themeName;\n const lines: string[] = [];\n lines.push(`Active theme: \"${themeName}\" — ${desc}`);\n lines.push(\"The CSS variables for this theme are already loaded. Just use the semantic classes (bg-primary, text-on-surface, bg-accent, etc.). The theme handles the actual color values.\");\n const themeObj = LANDING_THEMES.find(t => t.id === themeName);\n if (themeObj) {\n lines.push(\"Actual color values for this theme:\");\n for (const [key, hex] of Object.entries(themeObj.colors)) {\n lines.push(` - ${key}: ${hex}`);\n }\n lines.push(\"Use these values to understand the visual mood. Generate designs that COMPLEMENT these specific colors.\");\n }\n return lines.join(\"\\n\");\n}\n\nexport interface CustomColors {\n primary: string;\n secondary?: string;\n accent?: string;\n surface?: string;\n}\n\nfunction parseHex(hex: string) {\n return {\n r: parseInt(hex.slice(1, 3), 16),\n g: parseInt(hex.slice(3, 5), 16),\n b: parseInt(hex.slice(5, 7), 16),\n };\n}\n\nfunction toHex(r: number, g: number, b: number) {\n return `#${[r, g, b].map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction luminance(hex: string) {\n const { r, g, b } = parseHex(hex);\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n}\n\nfunction lighten(hex: string, amount = 40) {\n const { r, g, b } = parseHex(hex);\n return toHex(r + amount, g + amount, b + amount);\n}\n\nfunction darken(hex: string, amount = 40) {\n const { r, g, b } = parseHex(hex);\n return toHex(r - amount, g - amount, b - amount);\n}\n\nexport function buildCustomTheme(colors: CustomColors): LandingTheme {\n const { primary, secondary = \"#f59e0b\", accent = \"#06b6d4\", surface = \"#ffffff\" } = colors;\n\n const onPrimary = luminance(primary) > 0.5 ? \"#111827\" : \"#ffffff\";\n const surfaceLum = luminance(surface);\n const isDarkSurface = surfaceLum < 0.5;\n\n return {\n id: \"custom\",\n label: \"Custom\",\n colors: {\n primary,\n \"primary-light\": lighten(primary),\n \"primary-dark\": darken(primary),\n secondary,\n accent,\n surface,\n \"surface-alt\": isDarkSurface ? lighten(surface, 20) : darken(surface, 5),\n \"on-surface\": isDarkSurface ? \"#f1f5f9\" : \"#111827\",\n \"on-surface-muted\": isDarkSurface ? \"#94a3b8\" : \"#6b7280\",\n \"on-primary\": onPrimary,\n },\n };\n}\n\nexport function buildCustomThemeCss(colors: CustomColors): string {\n const theme = buildCustomTheme(colors);\n return `[data-theme=\"custom\"] {\\n${buildCssVars(theme.colors)}\\n}`;\n}\n\n/** CSS custom properties for a theme */\nfunction buildCssVars(colors: LandingTheme[\"colors\"]): string {\n return Object.entries(colors)\n .map(([k, v]) => ` --color-${k}: ${v};`)\n .join(\"\\n\");\n}\n\n/** Build the tailwind.config JS object string for TW v3 CDN */\nfunction buildTailwindConfig(): string {\n const colorEntries = Object.keys(LANDING_THEMES[0].colors)\n .map((k) => ` '${k}': 'var(--color-${k})'`)\n .join(\",\\n\");\n\n return `{\n theme: {\n extend: {\n colors: {\n${colorEntries}\n }\n }\n }\n }`;\n}\n\nexport function buildThemeCss(): { css: string; tailwindConfig: string } {\n const defaultTheme = LANDING_THEMES[0];\n\n const overrides = LANDING_THEMES.slice(1)\n .map((t) => `[data-theme=\"${t.id}\"] {\\n${buildCssVars(t.colors)}\\n}`)\n .join(\"\\n\\n\");\n\n const css = `:root {\\n${buildCssVars(defaultTheme.colors)}\\n}\\n\\n${overrides}`;\n return { css, tailwindConfig: buildTailwindConfig() };\n}\n\nexport function buildSingleThemeCss(themeId: string): { css: string; tailwindConfig: string } {\n const theme = LANDING_THEMES.find((t) => t.id === themeId) || LANDING_THEMES[0];\n const css = `:root {\\n${buildCssVars(theme.colors)}\\n}`;\n return { css, tailwindConfig: buildTailwindConfig() };\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildDeployHtml
3
- } from "./chunk-YYHFKHOQ.js";
3
+ } from "./chunk-R2XLJYBZ.js";
4
4
 
5
5
  // src/deploy.ts
6
6
  async function deployToS3(options) {
@@ -39,4 +39,4 @@ export {
39
39
  deployToS3,
40
40
  deployToEasyBits
41
41
  };
42
- //# sourceMappingURL=chunk-YTVJTJRN.js.map
42
+ //# sourceMappingURL=chunk-FIQOLRH3.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  buildPreviewHtml
3
- } from "./chunk-YYHFKHOQ.js";
3
+ } from "./chunk-R2XLJYBZ.js";
4
4
  import {
5
5
  LANDING_THEMES
6
- } from "./chunk-D7YHQJET.js";
6
+ } from "./chunk-F7QKVJ7F.js";
7
7
 
8
8
  // src/components/Canvas.tsx
9
9
  import { useRef, useEffect, useCallback, useState, forwardRef, useImperativeHandle } from "react";
@@ -1368,4 +1368,4 @@ export {
1368
1368
  ViewportToggle,
1369
1369
  useUndoStack
1370
1370
  };
1371
- //# sourceMappingURL=chunk-TDZO6X4O.js.map
1371
+ //# sourceMappingURL=chunk-JTK6WJP2.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
- LANDING_THEMES
3
- } from "./chunk-D7YHQJET.js";
2
+ buildThemePromptContext
3
+ } from "./chunk-F7QKVJ7F.js";
4
4
  import {
5
5
  dataUrlToImagePart,
6
6
  streamGenerate
@@ -123,29 +123,11 @@ IMPORTANT: Make each section VISUALLY UNIQUE \u2014 different layouts, different
123
123
  Think like a premium design agency creating a $50K landing page.
124
124
  NO generic Bootstrap layouts. Use creative grids, bento layouts, overlapping elements, asymmetric columns.`;
125
125
  }
126
- var THEME_DESCRIPTIONS = {
127
- minimal: "Clean, light, professional \u2014 white surfaces, subtle grays, minimal decoration",
128
- calido: "Warm tones \u2014 amber/orange primary, cream surfaces, cozy and inviting feel",
129
- oceano: "Cool blue tones \u2014 deep ocean blues, clean white surfaces, professional and fresh",
130
- noche: "DARK theme \u2014 very dark surfaces, purple/violet accents, moody and modern. Surfaces are dark, text is light.",
131
- bosque: "Nature-inspired \u2014 deep green primary, warm earth tones, organic and grounded",
132
- rosa: "Soft pink/rose tones \u2014 delicate, feminine, elegant with light surfaces"
133
- };
134
126
  function buildVisualContext(themeName, brandKit) {
135
127
  if (!themeName && !brandKit) return "";
136
128
  const lines = ["\n\n## Visual Context \u2014 MANDATORY"];
137
129
  if (themeName && themeName !== "custom") {
138
- const desc = THEME_DESCRIPTIONS[themeName] || themeName;
139
- lines.push(`Active theme: "${themeName}" \u2014 ${desc}`);
140
- lines.push("The CSS variables for this theme are already loaded. Just use the semantic classes (bg-primary, text-on-surface, bg-accent, etc.) as described in the COLOR SYSTEM above. The theme handles the actual color values.");
141
- const themeObj = LANDING_THEMES.find((t) => t.id === themeName);
142
- if (themeObj) {
143
- lines.push("Actual color values for this theme:");
144
- for (const [key, hex] of Object.entries(themeObj.colors)) {
145
- lines.push(` - ${key}: ${hex}`);
146
- }
147
- lines.push("Use these values to understand the visual mood. Generate designs that COMPLEMENT these specific colors.");
148
- }
130
+ lines.push(buildThemePromptContext(themeName));
149
131
  }
150
132
  if (brandKit?.fonts) {
151
133
  const { heading, body } = brandKit.fonts;
@@ -206,4 +188,4 @@ export {
206
188
  PROMPT_SUFFIX,
207
189
  generateLanding
208
190
  };
209
- //# sourceMappingURL=chunk-WI62YIOF.js.map
191
+ //# sourceMappingURL=chunk-PERVSTVF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generate.ts"],"sourcesContent":["import { streamGenerate, dataUrlToImagePart, extractJsonObjects } from \"./streamCore\";\nimport { buildThemePromptContext } from \"./themes\";\nimport type { Section3 } from \"./types\";\n\nexport { extractJsonObjects };\n\nexport const SYSTEM_PROMPT = `You are a world-class web designer who creates AWARD-WINNING landing pages. Your designs win Awwwards, FWA, and CSS Design Awards. You think in terms of visual hierarchy, whitespace, and emotional impact.\n\nRULES:\n- Each section is a complete <section> tag with Tailwind CSS classes\n- Use Tailwind CDN classes ONLY (no custom CSS, no @apply, no @import, no @tailwind directives)\n- NO JavaScript, only HTML+Tailwind\n- Each section must be independent and self-contained\n- Responsive: mobile-first with sm/md/lg/xl breakpoints\n- All text content in Spanish unless the prompt specifies otherwise\n- Use real-looking content (not Lorem ipsum) — make it specific to the prompt\n\nRESPONSIVE — MANDATORY:\n- EVERY grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 (NEVER grid-cols-3 alone)\n- EVERY flex row: flex flex-col md:flex-row (NEVER flex flex-row alone)\n- Text sizes: text-3xl md:text-5xl lg:text-7xl (NEVER text-7xl alone)\n- Images: w-full h-auto object-cover max-w-full\n- Padding: px-4 md:px-8 lg:px-16 (NEVER px-16 alone)\n- Hide decorative on mobile if breaks layout: hidden md:block\n\nIMAGES — CRITICAL:\n- EVERY image MUST use: <img data-image-query=\"english search query\" alt=\"description\" class=\"w-full h-auto object-cover rounded-xl\"/>\n- NEVER use <img> without data-image-query\n- NEVER include a src attribute — the system auto-replaces data-image-query with a real image URL\n- Queries must be generic stock-photo friendly (e.g. \"modern office\" not \"Juan's cybercafe\")\n- For avatar-like elements, use colored divs with initials instead of img tags (e.g. <div class=\"w-10 h-10 rounded-full bg-primary flex items-center justify-center text-on-primary font-bold\">JD</div>)\n\nIMAGE OVERLAYS — CRITICAL:\n- When using images as backgrounds or behind text, ALWAYS add a gradient overlay for text readability\n- Pattern: <div class=\"relative\"><img data-image-query=\"...\" alt=\"...\" class=\"absolute inset-0 w-full h-full object-cover\"/><div class=\"absolute inset-0 bg-gradient-to-r from-primary/80 to-transparent\"></div><div class=\"relative z-10\">...text...</div></div>\n- NEVER place text directly on images without an overlay\n- For hero sections with background images: use bg-gradient-to-t from-primary-dark/90 via-primary-dark/50 to-transparent\n- For testimonial/quote backgrounds: use bg-surface/90 backdrop-blur-sm on the card\n\nCOLOR SYSTEM — CRITICAL (READ CAREFULLY):\n- Use semantic color classes: bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary, bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted, bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded Tailwind color classes: NO bg-gray-*, bg-black, bg-white, bg-indigo-*, bg-blue-*, bg-purple-*, text-gray-*, text-black, text-white, etc.\n- The ONLY exception: border-gray-200 or border-gray-700 for subtle dividers.\n- ALL backgrounds MUST use: bg-primary, bg-primary-dark, bg-surface, bg-surface-alt\n- ALL text MUST use: text-on-surface, text-on-surface-muted, text-on-primary, text-accent. Use text-primary ONLY on bg-surface/bg-surface-alt (it's the same hue as bg-primary — invisible on primary backgrounds).\n- CONTRAST RULE: on bg-primary or bg-primary-dark → use ONLY text-on-primary. On bg-surface or bg-surface-alt → use text-on-surface, text-on-surface-muted, or text-primary. NEVER use text-primary on bg-primary — they are the SAME COLOR. NEVER put text-on-surface on bg-primary or text-on-primary on bg-surface. text-accent is decorative — use sparingly on bg-surface/bg-surface-alt only.\n- ANTI-PATTERN: NEVER put bg-primary on BOTH the section AND elements inside it. If section is bg-primary, inner cards/elements should be bg-surface. If section is bg-surface, cards can use bg-surface-alt or bg-primary.\n- For gradients: from-primary to-primary-dark, from-surface to-surface-alt\n- For hover: hover:bg-primary-dark, hover:bg-primary-light\n\nCOLOR VARIETY — MANDATORY:\n- ALTERNATE section backgrounds: bg-surface → bg-primary → bg-surface-alt → bg-primary-dark → bg-surface\n- Use bg-accent for at least ONE section (CTA or highlight section)\n- Use bg-secondary for at least ONE section (features or stats)\n- Cards on dark sections (bg-primary, bg-primary-dark) MUST be bg-surface (light cards on dark bg = visual pop)\n- Cards on light sections (bg-surface) can use bg-surface-alt or bg-primary-light\n- Use text-accent for decorative elements: labels, badges, icons, highlights, underlines\n- Use text-secondary for secondary information, tags, category labels\n- Gradients: mix colors creatively — from-primary to-accent for CTAs, from-secondary to-primary for headers\n- Buttons: primary CTA = bg-accent text-on-accent, secondary CTA = bg-secondary text-on-secondary or border-primary\n\nDESIGN PHILOSOPHY — what separates good from GREAT:\n- WHITESPACE is your best friend. Generous padding (py-24, py-32, px-8). Let elements breathe.\n- CONTRAST: mix dark sections with light ones. Alternate bg-primary and bg-surface sections.\n- TYPOGRAPHY: use extreme size differences for hierarchy (text-7xl headline next to text-sm label)\n- DEPTH: overlapping elements, negative margins (-mt-12), z-index layering, shadows\n- ASYMMETRY: avoid centering everything. Use grid-cols-5 with col-span-3 + col-span-2. Offset elements.\n- TEXTURE: use subtle patterns, gradients, border treatments, rounded-3xl mixed with sharp edges\n- Each section should have a COMPLETELY DIFFERENT layout from the others\n\nSECTION LAYOUT — CRITICAL:\n- Each <section> must be full-width (bg goes edge-to-edge). NO max-w on the section itself.\n- Constrain content inside with a wrapper div: <section class=\"bg-primary py-24\"><div class=\"max-w-7xl mx-auto px-4 md:px-8\">...content...</div></section>\n- EVERY section follows this pattern. The <section> handles bg color + vertical padding. The inner <div> handles horizontal padding + max-width.\n\nTESTIMONIALS SECTION:\n- Cards MUST use bg-surface or bg-surface-alt with text-on-surface\n- If section bg is bg-primary or bg-primary-dark, cards MUST be bg-surface (light cards on dark bg)\n- Quote text: text-on-surface, italic\n- Avatar: colored div with initials (bg-accent text-on-primary or bg-primary-light text-on-primary)\n- Name: text-on-surface font-semibold. Role/company: text-on-surface-muted\n- NEVER use same dark bg for both section AND cards\n\nHERO SECTION — your masterpiece:\n- Use a 2-column grid (lg:grid-cols-2) that fills the full height, NOT content floating in empty space\n- Left column: headline + description + CTAs, vertically centered with flex flex-col justify-center\n- Right column: large hero image (data-image-query) filling the column, or a bento-grid of image + stat cards\n- Bold oversized headline (text-4xl md:text-6xl lg:text-7xl font-black leading-tight)\n- Tag/label above headline (uppercase, tracking-wider, text-xs text-accent)\n- Short description paragraph (text-lg text-on-surface-muted, max-w-lg)\n- 2 CTAs: primary (large, px-8 py-4, with → arrow) + secondary (ghost/outlined)\n- Optional: social proof bar below CTAs (avatar stack + \"2,847+ users\" text)\n- Min height: min-h-[90vh] with items-center on the grid so content is vertically centered\n- CRITICAL: the grid must stretch to fill the section height. Use min-h-[90vh] on the grid container itself, not just the section\n- NEVER leave large empty areas — if using min-h-[90vh], content must be centered/distributed within it\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders`;\n\n/** @deprecated Use buildPromptSuffix(prompt) internally. Kept for backward compat. */\nexport const PROMPT_SUFFIX = `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Short Label\", \"html\": \"<section>...</section>\"}\n\nGenerate 7-9 sections. Always start with Hero and end with Footer.\nIMPORTANT: Make each section VISUALLY UNIQUE — different layouts, different background colors, different grid structures.\nThink like a premium design agency creating a $50K landing page.\nNO generic Bootstrap layouts. Use creative grids, bento layouts, overlapping elements, asymmetric columns.`;\n\nfunction buildPromptSuffix(userPrompt: string): string {\n // Extract section count from user prompt (e.g. \"3 secciones\", \"2 sections\", \"dame 4\")\n const countMatch = userPrompt.match(/(\\d+)\\s*(?:secciones?|sections?|bloques?|blocks?|partes?|pages?)/i)\n || userPrompt.match(/(?:genera|generar|crea|crear|haz|hazme|dame|quiero)\\s*(\\d+)/i);\n const count = countMatch ? parseInt(countMatch[1] || countMatch[2] || \"0\") : 0;\n const sectionInstruction = count > 0\n ? `Generate EXACTLY ${count} sections (no more, no less). Always include Hero as first and Footer as last.`\n : `Generate 7-9 sections. Always start with Hero and end with Footer.`;\n\n return `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Short Label\", \"html\": \"<section>...</section>\"}\n\n${sectionInstruction}\nIMPORTANT: Make each section VISUALLY UNIQUE — different layouts, different background colors, different grid structures.\nThink like a premium design agency creating a $50K landing page.\nNO generic Bootstrap layouts. Use creative grids, bento layouts, overlapping elements, asymmetric columns.`;\n}\n\nexport interface GenerateOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n referenceImage?: string;\n extraInstructions?: string;\n systemPrompt?: string;\n model?: string;\n pexelsApiKey?: string;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n onSection?: (section: Section3) => void;\n onImageUpdate?: (sectionId: string, html: string) => void;\n onDone?: (sections: Section3[]) => void;\n onError?: (error: Error) => void;\n /** Theme colors to inject into the AI prompt (deprecated — use themeName) */\n themeColors?: Record<string, string>;\n /** Theme name (e.g. \"minimal\", \"noche\", \"oceano\") — tells the AI the design mood */\n themeName?: string;\n /** Brand kit info for AI context */\n brandKit?: {\n fonts?: { heading?: string; body?: string };\n mood?: string;\n logoUrl?: string;\n };\n}\n\n/**\n * Generate a landing page with streaming AI + image enrichment.\n */\nfunction buildVisualContext(themeName?: string, brandKit?: GenerateOptions[\"brandKit\"]): string {\n if (!themeName && !brandKit) return \"\";\n\n const lines: string[] = [\"\\n\\n## Visual Context — MANDATORY\"];\n\n if (themeName && themeName !== \"custom\") {\n lines.push(buildThemePromptContext(themeName));\n }\n\n if (brandKit?.fonts) {\n const { heading, body } = brandKit.fonts;\n if (heading) lines.push(`- Heading font: use font-family: '${heading}' via inline style on h1-h6`);\n if (body) lines.push(`- Body font: use font-family: '${body}' via inline style on p, li, span`);\n }\n\n if (brandKit?.mood) {\n lines.push(`- Design mood: ${brandKit.mood} — adapt spacing, imagery style, and visual weight to match this mood`);\n }\n\n if (brandKit?.logoUrl) {\n lines.push(`- Brand logo: include <img src=\"${brandKit.logoUrl}\" alt=\"Logo\" class=\"h-8 w-auto\" /> in the navbar/hero area`);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport async function generateLanding(options: GenerateOptions): Promise<Section3[]> {\n const {\n prompt,\n referenceImage,\n extraInstructions,\n systemPrompt = SYSTEM_PROMPT,\n themeColors: _themeColors,\n themeName,\n brandKit,\n ...rest\n } = options;\n\n const visualContext = buildVisualContext(themeName, brandKit);\n const extra = extraInstructions ? `\\nAdditional instructions: ${extraInstructions}` : \"\";\n const content: any[] = [];\n\n if (referenceImage) {\n const converted = dataUrlToImagePart(referenceImage);\n if (converted) {\n content.push({ type: \"image\", ...converted });\n } else {\n content.push({ type: \"image\", image: referenceImage });\n }\n content.push({\n type: \"text\",\n text: `Generate a landing page for: ${prompt}${extra}\\n\\nIMPORTANT: Use the reference image as a DIRECT visual guide. Replicate its layout structure, grid arrangement, spacing, visual hierarchy, and section organization as closely as possible. Match the number of columns, element positioning, and overall composition. Adapt the content to the prompt but keep the visual DNA of the reference.${buildPromptSuffix(prompt)}`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Generate a landing page for: ${prompt}${extra}${buildPromptSuffix(prompt)}`,\n });\n }\n\n return streamGenerate({\n ...rest,\n systemPrompt: systemPrompt + visualContext,\n userContent: content,\n });\n}\n"],"mappings":";;;;;;;;;AAMO,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+FtB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU7B,SAAS,kBAAkB,YAA4B;AAErD,QAAM,aAAa,WAAW,MAAM,mEAAmE,KAClG,WAAW,MAAM,8DAA8D;AACpF,QAAM,QAAQ,aAAa,SAAS,WAAW,CAAC,KAAK,WAAW,CAAC,KAAK,GAAG,IAAI;AAC7E,QAAM,qBAAqB,QAAQ,IAC/B,oBAAoB,KAAK,mFACzB;AAEJ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKP,kBAAkB;AAAA;AAAA;AAAA;AAIpB;AA+BA,SAAS,mBAAmB,WAAoB,UAAgD;AAC9F,MAAI,CAAC,aAAa,CAAC,SAAU,QAAO;AAEpC,QAAM,QAAkB,CAAC,wCAAmC;AAE5D,MAAI,aAAa,cAAc,UAAU;AACvC,UAAM,KAAK,wBAAwB,SAAS,CAAC;AAAA,EAC/C;AAEA,MAAI,UAAU,OAAO;AACnB,UAAM,EAAE,SAAS,KAAK,IAAI,SAAS;AACnC,QAAI,QAAS,OAAM,KAAK,qCAAqC,OAAO,6BAA6B;AACjG,QAAI,KAAM,OAAM,KAAK,kCAAkC,IAAI,mCAAmC;AAAA,EAChG;AAEA,MAAI,UAAU,MAAM;AAClB,UAAM,KAAK,kBAAkB,SAAS,IAAI,4EAAuE;AAAA,EACnH;AAEA,MAAI,UAAU,SAAS;AACrB,UAAM,KAAK,mCAAmC,SAAS,OAAO,4DAA4D;AAAA,EAC5H;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,gBAAgB,SAA+C;AACnF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,gBAAgB,mBAAmB,WAAW,QAAQ;AAC5D,QAAM,QAAQ,oBAAoB;AAAA,2BAA8B,iBAAiB,KAAK;AACtF,QAAM,UAAiB,CAAC;AAExB,MAAI,gBAAgB;AAClB,UAAM,YAAY,mBAAmB,cAAc;AACnD,QAAI,WAAW;AACb,cAAQ,KAAK,EAAE,MAAM,SAAS,GAAG,UAAU,CAAC;AAAA,IAC9C,OAAO;AACL,cAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,eAAe,CAAC;AAAA,IACvD;AACA,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,gCAAgC,MAAM,GAAG,KAAK;AAAA;AAAA,iVAAsV,kBAAkB,MAAM,CAAC;AAAA,IACra,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,gCAAgC,MAAM,GAAG,KAAK,GAAG,kBAAkB,MAAM,CAAC;AAAA,IAClF,CAAC;AAAA,EACH;AAEA,SAAO,eAAe;AAAA,IACpB,GAAG;AAAA,IACH,cAAc,eAAe;AAAA,IAC7B,aAAa;AAAA,EACf,CAAC;AACH;","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  buildCustomTheme,
3
3
  buildSingleThemeCss,
4
4
  buildThemeCss
5
- } from "./chunk-D7YHQJET.js";
5
+ } from "./chunk-F7QKVJ7F.js";
6
6
 
7
7
  // src/iframeScript.ts
8
8
  function getIframeScript() {
@@ -633,4 +633,4 @@ export {
633
633
  buildPreviewHtml,
634
634
  buildDeployHtml
635
635
  };
636
- //# sourceMappingURL=chunk-YYHFKHOQ.js.map
636
+ //# sourceMappingURL=chunk-R2XLJYBZ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
- LANDING_THEMES
3
- } from "./chunk-D7YHQJET.js";
2
+ buildThemePromptContext
3
+ } from "./chunk-F7QKVJ7F.js";
4
4
  import {
5
5
  currentDateLine,
6
6
  enrichImages,
@@ -112,30 +112,11 @@ Return the updated HTML.`
112
112
  });
113
113
  }
114
114
  let finalSystem = systemPrompt + currentDateLine();
115
- const THEME_DESCS = {
116
- minimal: "Clean, light, professional \u2014 white surfaces, subtle grays",
117
- calido: "Warm tones \u2014 amber/orange primary, cream surfaces, cozy",
118
- oceano: "Cool blue tones \u2014 deep ocean blues, clean white surfaces",
119
- noche: "DARK theme \u2014 very dark surfaces, purple/violet accents, moody. Surfaces are dark, text is light.",
120
- bosque: "Nature-inspired \u2014 deep green primary, warm earth tones",
121
- rosa: "Soft pink/rose tones \u2014 delicate, feminine, elegant"
122
- };
123
115
  if (themeName && themeName !== "custom") {
124
- const desc = THEME_DESCS[themeName] || themeName;
125
116
  finalSystem += `
126
117
 
127
118
  ## Active Theme
128
- Theme: "${themeName}" \u2014 ${desc}
129
- The CSS variables are already loaded. Keep using semantic classes (bg-primary, text-on-surface, etc).`;
130
- const themeObj = LANDING_THEMES.find((t) => t.id === themeName);
131
- if (themeObj) {
132
- finalSystem += "\nActual color values:";
133
- for (const [key, hex] of Object.entries(themeObj.colors)) {
134
- finalSystem += `
135
- - ${key}: ${hex}`;
136
- }
137
- finalSystem += "\nUse these to understand the visual mood. Designs should COMPLEMENT these specific colors.";
138
- }
119
+ ${buildThemePromptContext(themeName)}`;
139
120
  }
140
121
  if (brandKit) {
141
122
  const bkLines = [];
@@ -180,4 +161,4 @@ export {
180
161
  extractSectionDescription,
181
162
  refineLanding
182
163
  };
183
- //# sourceMappingURL=chunk-CSBLPJAL.js.map
164
+ //# sourceMappingURL=chunk-Z4HSI7KU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/refine.ts"],"sourcesContent":["import { streamText } from \"ai\";\nimport { enrichImages } from \"./images/enrichImages\";\nimport { sanitizeSemanticColors } from \"./sanitizeColors\";\nimport { resolveModel, currentDateLine } from \"./streamCore\";\nimport { buildThemePromptContext } from \"./themes\";\n\nexport const REFINE_SYSTEM = `You are an expert HTML/Tailwind CSS developer. You receive the current HTML of a landing page section and a user instruction.\n\nRULES:\n- Return ONLY the modified HTML — no full page, no <html>/<head>/<body> tags\n- Use Tailwind CSS classes (CDN loaded)\n- You may use inline styles for specific adjustments\n- Images: use data-image-query=\"english search query\" for new images\n- Keep all text in its original language unless asked to translate\n- Be creative — don't just make minimal changes, improve the design\n- Return raw HTML only — no markdown fences, no explanations\n\nCOLOR SYSTEM — CRITICAL:\n- Use semantic color classes: bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary, bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted, bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded colors: NO bg-gray-*, bg-black, bg-white, text-gray-*, text-black, text-white, etc.\n- The ONLY exception: border-gray-200 or border-gray-700 for subtle dividers.\n- ALL text MUST use: text-on-surface, text-on-surface-muted, text-on-primary, text-accent. Use text-primary ONLY on bg-surface/bg-surface-alt (it's the same hue as bg-primary — invisible on primary backgrounds).\n- CONTRAST RULE: on bg-primary or bg-primary-dark → use ONLY text-on-primary. On bg-surface or bg-surface-alt → use text-on-surface, text-on-surface-muted, or text-primary. NEVER use text-primary on bg-primary — they are the SAME COLOR. NEVER put text-on-surface on bg-primary or text-on-primary on bg-surface.\n- Use bg-accent, bg-secondary, text-accent, text-secondary for visual variety — not everything should be primary.\n\nIMAGE OVERLAYS:\n- When placing text over images, ALWAYS add a gradient overlay for readability\n- Pattern: <div class=\"relative\"><img .../><div class=\"absolute inset-0 bg-gradient-to-r from-primary/80 to-transparent\"></div><div class=\"relative z-10\">...text...</div></div>\n- NEVER place text directly on images without an overlay\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders`;\n\n/**\n * Extract a text description from HTML for variant generation.\n * Instead of sending full HTML to the model, we send a content summary\n * so the model generates a completely new layout rather than tweaking colors.\n */\nexport function extractSectionDescription(html: string, label?: string): { content: string; layoutHint: string } {\n // Extract headings\n const headings = [...html.matchAll(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(Boolean);\n\n // Extract paragraphs\n const paragraphs = [...html.matchAll(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(Boolean);\n\n // Extract button/CTA text\n const buttons = [...html.matchAll(/<(?:button|a)[^>]*>([\\s\\S]*?)<\\/(?:button|a)>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(t => t.length > 0 && t.length < 60);\n\n // Count items (cards, list items, grid children)\n const listItems = (html.match(/<li[\\s>]/gi) || []).length;\n const gridMatch = html.match(/grid-cols-(\\d)/);\n const gridCols = gridMatch ? parseInt(gridMatch[1]) : 0;\n\n // Detect layout patterns for negative prompt\n const layouts: string[] = [];\n if (html.includes(\"grid\")) layouts.push(\"grid\");\n if (html.includes(\"flex-col\")) layouts.push(\"vertical-stack\");\n if (html.includes(\"flex-row\") || html.includes(\"md:flex-row\")) layouts.push(\"horizontal-flex\");\n if (html.includes(\"text-center\") && !html.includes(\"text-left\")) layouts.push(\"centered\");\n if (gridCols) layouts.push(`${gridCols}-column-grid`);\n\n const content = [\n label ? `Section: ${label}` : \"\",\n headings.length ? `Headings: ${headings.join(\" | \")}` : \"\",\n paragraphs.length ? `Text: ${paragraphs.slice(0, 3).join(\" \")}` : \"\",\n buttons.length ? `CTAs: ${buttons.join(\", \")}` : \"\",\n listItems > 0 ? `${listItems} list/card items` : \"\",\n ].filter(Boolean).join(\"\\n\");\n\n return { content, layoutHint: layouts.join(\", \") };\n}\n\nexport interface RefineOptions {\n /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var */\n anthropicApiKey?: string;\n /** OpenAI API key. If provided, uses GPT-4o-mini instead of Claude */\n openaiApiKey?: string;\n /** Current HTML of the section being refined */\n currentHtml: string;\n /** User instruction for refinement */\n instruction: string;\n /** Reference image (base64 data URI) for vision-based refinement */\n referenceImage?: string;\n /** When true, generates a completely new layout variant instead of refining */\n isVariant?: boolean;\n /** Custom system prompt (overrides default REFINE_SYSTEM) */\n systemPrompt?: string;\n /** Model ID (default: gpt-4o-mini/gpt-4o for OpenAI, claude-haiku/claude-sonnet for Anthropic) */\n model?: string;\n /** Pexels API key for image enrichment. Falls back to PEXELS_API_KEY env var */\n pexelsApiKey?: string;\n /** Called with temp DALL-E URL + query, returns permanent URL. Use to persist to S3/etc. */\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n /** Called with accumulated HTML as it streams */\n onChunk?: (html: string) => void;\n /** Called when refinement is complete with final enriched HTML */\n onDone?: (html: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Theme colors for AI context (deprecated — use themeName) */\n themeColors?: Record<string, string>;\n /** Theme name (e.g. \"minimal\", \"noche\") — tells the AI the design mood */\n themeName?: string;\n /** Brand kit info for AI context */\n brandKit?: {\n fonts?: { heading?: string; body?: string };\n mood?: string;\n logoUrl?: string;\n };\n}\n\n/**\n * Refine a landing page section with streaming AI.\n * Returns the final enriched HTML.\n */\nexport async function refineLanding(options: RefineOptions): Promise<string> {\n const {\n anthropicApiKey,\n openaiApiKey: _openaiApiKey,\n currentHtml,\n instruction,\n referenceImage,\n isVariant,\n systemPrompt = REFINE_SYSTEM,\n model: modelId,\n pexelsApiKey,\n persistImage,\n onChunk,\n onDone,\n onError,\n themeColors: _themeColors,\n themeName,\n brandKit,\n } = options;\n\n const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;\n const useVision = !!referenceImage;\n const defaultOpenai = useVision ? \"gpt-4o\" : \"gpt-4o-mini\";\n const defaultAnthropic = useVision ? \"claude-sonnet-4-6\" : \"claude-haiku-4-5-20251001\";\n const model = await resolveModel({ openaiApiKey, anthropicApiKey, modelId, defaultOpenai, defaultAnthropic });\n\n // Build content (supports multimodal with reference image)\n const content: any[] = [];\n if (referenceImage) {\n content.push({ type: \"image\", image: referenceImage });\n }\n\n if (isVariant && !referenceImage) {\n // Variant mode: send description instead of HTML to force creative layout\n const { content: desc, layoutHint } = extractSectionDescription(currentHtml);\n content.push({\n type: \"text\",\n text: `Generate a COMPLETELY NEW section with the following content. Create an original, creative layout.\\n\\nContent:\\n${desc}\\n\\n${layoutHint ? `DO NOT use these layout patterns (the current design already uses them): ${layoutHint}. Choose a radically different structure.` : \"\"}\\n\\nReturn ONLY the <section>...</section> HTML with Tailwind classes.`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Current HTML:\\n${currentHtml}\\n\\nInstruction: ${instruction}\\n\\nReturn the updated HTML.`,\n });\n }\n\n // Inject theme + brand kit context\n let finalSystem = systemPrompt + currentDateLine();\n\n if (themeName && themeName !== \"custom\") {\n finalSystem += `\\n\\n## Active Theme\\n${buildThemePromptContext(themeName)}`;\n }\n\n if (brandKit) {\n const bkLines: string[] = [];\n if (brandKit.fonts?.heading) bkLines.push(`- Heading font: use font-family: '${brandKit.fonts.heading}' via inline style on h1-h6`);\n if (brandKit.fonts?.body) bkLines.push(`- Body font: use font-family: '${brandKit.fonts.body}' via inline style on p, li, span`);\n if (brandKit.mood) bkLines.push(`- Design mood: ${brandKit.mood} — adapt spacing, imagery style, and visual weight to match`);\n if (brandKit.logoUrl) bkLines.push(`- Brand logo: include <img src=\"${brandKit.logoUrl}\" alt=\"Logo\" class=\"h-8 w-auto\" /> in the navbar/hero area`);\n if (bkLines.length) finalSystem += `\\n\\n## Brand Kit\\n${bkLines.join(\"\\n\")}`;\n }\n\n const result = streamText({\n model,\n system: finalSystem,\n messages: [{ role: \"user\", content }],\n ...(isVariant && !referenceImage ? { temperature: 1.2 } : {}),\n });\n\n try {\n let accumulated = \"\";\n\n for await (const chunk of result.textStream) {\n accumulated += chunk;\n onChunk?.(accumulated);\n }\n\n // Clean up markdown fences if present\n let html = accumulated.trim();\n if (html.startsWith(\"```\")) {\n html = html.replace(/^```(?:html|xml)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n\n // Sanitize hardcoded colors to semantic classes\n html = sanitizeSemanticColors(html);\n\n // Enrich images (DALL-E if openaiApiKey, otherwise Pexels)\n html = await enrichImages(html, { pexelsApiKey, openaiApiKey, persistImage });\n\n onDone?.(html);\n return html;\n } catch (err: any) {\n const error = err instanceof Error ? err : new Error(err?.message || \"Refine failed\");\n onError?.(error);\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -5,9 +5,9 @@ import {
5
5
  SectionList,
6
6
  ViewportToggle,
7
7
  useUndoStack
8
- } from "./chunk-TDZO6X4O.js";
9
- import "./chunk-YYHFKHOQ.js";
10
- import "./chunk-D7YHQJET.js";
8
+ } from "./chunk-JTK6WJP2.js";
9
+ import "./chunk-R2XLJYBZ.js";
10
+ import "./chunk-F7QKVJ7F.js";
11
11
  export {
12
12
  Canvas,
13
13
  CodeEditor,
package/dist/deploy.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  deployToEasyBits,
3
3
  deployToS3
4
- } from "./chunk-YTVJTJRN.js";
5
- import "./chunk-YYHFKHOQ.js";
6
- import "./chunk-D7YHQJET.js";
4
+ } from "./chunk-FIQOLRH3.js";
5
+ import "./chunk-R2XLJYBZ.js";
6
+ import "./chunk-F7QKVJ7F.js";
7
7
  export {
8
8
  deployToEasyBits,
9
9
  deployToS3
package/dist/generate.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  PROMPT_SUFFIX,
3
3
  SYSTEM_PROMPT,
4
4
  generateLanding
5
- } from "./chunk-WI62YIOF.js";
6
- import "./chunk-D7YHQJET.js";
5
+ } from "./chunk-PERVSTVF.js";
6
+ import "./chunk-F7QKVJ7F.js";
7
7
  import {
8
8
  extractJsonObjects
9
9
  } from "./chunk-L7D64X7G.js";
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import {
4
4
  FloatingToolbar,
5
5
  SectionList,
6
6
  ViewportToggle
7
- } from "./chunk-TDZO6X4O.js";
7
+ } from "./chunk-JTK6WJP2.js";
8
8
  import "./chunk-MJ34S5ZC.js";
9
9
  import {
10
10
  PROMPT_SUFFIX,
11
11
  SYSTEM_PROMPT,
12
12
  generateLanding
13
- } from "./chunk-WI62YIOF.js";
13
+ } from "./chunk-PERVSTVF.js";
14
14
  import {
15
15
  DOCUMENT_PROMPT_SUFFIX,
16
16
  DOCUMENT_SYSTEM_PROMPT,
@@ -19,23 +19,23 @@ import {
19
19
  import {
20
20
  REFINE_SYSTEM,
21
21
  refineLanding
22
- } from "./chunk-CSBLPJAL.js";
22
+ } from "./chunk-Z4HSI7KU.js";
23
23
  import {
24
24
  deployToEasyBits,
25
25
  deployToS3
26
- } from "./chunk-YTVJTJRN.js";
26
+ } from "./chunk-FIQOLRH3.js";
27
27
  import {
28
28
  buildDeployHtml,
29
29
  buildPreviewHtml,
30
30
  getIframeScript
31
- } from "./chunk-YYHFKHOQ.js";
31
+ } from "./chunk-R2XLJYBZ.js";
32
32
  import {
33
33
  LANDING_THEMES,
34
34
  buildCustomTheme,
35
35
  buildCustomThemeCss,
36
36
  buildSingleThemeCss,
37
37
  buildThemeCss
38
- } from "./chunk-D7YHQJET.js";
38
+ } from "./chunk-F7QKVJ7F.js";
39
39
  import {
40
40
  enrichImages,
41
41
  extractJsonObjects,
package/dist/refine.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  REFINE_SYSTEM,
3
3
  extractSectionDescription,
4
4
  refineLanding
5
- } from "./chunk-CSBLPJAL.js";
6
- import "./chunk-D7YHQJET.js";
5
+ } from "./chunk-Z4HSI7KU.js";
6
+ import "./chunk-F7QKVJ7F.js";
7
7
  import "./chunk-L7D64X7G.js";
8
8
  export {
9
9
  REFINE_SYSTEM,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/html-tailwind-generator",
3
- "version": "0.2.119",
3
+ "version": "0.2.121",
4
4
  "description": "AI-powered landing page generator with Tailwind CSS — canvas editor, streaming generation, and one-click deploy",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "type": "module",
@@ -99,7 +99,7 @@
99
99
  }
100
100
  },
101
101
  "dependencies": {
102
- "@easybits.cloud/html-tailwind-generator": "^0.2.81",
102
+ "@easybits.cloud/html-tailwind-generator": "^0.2.120",
103
103
  "nanoid": "^5.1.5"
104
104
  },
105
105
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/refine.ts"],"sourcesContent":["import { streamText } from \"ai\";\nimport { enrichImages } from \"./images/enrichImages\";\nimport { sanitizeSemanticColors } from \"./sanitizeColors\";\nimport { resolveModel, currentDateLine } from \"./streamCore\";\nimport { LANDING_THEMES } from \"./themes\";\n\nexport const REFINE_SYSTEM = `You are an expert HTML/Tailwind CSS developer. You receive the current HTML of a landing page section and a user instruction.\n\nRULES:\n- Return ONLY the modified HTML — no full page, no <html>/<head>/<body> tags\n- Use Tailwind CSS classes (CDN loaded)\n- You may use inline styles for specific adjustments\n- Images: use data-image-query=\"english search query\" for new images\n- Keep all text in its original language unless asked to translate\n- Be creative — don't just make minimal changes, improve the design\n- Return raw HTML only — no markdown fences, no explanations\n\nCOLOR SYSTEM — CRITICAL:\n- Use semantic color classes: bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary, bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted, bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded colors: NO bg-gray-*, bg-black, bg-white, text-gray-*, text-black, text-white, etc.\n- The ONLY exception: border-gray-200 or border-gray-700 for subtle dividers.\n- ALL text MUST use: text-on-surface, text-on-surface-muted, text-on-primary, text-accent. Use text-primary ONLY on bg-surface/bg-surface-alt (it's the same hue as bg-primary — invisible on primary backgrounds).\n- CONTRAST RULE: on bg-primary or bg-primary-dark → use ONLY text-on-primary. On bg-surface or bg-surface-alt → use text-on-surface, text-on-surface-muted, or text-primary. NEVER use text-primary on bg-primary — they are the SAME COLOR. NEVER put text-on-surface on bg-primary or text-on-primary on bg-surface.\n- Use bg-accent, bg-secondary, text-accent, text-secondary for visual variety — not everything should be primary.\n\nIMAGE OVERLAYS:\n- When placing text over images, ALWAYS add a gradient overlay for readability\n- Pattern: <div class=\"relative\"><img .../><div class=\"absolute inset-0 bg-gradient-to-r from-primary/80 to-transparent\"></div><div class=\"relative z-10\">...text...</div></div>\n- NEVER place text directly on images without an overlay\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders`;\n\n/**\n * Extract a text description from HTML for variant generation.\n * Instead of sending full HTML to the model, we send a content summary\n * so the model generates a completely new layout rather than tweaking colors.\n */\nexport function extractSectionDescription(html: string, label?: string): { content: string; layoutHint: string } {\n // Extract headings\n const headings = [...html.matchAll(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(Boolean);\n\n // Extract paragraphs\n const paragraphs = [...html.matchAll(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(Boolean);\n\n // Extract button/CTA text\n const buttons = [...html.matchAll(/<(?:button|a)[^>]*>([\\s\\S]*?)<\\/(?:button|a)>/gi)]\n .map(m => m[1].replace(/<[^>]+>/g, \"\").trim())\n .filter(t => t.length > 0 && t.length < 60);\n\n // Count items (cards, list items, grid children)\n const listItems = (html.match(/<li[\\s>]/gi) || []).length;\n const gridMatch = html.match(/grid-cols-(\\d)/);\n const gridCols = gridMatch ? parseInt(gridMatch[1]) : 0;\n\n // Detect layout patterns for negative prompt\n const layouts: string[] = [];\n if (html.includes(\"grid\")) layouts.push(\"grid\");\n if (html.includes(\"flex-col\")) layouts.push(\"vertical-stack\");\n if (html.includes(\"flex-row\") || html.includes(\"md:flex-row\")) layouts.push(\"horizontal-flex\");\n if (html.includes(\"text-center\") && !html.includes(\"text-left\")) layouts.push(\"centered\");\n if (gridCols) layouts.push(`${gridCols}-column-grid`);\n\n const content = [\n label ? `Section: ${label}` : \"\",\n headings.length ? `Headings: ${headings.join(\" | \")}` : \"\",\n paragraphs.length ? `Text: ${paragraphs.slice(0, 3).join(\" \")}` : \"\",\n buttons.length ? `CTAs: ${buttons.join(\", \")}` : \"\",\n listItems > 0 ? `${listItems} list/card items` : \"\",\n ].filter(Boolean).join(\"\\n\");\n\n return { content, layoutHint: layouts.join(\", \") };\n}\n\nexport interface RefineOptions {\n /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var */\n anthropicApiKey?: string;\n /** OpenAI API key. If provided, uses GPT-4o-mini instead of Claude */\n openaiApiKey?: string;\n /** Current HTML of the section being refined */\n currentHtml: string;\n /** User instruction for refinement */\n instruction: string;\n /** Reference image (base64 data URI) for vision-based refinement */\n referenceImage?: string;\n /** When true, generates a completely new layout variant instead of refining */\n isVariant?: boolean;\n /** Custom system prompt (overrides default REFINE_SYSTEM) */\n systemPrompt?: string;\n /** Model ID (default: gpt-4o-mini/gpt-4o for OpenAI, claude-haiku/claude-sonnet for Anthropic) */\n model?: string;\n /** Pexels API key for image enrichment. Falls back to PEXELS_API_KEY env var */\n pexelsApiKey?: string;\n /** Called with temp DALL-E URL + query, returns permanent URL. Use to persist to S3/etc. */\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n /** Called with accumulated HTML as it streams */\n onChunk?: (html: string) => void;\n /** Called when refinement is complete with final enriched HTML */\n onDone?: (html: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Theme colors for AI context (deprecated — use themeName) */\n themeColors?: Record<string, string>;\n /** Theme name (e.g. \"minimal\", \"noche\") — tells the AI the design mood */\n themeName?: string;\n /** Brand kit info for AI context */\n brandKit?: {\n fonts?: { heading?: string; body?: string };\n mood?: string;\n logoUrl?: string;\n };\n}\n\n/**\n * Refine a landing page section with streaming AI.\n * Returns the final enriched HTML.\n */\nexport async function refineLanding(options: RefineOptions): Promise<string> {\n const {\n anthropicApiKey,\n openaiApiKey: _openaiApiKey,\n currentHtml,\n instruction,\n referenceImage,\n isVariant,\n systemPrompt = REFINE_SYSTEM,\n model: modelId,\n pexelsApiKey,\n persistImage,\n onChunk,\n onDone,\n onError,\n themeColors: _themeColors,\n themeName,\n brandKit,\n } = options;\n\n const openaiApiKey = _openaiApiKey || process.env.OPENAI_API_KEY;\n const useVision = !!referenceImage;\n const defaultOpenai = useVision ? \"gpt-4o\" : \"gpt-4o-mini\";\n const defaultAnthropic = useVision ? \"claude-sonnet-4-6\" : \"claude-haiku-4-5-20251001\";\n const model = await resolveModel({ openaiApiKey, anthropicApiKey, modelId, defaultOpenai, defaultAnthropic });\n\n // Build content (supports multimodal with reference image)\n const content: any[] = [];\n if (referenceImage) {\n content.push({ type: \"image\", image: referenceImage });\n }\n\n if (isVariant && !referenceImage) {\n // Variant mode: send description instead of HTML to force creative layout\n const { content: desc, layoutHint } = extractSectionDescription(currentHtml);\n content.push({\n type: \"text\",\n text: `Generate a COMPLETELY NEW section with the following content. Create an original, creative layout.\\n\\nContent:\\n${desc}\\n\\n${layoutHint ? `DO NOT use these layout patterns (the current design already uses them): ${layoutHint}. Choose a radically different structure.` : \"\"}\\n\\nReturn ONLY the <section>...</section> HTML with Tailwind classes.`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Current HTML:\\n${currentHtml}\\n\\nInstruction: ${instruction}\\n\\nReturn the updated HTML.`,\n });\n }\n\n // Inject theme + brand kit context\n let finalSystem = systemPrompt + currentDateLine();\n\n const THEME_DESCS: Record<string, string> = {\n minimal: \"Clean, light, professional — white surfaces, subtle grays\",\n calido: \"Warm tones — amber/orange primary, cream surfaces, cozy\",\n oceano: \"Cool blue tones — deep ocean blues, clean white surfaces\",\n noche: \"DARK theme — very dark surfaces, purple/violet accents, moody. Surfaces are dark, text is light.\",\n bosque: \"Nature-inspired — deep green primary, warm earth tones\",\n rosa: \"Soft pink/rose tones — delicate, feminine, elegant\",\n };\n\n if (themeName && themeName !== \"custom\") {\n const desc = THEME_DESCS[themeName] || themeName;\n finalSystem += `\\n\\n## Active Theme\\nTheme: \"${themeName}\" — ${desc}\\nThe CSS variables are already loaded. Keep using semantic classes (bg-primary, text-on-surface, etc).`;\n const themeObj = LANDING_THEMES.find(t => t.id === themeName);\n if (themeObj) {\n finalSystem += \"\\nActual color values:\";\n for (const [key, hex] of Object.entries(themeObj.colors)) {\n finalSystem += `\\n - ${key}: ${hex}`;\n }\n finalSystem += \"\\nUse these to understand the visual mood. Designs should COMPLEMENT these specific colors.\";\n }\n }\n\n if (brandKit) {\n const bkLines: string[] = [];\n if (brandKit.fonts?.heading) bkLines.push(`- Heading font: use font-family: '${brandKit.fonts.heading}' via inline style on h1-h6`);\n if (brandKit.fonts?.body) bkLines.push(`- Body font: use font-family: '${brandKit.fonts.body}' via inline style on p, li, span`);\n if (brandKit.mood) bkLines.push(`- Design mood: ${brandKit.mood} — adapt spacing, imagery style, and visual weight to match`);\n if (brandKit.logoUrl) bkLines.push(`- Brand logo: include <img src=\"${brandKit.logoUrl}\" alt=\"Logo\" class=\"h-8 w-auto\" /> in the navbar/hero area`);\n if (bkLines.length) finalSystem += `\\n\\n## Brand Kit\\n${bkLines.join(\"\\n\")}`;\n }\n\n const result = streamText({\n model,\n system: finalSystem,\n messages: [{ role: \"user\", content }],\n ...(isVariant && !referenceImage ? { temperature: 1.2 } : {}),\n });\n\n try {\n let accumulated = \"\";\n\n for await (const chunk of result.textStream) {\n accumulated += chunk;\n onChunk?.(accumulated);\n }\n\n // Clean up markdown fences if present\n let html = accumulated.trim();\n if (html.startsWith(\"```\")) {\n html = html.replace(/^```(?:html|xml)?\\s*/, \"\").replace(/\\s*```$/, \"\");\n }\n\n // Sanitize hardcoded colors to semantic classes\n html = sanitizeSemanticColors(html);\n\n // Enrich images (DALL-E if openaiApiKey, otherwise Pexels)\n html = await enrichImages(html, { pexelsApiKey, openaiApiKey, persistImage });\n\n onDone?.(html);\n return html;\n } catch (err: any) {\n const error = err instanceof Error ? err : new Error(err?.message || \"Refine failed\");\n onError?.(error);\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/themes.ts"],"sourcesContent":["export interface LandingTheme {\n id: string;\n label: string;\n colors: {\n primary: string;\n \"primary-light\": string;\n \"primary-dark\": string;\n secondary: string;\n accent: string;\n surface: string;\n \"surface-alt\": string;\n \"on-surface\": string;\n \"on-surface-muted\": string;\n \"on-primary\": string;\n };\n}\n\nexport const LANDING_THEMES: LandingTheme[] = [\n {\n id: \"minimal\",\n label: \"Minimal\",\n colors: {\n primary: \"#18181b\",\n \"primary-light\": \"#3f3f46\",\n \"primary-dark\": \"#09090b\",\n secondary: \"#71717a\",\n accent: \"#2563eb\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#f4f4f5\",\n \"on-surface\": \"#18181b\",\n \"on-surface-muted\": \"#71717a\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"calido\",\n label: \"Cálido\",\n colors: {\n primary: \"#9a3412\",\n \"primary-light\": \"#c2410c\",\n \"primary-dark\": \"#7c2d12\",\n secondary: \"#78716c\",\n accent: \"#d97706\",\n surface: \"#fafaf9\",\n \"surface-alt\": \"#f5f5f4\",\n \"on-surface\": \"#1c1917\",\n \"on-surface-muted\": \"#78716c\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"oceano\",\n label: \"Océano\",\n colors: {\n primary: \"#0f4c75\",\n \"primary-light\": \"#1a6fa0\",\n \"primary-dark\": \"#0a3555\",\n secondary: \"#6b7280\",\n accent: \"#0d9488\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#f0fdfa\",\n \"on-surface\": \"#0f172a\",\n \"on-surface-muted\": \"#64748b\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"noche\",\n label: \"Noche\",\n colors: {\n primary: \"#a78bfa\",\n \"primary-light\": \"#c4b5fd\",\n \"primary-dark\": \"#8b5cf6\",\n secondary: \"#9ca3af\",\n accent: \"#a78bfa\",\n surface: \"#111827\",\n \"surface-alt\": \"#1f2937\",\n \"on-surface\": \"#f9fafb\",\n \"on-surface-muted\": \"#9ca3af\",\n \"on-primary\": \"#111827\",\n },\n },\n {\n id: \"bosque\",\n label: \"Bosque\",\n colors: {\n primary: \"#16a34a\",\n \"primary-light\": \"#22c55e\",\n \"primary-dark\": \"#15803d\",\n secondary: \"#a3a3a3\",\n accent: \"#16a34a\",\n surface: \"#0a0a0a\",\n \"surface-alt\": \"#171717\",\n \"on-surface\": \"#fafafa\",\n \"on-surface-muted\": \"#a3a3a3\",\n \"on-primary\": \"#000000\",\n },\n },\n {\n id: \"rosa\",\n label: \"Rosa\",\n colors: {\n primary: \"#be185d\",\n \"primary-light\": \"#db2777\",\n \"primary-dark\": \"#9d174d\",\n secondary: \"#6b7280\",\n accent: \"#be185d\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#fdf2f8\",\n \"on-surface\": \"#1f2937\",\n \"on-surface-muted\": \"#6b7280\",\n \"on-primary\": \"#ffffff\",\n },\n },\n];\n\nexport interface CustomColors {\n primary: string;\n secondary?: string;\n accent?: string;\n surface?: string;\n}\n\nfunction parseHex(hex: string) {\n return {\n r: parseInt(hex.slice(1, 3), 16),\n g: parseInt(hex.slice(3, 5), 16),\n b: parseInt(hex.slice(5, 7), 16),\n };\n}\n\nfunction toHex(r: number, g: number, b: number) {\n return `#${[r, g, b].map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction luminance(hex: string) {\n const { r, g, b } = parseHex(hex);\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n}\n\nfunction lighten(hex: string, amount = 40) {\n const { r, g, b } = parseHex(hex);\n return toHex(r + amount, g + amount, b + amount);\n}\n\nfunction darken(hex: string, amount = 40) {\n const { r, g, b } = parseHex(hex);\n return toHex(r - amount, g - amount, b - amount);\n}\n\nexport function buildCustomTheme(colors: CustomColors): LandingTheme {\n const { primary, secondary = \"#f59e0b\", accent = \"#06b6d4\", surface = \"#ffffff\" } = colors;\n\n const onPrimary = luminance(primary) > 0.5 ? \"#111827\" : \"#ffffff\";\n const surfaceLum = luminance(surface);\n const isDarkSurface = surfaceLum < 0.5;\n\n return {\n id: \"custom\",\n label: \"Custom\",\n colors: {\n primary,\n \"primary-light\": lighten(primary),\n \"primary-dark\": darken(primary),\n secondary,\n accent,\n surface,\n \"surface-alt\": isDarkSurface ? lighten(surface, 20) : darken(surface, 5),\n \"on-surface\": isDarkSurface ? \"#f1f5f9\" : \"#111827\",\n \"on-surface-muted\": isDarkSurface ? \"#94a3b8\" : \"#6b7280\",\n \"on-primary\": onPrimary,\n },\n };\n}\n\nexport function buildCustomThemeCss(colors: CustomColors): string {\n const theme = buildCustomTheme(colors);\n return `[data-theme=\"custom\"] {\\n${buildCssVars(theme.colors)}\\n}`;\n}\n\n/** CSS custom properties for a theme */\nfunction buildCssVars(colors: LandingTheme[\"colors\"]): string {\n return Object.entries(colors)\n .map(([k, v]) => ` --color-${k}: ${v};`)\n .join(\"\\n\");\n}\n\n/** Build the tailwind.config JS object string for TW v3 CDN */\nfunction buildTailwindConfig(): string {\n const colorEntries = Object.keys(LANDING_THEMES[0].colors)\n .map((k) => ` '${k}': 'var(--color-${k})'`)\n .join(\",\\n\");\n\n return `{\n theme: {\n extend: {\n colors: {\n${colorEntries}\n }\n }\n }\n }`;\n}\n\nexport function buildThemeCss(): { css: string; tailwindConfig: string } {\n const defaultTheme = LANDING_THEMES[0];\n\n const overrides = LANDING_THEMES.slice(1)\n .map((t) => `[data-theme=\"${t.id}\"] {\\n${buildCssVars(t.colors)}\\n}`)\n .join(\"\\n\\n\");\n\n const css = `:root {\\n${buildCssVars(defaultTheme.colors)}\\n}\\n\\n${overrides}`;\n return { css, tailwindConfig: buildTailwindConfig() };\n}\n\nexport function buildSingleThemeCss(themeId: string): { css: string; tailwindConfig: string } {\n const theme = LANDING_THEMES.find((t) => t.id === themeId) || LANDING_THEMES[0];\n const css = `:root {\\n${buildCssVars(theme.colors)}\\n}`;\n return { css, tailwindConfig: buildTailwindConfig() };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/generate.ts"],"sourcesContent":["import { streamGenerate, dataUrlToImagePart, extractJsonObjects } from \"./streamCore\";\nimport { LANDING_THEMES } from \"./themes\";\nimport type { Section3 } from \"./types\";\n\nexport { extractJsonObjects };\n\nexport const SYSTEM_PROMPT = `You are a world-class web designer who creates AWARD-WINNING landing pages. Your designs win Awwwards, FWA, and CSS Design Awards. You think in terms of visual hierarchy, whitespace, and emotional impact.\n\nRULES:\n- Each section is a complete <section> tag with Tailwind CSS classes\n- Use Tailwind CDN classes ONLY (no custom CSS, no @apply, no @import, no @tailwind directives)\n- NO JavaScript, only HTML+Tailwind\n- Each section must be independent and self-contained\n- Responsive: mobile-first with sm/md/lg/xl breakpoints\n- All text content in Spanish unless the prompt specifies otherwise\n- Use real-looking content (not Lorem ipsum) — make it specific to the prompt\n\nRESPONSIVE — MANDATORY:\n- EVERY grid: grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 (NEVER grid-cols-3 alone)\n- EVERY flex row: flex flex-col md:flex-row (NEVER flex flex-row alone)\n- Text sizes: text-3xl md:text-5xl lg:text-7xl (NEVER text-7xl alone)\n- Images: w-full h-auto object-cover max-w-full\n- Padding: px-4 md:px-8 lg:px-16 (NEVER px-16 alone)\n- Hide decorative on mobile if breaks layout: hidden md:block\n\nIMAGES — CRITICAL:\n- EVERY image MUST use: <img data-image-query=\"english search query\" alt=\"description\" class=\"w-full h-auto object-cover rounded-xl\"/>\n- NEVER use <img> without data-image-query\n- NEVER include a src attribute — the system auto-replaces data-image-query with a real image URL\n- Queries must be generic stock-photo friendly (e.g. \"modern office\" not \"Juan's cybercafe\")\n- For avatar-like elements, use colored divs with initials instead of img tags (e.g. <div class=\"w-10 h-10 rounded-full bg-primary flex items-center justify-center text-on-primary font-bold\">JD</div>)\n\nIMAGE OVERLAYS — CRITICAL:\n- When using images as backgrounds or behind text, ALWAYS add a gradient overlay for text readability\n- Pattern: <div class=\"relative\"><img data-image-query=\"...\" alt=\"...\" class=\"absolute inset-0 w-full h-full object-cover\"/><div class=\"absolute inset-0 bg-gradient-to-r from-primary/80 to-transparent\"></div><div class=\"relative z-10\">...text...</div></div>\n- NEVER place text directly on images without an overlay\n- For hero sections with background images: use bg-gradient-to-t from-primary-dark/90 via-primary-dark/50 to-transparent\n- For testimonial/quote backgrounds: use bg-surface/90 backdrop-blur-sm on the card\n\nCOLOR SYSTEM — CRITICAL (READ CAREFULLY):\n- Use semantic color classes: bg-primary, text-primary, bg-primary-light, bg-primary-dark, text-on-primary, bg-surface, bg-surface-alt, text-on-surface, text-on-surface-muted, bg-secondary, text-secondary, bg-accent, text-accent\n- NEVER use hardcoded Tailwind color classes: NO bg-gray-*, bg-black, bg-white, bg-indigo-*, bg-blue-*, bg-purple-*, text-gray-*, text-black, text-white, etc.\n- The ONLY exception: border-gray-200 or border-gray-700 for subtle dividers.\n- ALL backgrounds MUST use: bg-primary, bg-primary-dark, bg-surface, bg-surface-alt\n- ALL text MUST use: text-on-surface, text-on-surface-muted, text-on-primary, text-accent. Use text-primary ONLY on bg-surface/bg-surface-alt (it's the same hue as bg-primary — invisible on primary backgrounds).\n- CONTRAST RULE: on bg-primary or bg-primary-dark → use ONLY text-on-primary. On bg-surface or bg-surface-alt → use text-on-surface, text-on-surface-muted, or text-primary. NEVER use text-primary on bg-primary — they are the SAME COLOR. NEVER put text-on-surface on bg-primary or text-on-primary on bg-surface. text-accent is decorative — use sparingly on bg-surface/bg-surface-alt only.\n- ANTI-PATTERN: NEVER put bg-primary on BOTH the section AND elements inside it. If section is bg-primary, inner cards/elements should be bg-surface. If section is bg-surface, cards can use bg-surface-alt or bg-primary.\n- For gradients: from-primary to-primary-dark, from-surface to-surface-alt\n- For hover: hover:bg-primary-dark, hover:bg-primary-light\n\nCOLOR VARIETY — MANDATORY:\n- ALTERNATE section backgrounds: bg-surface → bg-primary → bg-surface-alt → bg-primary-dark → bg-surface\n- Use bg-accent for at least ONE section (CTA or highlight section)\n- Use bg-secondary for at least ONE section (features or stats)\n- Cards on dark sections (bg-primary, bg-primary-dark) MUST be bg-surface (light cards on dark bg = visual pop)\n- Cards on light sections (bg-surface) can use bg-surface-alt or bg-primary-light\n- Use text-accent for decorative elements: labels, badges, icons, highlights, underlines\n- Use text-secondary for secondary information, tags, category labels\n- Gradients: mix colors creatively — from-primary to-accent for CTAs, from-secondary to-primary for headers\n- Buttons: primary CTA = bg-accent text-on-accent, secondary CTA = bg-secondary text-on-secondary or border-primary\n\nDESIGN PHILOSOPHY — what separates good from GREAT:\n- WHITESPACE is your best friend. Generous padding (py-24, py-32, px-8). Let elements breathe.\n- CONTRAST: mix dark sections with light ones. Alternate bg-primary and bg-surface sections.\n- TYPOGRAPHY: use extreme size differences for hierarchy (text-7xl headline next to text-sm label)\n- DEPTH: overlapping elements, negative margins (-mt-12), z-index layering, shadows\n- ASYMMETRY: avoid centering everything. Use grid-cols-5 with col-span-3 + col-span-2. Offset elements.\n- TEXTURE: use subtle patterns, gradients, border treatments, rounded-3xl mixed with sharp edges\n- Each section should have a COMPLETELY DIFFERENT layout from the others\n\nSECTION LAYOUT — CRITICAL:\n- Each <section> must be full-width (bg goes edge-to-edge). NO max-w on the section itself.\n- Constrain content inside with a wrapper div: <section class=\"bg-primary py-24\"><div class=\"max-w-7xl mx-auto px-4 md:px-8\">...content...</div></section>\n- EVERY section follows this pattern. The <section> handles bg color + vertical padding. The inner <div> handles horizontal padding + max-width.\n\nTESTIMONIALS SECTION:\n- Cards MUST use bg-surface or bg-surface-alt with text-on-surface\n- If section bg is bg-primary or bg-primary-dark, cards MUST be bg-surface (light cards on dark bg)\n- Quote text: text-on-surface, italic\n- Avatar: colored div with initials (bg-accent text-on-primary or bg-primary-light text-on-primary)\n- Name: text-on-surface font-semibold. Role/company: text-on-surface-muted\n- NEVER use same dark bg for both section AND cards\n\nHERO SECTION — your masterpiece:\n- Use a 2-column grid (lg:grid-cols-2) that fills the full height, NOT content floating in empty space\n- Left column: headline + description + CTAs, vertically centered with flex flex-col justify-center\n- Right column: large hero image (data-image-query) filling the column, or a bento-grid of image + stat cards\n- Bold oversized headline (text-4xl md:text-6xl lg:text-7xl font-black leading-tight)\n- Tag/label above headline (uppercase, tracking-wider, text-xs text-accent)\n- Short description paragraph (text-lg text-on-surface-muted, max-w-lg)\n- 2 CTAs: primary (large, px-8 py-4, with → arrow) + secondary (ghost/outlined)\n- Optional: social proof bar below CTAs (avatar stack + \"2,847+ users\" text)\n- Min height: min-h-[90vh] with items-center on the grid so content is vertically centered\n- CRITICAL: the grid must stretch to fill the section height. Use min-h-[90vh] on the grid container itself, not just the section\n- NEVER leave large empty areas — if using min-h-[90vh], content must be centered/distributed within it\n\nTAILWIND v3 NOTES:\n- Standard Tailwind v3 classes (shadow-sm, shadow-md, rounded-md, etc.)\n- Borders: border + border-gray-200 for visible borders`;\n\n/** @deprecated Use buildPromptSuffix(prompt) internally. Kept for backward compat. */\nexport const PROMPT_SUFFIX = `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Short Label\", \"html\": \"<section>...</section>\"}\n\nGenerate 7-9 sections. Always start with Hero and end with Footer.\nIMPORTANT: Make each section VISUALLY UNIQUE — different layouts, different background colors, different grid structures.\nThink like a premium design agency creating a $50K landing page.\nNO generic Bootstrap layouts. Use creative grids, bento layouts, overlapping elements, asymmetric columns.`;\n\nfunction buildPromptSuffix(userPrompt: string): string {\n // Extract section count from user prompt (e.g. \"3 secciones\", \"2 sections\", \"dame 4\")\n const countMatch = userPrompt.match(/(\\d+)\\s*(?:secciones?|sections?|bloques?|blocks?|partes?|pages?)/i)\n || userPrompt.match(/(?:genera|generar|crea|crear|haz|hazme|dame|quiero)\\s*(\\d+)/i);\n const count = countMatch ? parseInt(countMatch[1] || countMatch[2] || \"0\") : 0;\n const sectionInstruction = count > 0\n ? `Generate EXACTLY ${count} sections (no more, no less). Always include Hero as first and Footer as last.`\n : `Generate 7-9 sections. Always start with Hero and end with Footer.`;\n\n return `\n\nOUTPUT FORMAT: NDJSON — one JSON object per line, NO wrapper array, NO markdown fences.\nEach line: {\"label\": \"Short Label\", \"html\": \"<section>...</section>\"}\n\n${sectionInstruction}\nIMPORTANT: Make each section VISUALLY UNIQUE — different layouts, different background colors, different grid structures.\nThink like a premium design agency creating a $50K landing page.\nNO generic Bootstrap layouts. Use creative grids, bento layouts, overlapping elements, asymmetric columns.`;\n}\n\nexport interface GenerateOptions {\n anthropicApiKey?: string;\n openaiApiKey?: string;\n prompt: string;\n referenceImage?: string;\n extraInstructions?: string;\n systemPrompt?: string;\n model?: string;\n pexelsApiKey?: string;\n persistImage?: (tempUrl: string, query: string) => Promise<string>;\n onSection?: (section: Section3) => void;\n onImageUpdate?: (sectionId: string, html: string) => void;\n onDone?: (sections: Section3[]) => void;\n onError?: (error: Error) => void;\n /** Theme colors to inject into the AI prompt (deprecated — use themeName) */\n themeColors?: Record<string, string>;\n /** Theme name (e.g. \"minimal\", \"noche\", \"oceano\") — tells the AI the design mood */\n themeName?: string;\n /** Brand kit info for AI context */\n brandKit?: {\n fonts?: { heading?: string; body?: string };\n mood?: string;\n logoUrl?: string;\n };\n}\n\n/**\n * Generate a landing page with streaming AI + image enrichment.\n */\nconst THEME_DESCRIPTIONS: Record<string, string> = {\n minimal: \"Clean, light, professional — white surfaces, subtle grays, minimal decoration\",\n calido: \"Warm tones — amber/orange primary, cream surfaces, cozy and inviting feel\",\n oceano: \"Cool blue tones — deep ocean blues, clean white surfaces, professional and fresh\",\n noche: \"DARK theme — very dark surfaces, purple/violet accents, moody and modern. Surfaces are dark, text is light.\",\n bosque: \"Nature-inspired — deep green primary, warm earth tones, organic and grounded\",\n rosa: \"Soft pink/rose tones — delicate, feminine, elegant with light surfaces\",\n};\n\nfunction buildVisualContext(themeName?: string, brandKit?: GenerateOptions[\"brandKit\"]): string {\n if (!themeName && !brandKit) return \"\";\n\n const lines: string[] = [\"\\n\\n## Visual Context — MANDATORY\"];\n\n if (themeName && themeName !== \"custom\") {\n const desc = THEME_DESCRIPTIONS[themeName] || themeName;\n lines.push(`Active theme: \"${themeName}\" — ${desc}`);\n lines.push(\"The CSS variables for this theme are already loaded. Just use the semantic classes (bg-primary, text-on-surface, bg-accent, etc.) as described in the COLOR SYSTEM above. The theme handles the actual color values.\");\n const themeObj = LANDING_THEMES.find(t => t.id === themeName);\n if (themeObj) {\n lines.push(\"Actual color values for this theme:\");\n for (const [key, hex] of Object.entries(themeObj.colors)) {\n lines.push(` - ${key}: ${hex}`);\n }\n lines.push(\"Use these values to understand the visual mood. Generate designs that COMPLEMENT these specific colors.\");\n }\n }\n\n if (brandKit?.fonts) {\n const { heading, body } = brandKit.fonts;\n if (heading) lines.push(`- Heading font: use font-family: '${heading}' via inline style on h1-h6`);\n if (body) lines.push(`- Body font: use font-family: '${body}' via inline style on p, li, span`);\n }\n\n if (brandKit?.mood) {\n lines.push(`- Design mood: ${brandKit.mood} — adapt spacing, imagery style, and visual weight to match this mood`);\n }\n\n if (brandKit?.logoUrl) {\n lines.push(`- Brand logo: include <img src=\"${brandKit.logoUrl}\" alt=\"Logo\" class=\"h-8 w-auto\" /> in the navbar/hero area`);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport async function generateLanding(options: GenerateOptions): Promise<Section3[]> {\n const {\n prompt,\n referenceImage,\n extraInstructions,\n systemPrompt = SYSTEM_PROMPT,\n themeColors: _themeColors,\n themeName,\n brandKit,\n ...rest\n } = options;\n\n const visualContext = buildVisualContext(themeName, brandKit);\n const extra = extraInstructions ? `\\nAdditional instructions: ${extraInstructions}` : \"\";\n const content: any[] = [];\n\n if (referenceImage) {\n const converted = dataUrlToImagePart(referenceImage);\n if (converted) {\n content.push({ type: \"image\", ...converted });\n } else {\n content.push({ type: \"image\", image: referenceImage });\n }\n content.push({\n type: \"text\",\n text: `Generate a landing page for: ${prompt}${extra}\\n\\nIMPORTANT: Use the reference image as a DIRECT visual guide. Replicate its layout structure, grid arrangement, spacing, visual hierarchy, and section organization as closely as possible. Match the number of columns, element positioning, and overall composition. Adapt the content to the prompt but keep the visual DNA of the reference.${buildPromptSuffix(prompt)}`,\n });\n } else {\n content.push({\n type: \"text\",\n text: `Generate a landing page for: ${prompt}${extra}${buildPromptSuffix(prompt)}`,\n });\n }\n\n return streamGenerate({\n ...rest,\n systemPrompt: systemPrompt + visualContext,\n userContent: content,\n });\n}\n"],"mappings":"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","names":[]}