@easybits.cloud/html-tailwind-generator 0.1.5 → 0.2.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.
Files changed (48) hide show
  1. package/{src/images/enrichImages.ts → dist/chunk-5HSVOF2J.js} +65 -53
  2. package/dist/chunk-5HSVOF2J.js.map +1 -0
  3. package/{src/iframeScript.ts → dist/chunk-5TYGSZAF.js} +259 -10
  4. package/dist/chunk-5TYGSZAF.js.map +1 -0
  5. package/{src/refine.ts → dist/chunk-GMJR2GXL.js} +30 -60
  6. package/dist/chunk-GMJR2GXL.js.map +1 -0
  7. package/dist/chunk-LQ65H4AO.js +41 -0
  8. package/dist/chunk-LQ65H4AO.js.map +1 -0
  9. package/{src/generate.ts → dist/chunk-PK26CWDO.js} +67 -108
  10. package/dist/chunk-PK26CWDO.js.map +1 -0
  11. package/dist/chunk-RTGCZUNJ.js +1 -0
  12. package/dist/chunk-RTGCZUNJ.js.map +1 -0
  13. package/dist/chunk-XM3D3TTJ.js +852 -0
  14. package/dist/chunk-XM3D3TTJ.js.map +1 -0
  15. package/dist/components.d.ts +57 -0
  16. package/dist/components.js +14 -0
  17. package/dist/components.js.map +1 -0
  18. package/dist/deploy.d.ts +39 -0
  19. package/dist/deploy.js +10 -0
  20. package/dist/deploy.js.map +1 -0
  21. package/dist/generate.d.ts +41 -0
  22. package/dist/generate.js +14 -0
  23. package/dist/generate.js.map +1 -0
  24. package/dist/images.d.ts +30 -0
  25. package/dist/images.js +14 -0
  26. package/dist/images.js.map +1 -0
  27. package/dist/index.d.ts +30 -0
  28. package/dist/index.js +64 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/refine.d.ts +32 -0
  31. package/dist/refine.js +10 -0
  32. package/dist/refine.js.map +1 -0
  33. package/dist/themes-DOoj19c8.d.ts +35 -0
  34. package/dist/types-Flpl4wDs.d.ts +31 -0
  35. package/package.json +53 -50
  36. package/src/buildHtml.ts +0 -78
  37. package/src/components/Canvas.tsx +0 -162
  38. package/src/components/CodeEditor.tsx +0 -239
  39. package/src/components/FloatingToolbar.tsx +0 -350
  40. package/src/components/SectionList.tsx +0 -217
  41. package/src/components/index.ts +0 -4
  42. package/src/deploy.ts +0 -73
  43. package/src/images/dalleImages.ts +0 -29
  44. package/src/images/index.ts +0 -3
  45. package/src/images/pexels.ts +0 -27
  46. package/src/index.ts +0 -58
  47. package/src/themes.ts +0 -204
  48. package/src/types.ts +0 -30
@@ -1,13 +1,51 @@
1
- import { searchImage } from "./pexels";
2
- import { generateImage } from "./dalleImages";
1
+ // src/images/pexels.ts
2
+ async function searchImage(query, apiKey) {
3
+ const key = apiKey || process.env.PEXELS_API_KEY;
4
+ if (!key) return null;
5
+ try {
6
+ const res = await fetch(
7
+ `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=1&orientation=landscape`,
8
+ { headers: { Authorization: key } }
9
+ );
10
+ if (!res.ok) return null;
11
+ const data = await res.json();
12
+ const photo = data.photos?.[0];
13
+ if (!photo) return null;
14
+ return {
15
+ url: photo.src.large,
16
+ photographer: photo.photographer,
17
+ alt: photo.alt || query
18
+ };
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
3
23
 
4
- interface ImageMatch {
5
- query: string;
6
- searchStr: string;
7
- replaceStr: string;
24
+ // src/images/dalleImages.ts
25
+ async function generateImage(query, openaiApiKey) {
26
+ const res = await fetch("https://api.openai.com/v1/images/generations", {
27
+ method: "POST",
28
+ headers: {
29
+ Authorization: `Bearer ${openaiApiKey}`,
30
+ "Content-Type": "application/json"
31
+ },
32
+ body: JSON.stringify({
33
+ model: "dall-e-3",
34
+ prompt: query,
35
+ n: 1,
36
+ size: "1792x1024"
37
+ })
38
+ });
39
+ if (!res.ok) {
40
+ const err = await res.text().catch(() => "Unknown error");
41
+ throw new Error(`DALL-E API error ${res.status}: ${err}`);
42
+ }
43
+ const data = await res.json();
44
+ return data.data[0].url;
8
45
  }
9
46
 
10
- const FAKE_DOMAINS = [
47
+ // src/images/enrichImages.ts
48
+ var FAKE_DOMAINS = [
11
49
  "images.unsplash.com",
12
50
  "unsplash.com",
13
51
  "via.placeholder.com",
@@ -22,22 +60,13 @@ const FAKE_DOMAINS = [
22
60
  "fakeimg.pl",
23
61
  "example.com",
24
62
  "img.freepik.com",
25
- "cdn.pixabay.com",
63
+ "cdn.pixabay.com"
26
64
  ];
27
-
28
- /**
29
- * Find all images in HTML that need Pexels enrichment.
30
- * Two strategies:
31
- * 1. data-image-query="..." — AI followed instructions
32
- * 2. <img src="fake-url" — detect fake domains, use alt/class/nearby text as query
33
- */
34
- export function findImageSlots(html: string): ImageMatch[] {
35
- const matches: ImageMatch[] = [];
36
- const seen = new Set<string>();
37
-
38
- // 1. data-image-query="..."
65
+ function findImageSlots(html) {
66
+ const matches = [];
67
+ const seen = /* @__PURE__ */ new Set();
39
68
  const diqRegex = /data-image-query="([^"]+)"/g;
40
- let m: RegExpExecArray | null;
69
+ let m;
41
70
  while ((m = diqRegex.exec(html)) !== null) {
42
71
  const query = m[1];
43
72
  if (seen.has(query)) continue;
@@ -45,21 +74,16 @@ export function findImageSlots(html: string): ImageMatch[] {
45
74
  matches.push({
46
75
  query,
47
76
  searchStr: `data-image-query="${query}"`,
48
- replaceStr: `src="{url}" data-enriched="true"`,
77
+ replaceStr: `src="{url}" data-enriched="true"`
49
78
  });
50
79
  }
51
-
52
- // 2. <img with fake/non-existent src URLs
53
80
  const imgRegex = /<img\s[^>]*src="(https?:\/\/[^"]+)"[^>]*>/gi;
54
81
  while ((m = imgRegex.exec(html)) !== null) {
55
82
  const fullTag = m[0];
56
83
  const srcUrl = m[1];
57
-
58
84
  if (fullTag.includes("data-enriched")) continue;
59
85
  if (srcUrl.includes("pexels.com")) continue;
60
86
  if (seen.has(srcUrl)) continue;
61
-
62
- // Check if domain is fake
63
87
  let isFake = false;
64
88
  try {
65
89
  const domain = new URL(srcUrl).hostname;
@@ -68,48 +92,32 @@ export function findImageSlots(html: string): ImageMatch[] {
68
92
  isFake = true;
69
93
  }
70
94
  if (!isFake) continue;
71
-
72
- // Extract query: try alt, then class context, then URL path words
73
95
  const altMatch = fullTag.match(/alt="([^"]*?)"/);
74
96
  let query = altMatch?.[1]?.trim() || "";
75
-
76
97
  if (!query) {
77
- // Try to extract meaningful words from the URL path
78
98
  try {
79
99
  const path = new URL(srcUrl).pathname;
80
- const words = path
81
- .replace(/[^a-zA-Z]/g, " ")
82
- .split(/\s+/)
83
- .filter((w) => w.length > 2)
84
- .slice(0, 4)
85
- .join(" ");
100
+ const words = path.replace(/[^a-zA-Z]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 4).join(" ");
86
101
  if (words.length > 3) query = words;
87
- } catch { /* ignore */ }
102
+ } catch {
103
+ }
88
104
  }
89
-
90
105
  if (!query) query = "professional website hero image";
91
-
92
106
  seen.add(srcUrl);
93
107
  matches.push({
94
108
  query,
95
109
  searchStr: `src="${srcUrl}"`,
96
- replaceStr: `src="{url}" data-enriched="true"`,
110
+ replaceStr: `src="{url}" data-enriched="true"`
97
111
  });
98
112
  }
99
-
100
113
  return matches;
101
114
  }
102
-
103
- /**
104
- * Enrich all images in an HTML string with Pexels photos.
105
- */
106
- export async function enrichImages(html: string, pexelsApiKey?: string, openaiApiKey?: string): Promise<string> {
115
+ async function enrichImages(html, pexelsApiKey, openaiApiKey) {
107
116
  const slots = findImageSlots(html);
108
117
  if (slots.length === 0) return html;
109
-
110
118
  let result = html;
111
119
  const promises = slots.map(async (slot) => {
112
- let url: string | null = null;
120
+ let url = null;
113
121
  if (openaiApiKey) {
114
122
  url = await generateImage(slot.query, openaiApiKey).catch(() => null);
115
123
  }
@@ -121,15 +129,19 @@ export async function enrichImages(html: string, pexelsApiKey?: string, openaiAp
121
129
  const replacement = slot.replaceStr.replace("{url}", url);
122
130
  result = result.replaceAll(slot.searchStr, replacement);
123
131
  });
124
-
125
132
  await Promise.allSettled(promises);
126
-
127
- // Catch any remaining <img> tags without src (AI didn't follow instructions)
128
133
  result = result.replace(/<img\s(?![^>]*\bsrc=)([^>]*?)>/gi, (_match, attrs) => {
129
134
  const altMatch = attrs.match(/alt="([^"]*?)"/);
130
135
  const query = altMatch?.[1] || "professional image";
131
136
  return `<img src="https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}" ${attrs}>`;
132
137
  });
133
-
134
138
  return result;
135
139
  }
140
+
141
+ export {
142
+ searchImage,
143
+ generateImage,
144
+ findImageSlots,
145
+ enrichImages
146
+ };
147
+ //# sourceMappingURL=chunk-5HSVOF2J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/images/pexels.ts","../src/images/dalleImages.ts","../src/images/enrichImages.ts"],"sourcesContent":["export interface PexelsResult {\n url: string;\n photographer: string;\n alt: string;\n}\n\nexport async function searchImage(query: string, apiKey?: string): Promise<PexelsResult | null> {\n const key = apiKey || process.env.PEXELS_API_KEY;\n if (!key) return null;\n try {\n const res = await fetch(\n `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=1&orientation=landscape`,\n { headers: { Authorization: key } }\n );\n if (!res.ok) return null;\n const data = await res.json();\n const photo = data.photos?.[0];\n if (!photo) return null;\n return {\n url: photo.src.large,\n photographer: photo.photographer,\n alt: photo.alt || query,\n };\n } catch {\n return null;\n }\n}\n","/**\n * Generate an image using DALL-E 3 API.\n */\nexport async function generateImage(\n query: string,\n openaiApiKey: string\n): Promise<string> {\n const res = await fetch(\"https://api.openai.com/v1/images/generations\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${openaiApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: \"dall-e-3\",\n prompt: query,\n n: 1,\n size: \"1792x1024\",\n }),\n });\n\n if (!res.ok) {\n const err = await res.text().catch(() => \"Unknown error\");\n throw new Error(`DALL-E API error ${res.status}: ${err}`);\n }\n\n const data = await res.json();\n return data.data[0].url;\n}\n","import { searchImage } from \"./pexels\";\nimport { generateImage } from \"./dalleImages\";\n\ninterface ImageMatch {\n query: string;\n searchStr: string;\n replaceStr: string;\n}\n\nconst FAKE_DOMAINS = [\n \"images.unsplash.com\",\n \"unsplash.com\",\n \"via.placeholder.com\",\n \"placeholder.com\",\n \"placehold.co\",\n \"placehold.it\",\n \"placekitten.com\",\n \"picsum.photos\",\n \"loremflickr.com\",\n \"source.unsplash.com\",\n \"dummyimage.com\",\n \"fakeimg.pl\",\n \"example.com\",\n \"img.freepik.com\",\n \"cdn.pixabay.com\",\n];\n\n/**\n * Find all images in HTML that need Pexels enrichment.\n * Two strategies:\n * 1. data-image-query=\"...\" — AI followed instructions\n * 2. <img src=\"fake-url\" — detect fake domains, use alt/class/nearby text as query\n */\nexport function findImageSlots(html: string): ImageMatch[] {\n const matches: ImageMatch[] = [];\n const seen = new Set<string>();\n\n // 1. data-image-query=\"...\"\n const diqRegex = /data-image-query=\"([^\"]+)\"/g;\n let m: RegExpExecArray | null;\n while ((m = diqRegex.exec(html)) !== null) {\n const query = m[1];\n if (seen.has(query)) continue;\n seen.add(query);\n matches.push({\n query,\n searchStr: `data-image-query=\"${query}\"`,\n replaceStr: `src=\"{url}\" data-enriched=\"true\"`,\n });\n }\n\n // 2. <img with fake/non-existent src URLs\n const imgRegex = /<img\\s[^>]*src=\"(https?:\\/\\/[^\"]+)\"[^>]*>/gi;\n while ((m = imgRegex.exec(html)) !== null) {\n const fullTag = m[0];\n const srcUrl = m[1];\n\n if (fullTag.includes(\"data-enriched\")) continue;\n if (srcUrl.includes(\"pexels.com\")) continue;\n if (seen.has(srcUrl)) continue;\n\n // Check if domain is fake\n let isFake = false;\n try {\n const domain = new URL(srcUrl).hostname;\n isFake = FAKE_DOMAINS.some((d) => domain.includes(d));\n } catch {\n isFake = true;\n }\n if (!isFake) continue;\n\n // Extract query: try alt, then class context, then URL path words\n const altMatch = fullTag.match(/alt=\"([^\"]*?)\"/);\n let query = altMatch?.[1]?.trim() || \"\";\n\n if (!query) {\n // Try to extract meaningful words from the URL path\n try {\n const path = new URL(srcUrl).pathname;\n const words = path\n .replace(/[^a-zA-Z]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2)\n .slice(0, 4)\n .join(\" \");\n if (words.length > 3) query = words;\n } catch { /* ignore */ }\n }\n\n if (!query) query = \"professional website hero image\";\n\n seen.add(srcUrl);\n matches.push({\n query,\n searchStr: `src=\"${srcUrl}\"`,\n replaceStr: `src=\"{url}\" data-enriched=\"true\"`,\n });\n }\n\n return matches;\n}\n\n/**\n * Enrich all images in an HTML string with Pexels photos.\n */\nexport async function enrichImages(html: string, pexelsApiKey?: string, openaiApiKey?: string): Promise<string> {\n const slots = findImageSlots(html);\n if (slots.length === 0) return html;\n\n let result = html;\n const promises = slots.map(async (slot) => {\n let url: string | null = null;\n if (openaiApiKey) {\n url = await generateImage(slot.query, openaiApiKey).catch(() => null);\n }\n if (!url) {\n const img = await searchImage(slot.query, pexelsApiKey).catch(() => null);\n url = img?.url || null;\n }\n url ??= `https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(slot.query.slice(0, 30))}`;\n const replacement = slot.replaceStr.replace(\"{url}\", url);\n result = result.replaceAll(slot.searchStr, replacement);\n });\n\n await Promise.allSettled(promises);\n\n // Catch any remaining <img> tags without src (AI didn't follow instructions)\n result = result.replace(/<img\\s(?![^>]*\\bsrc=)([^>]*?)>/gi, (_match, attrs) => {\n const altMatch = attrs.match(/alt=\"([^\"]*?)\"/);\n const query = altMatch?.[1] || \"professional image\";\n return `<img src=\"https://placehold.co/800x500/1f2937/9ca3af?text=${encodeURIComponent(query.slice(0, 30))}\" ${attrs}>`;\n });\n\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,173 @@
1
- /**
2
- * JavaScript injected into the landing v3 iframe.
3
- * Handles hover highlights, click selection, contentEditable text editing,
4
- * postMessage communication with the parent editor,
5
- * and incremental section injection from parent.
6
- */
7
- export function getIframeScript(): string {
1
+ // src/themes.ts
2
+ var LANDING_THEMES = [
3
+ {
4
+ id: "default",
5
+ label: "Neutral",
6
+ colors: {
7
+ primary: "#18181b",
8
+ "primary-light": "#3f3f46",
9
+ "primary-dark": "#09090b",
10
+ secondary: "#a1a1aa",
11
+ accent: "#18181b",
12
+ surface: "#ffffff",
13
+ "surface-alt": "#fafafa",
14
+ "on-surface": "#18181b",
15
+ "on-surface-muted": "#71717a",
16
+ "on-primary": "#ffffff"
17
+ }
18
+ },
19
+ {
20
+ id: "dark",
21
+ label: "Dark",
22
+ colors: {
23
+ primary: "#e4e4e7",
24
+ "primary-light": "#f4f4f5",
25
+ "primary-dark": "#a1a1aa",
26
+ secondary: "#71717a",
27
+ accent: "#a78bfa",
28
+ surface: "#09090b",
29
+ "surface-alt": "#18181b",
30
+ "on-surface": "#fafafa",
31
+ "on-surface-muted": "#a1a1aa",
32
+ "on-primary": "#09090b"
33
+ }
34
+ },
35
+ {
36
+ id: "slate",
37
+ label: "Slate",
38
+ colors: {
39
+ primary: "#3b82f6",
40
+ "primary-light": "#60a5fa",
41
+ "primary-dark": "#2563eb",
42
+ secondary: "#64748b",
43
+ accent: "#3b82f6",
44
+ surface: "#ffffff",
45
+ "surface-alt": "#f8fafc",
46
+ "on-surface": "#0f172a",
47
+ "on-surface-muted": "#64748b",
48
+ "on-primary": "#ffffff"
49
+ }
50
+ },
51
+ {
52
+ id: "midnight",
53
+ label: "Midnight",
54
+ colors: {
55
+ primary: "#6366f1",
56
+ "primary-light": "#818cf8",
57
+ "primary-dark": "#4f46e5",
58
+ secondary: "#94a3b8",
59
+ accent: "#a78bfa",
60
+ surface: "#0f172a",
61
+ "surface-alt": "#1e293b",
62
+ "on-surface": "#e2e8f0",
63
+ "on-surface-muted": "#94a3b8",
64
+ "on-primary": "#ffffff"
65
+ }
66
+ },
67
+ {
68
+ id: "warm",
69
+ label: "Warm",
70
+ colors: {
71
+ primary: "#b45309",
72
+ "primary-light": "#d97706",
73
+ "primary-dark": "#92400e",
74
+ secondary: "#78716c",
75
+ accent: "#b45309",
76
+ surface: "#fafaf9",
77
+ "surface-alt": "#f5f5f4",
78
+ "on-surface": "#1c1917",
79
+ "on-surface-muted": "#78716c",
80
+ "on-primary": "#ffffff"
81
+ }
82
+ }
83
+ ];
84
+ function parseHex(hex) {
85
+ return {
86
+ r: parseInt(hex.slice(1, 3), 16),
87
+ g: parseInt(hex.slice(3, 5), 16),
88
+ b: parseInt(hex.slice(5, 7), 16)
89
+ };
90
+ }
91
+ function toHex(r, g, b) {
92
+ return `#${[r, g, b].map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, "0")).join("")}`;
93
+ }
94
+ function luminance(hex) {
95
+ const { r, g, b } = parseHex(hex);
96
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
97
+ }
98
+ function lighten(hex, amount = 40) {
99
+ const { r, g, b } = parseHex(hex);
100
+ return toHex(r + amount, g + amount, b + amount);
101
+ }
102
+ function darken(hex, amount = 40) {
103
+ const { r, g, b } = parseHex(hex);
104
+ return toHex(r - amount, g - amount, b - amount);
105
+ }
106
+ function buildCustomTheme(colors) {
107
+ const { primary, secondary = "#f59e0b", accent = "#06b6d4", surface = "#ffffff" } = colors;
108
+ const onPrimary = luminance(primary) > 0.5 ? "#111827" : "#ffffff";
109
+ const surfaceLum = luminance(surface);
110
+ const isDarkSurface = surfaceLum < 0.5;
111
+ return {
112
+ id: "custom",
113
+ label: "Custom",
114
+ colors: {
115
+ primary,
116
+ "primary-light": lighten(primary),
117
+ "primary-dark": darken(primary),
118
+ secondary,
119
+ accent,
120
+ surface,
121
+ "surface-alt": isDarkSurface ? lighten(surface, 20) : darken(surface, 5),
122
+ "on-surface": isDarkSurface ? "#f1f5f9" : "#111827",
123
+ "on-surface-muted": isDarkSurface ? "#94a3b8" : "#6b7280",
124
+ "on-primary": onPrimary
125
+ }
126
+ };
127
+ }
128
+ function buildCustomThemeCss(colors) {
129
+ const theme = buildCustomTheme(colors);
130
+ return `[data-theme="custom"] {
131
+ ${buildCssVars(theme.colors)}
132
+ }`;
133
+ }
134
+ function buildCssVars(colors) {
135
+ return Object.entries(colors).map(([k, v]) => ` --color-${k}: ${v};`).join("\n");
136
+ }
137
+ function buildTailwindConfig() {
138
+ const colorEntries = Object.keys(LANDING_THEMES[0].colors).map((k) => ` '${k}': 'var(--color-${k})'`).join(",\n");
139
+ return `{
140
+ theme: {
141
+ extend: {
142
+ colors: {
143
+ ${colorEntries}
144
+ }
145
+ }
146
+ }
147
+ }`;
148
+ }
149
+ function buildThemeCss() {
150
+ const defaultTheme = LANDING_THEMES[0];
151
+ const overrides = LANDING_THEMES.slice(1).map((t) => `[data-theme="${t.id}"] {
152
+ ${buildCssVars(t.colors)}
153
+ }`).join("\n\n");
154
+ const css = `:root {
155
+ ${buildCssVars(defaultTheme.colors)}
156
+ }
157
+
158
+ ${overrides}`;
159
+ return { css, tailwindConfig: buildTailwindConfig() };
160
+ }
161
+ function buildSingleThemeCss(themeId) {
162
+ const theme = LANDING_THEMES.find((t) => t.id === themeId) || LANDING_THEMES[0];
163
+ const css = `:root {
164
+ ${buildCssVars(theme.colors)}
165
+ }`;
166
+ return { css, tailwindConfig: buildTailwindConfig() };
167
+ }
168
+
169
+ // src/iframeScript.ts
170
+ function getIframeScript() {
8
171
  return `
9
172
  (function() {
10
173
  let hoveredEl = null;
@@ -70,7 +233,7 @@ export function getIframeScript(): string {
70
233
  hoveredEl = null;
71
234
  });
72
235
 
73
- // Click select element
236
+ // Click \u2014 select element
74
237
  document.addEventListener('click', function(e) {
75
238
  e.preventDefault();
76
239
  e.stopPropagation();
@@ -120,7 +283,7 @@ export function getIframeScript(): string {
120
283
  }, '*');
121
284
  }, true);
122
285
 
123
- // Double-click contentEditable for text
286
+ // Double-click \u2014 contentEditable for text
124
287
  document.addEventListener('dblclick', function(e) {
125
288
  e.preventDefault();
126
289
  e.stopPropagation();
@@ -178,7 +341,19 @@ export function getIframeScript(): string {
178
341
 
179
342
  if (msg.action === 'update-section') {
180
343
  var el = getSectionElement(msg.id);
181
- if (el) { el.innerHTML = msg.html; }
344
+ if (el && typeof window.morphdom === 'function') {
345
+ var tmp = document.createElement('div');
346
+ tmp.innerHTML = msg.html;
347
+ window.morphdom(el, tmp, {
348
+ childrenOnly: true,
349
+ onBeforeElUpdated: function(fromEl, toEl) {
350
+ if (fromEl.isEqualNode(toEl)) return false;
351
+ return true;
352
+ }
353
+ });
354
+ } else if (el) {
355
+ el.innerHTML = msg.html;
356
+ }
182
357
  }
183
358
 
184
359
  if (msg.action === 'remove-section') {
@@ -259,3 +434,77 @@ export function getIframeScript(): string {
259
434
  })();
260
435
  `;
261
436
  }
437
+
438
+ // src/buildHtml.ts
439
+ function buildPreviewHtml(sections, theme) {
440
+ const sorted = [...sections].sort((a, b) => a.order - b.order);
441
+ const body = sorted.map((s) => `<div data-section-id="${s.id}">${s.html}</div>`).join("\n");
442
+ const dataTheme = theme && theme !== "default" ? ` data-theme="${theme}"` : "";
443
+ const { css, tailwindConfig } = buildThemeCss();
444
+ return `<!DOCTYPE html>
445
+ <html lang="es"${dataTheme}>
446
+ <head>
447
+ <meta charset="UTF-8"/>
448
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
449
+ <script src="https://cdn.tailwindcss.com"></script>
450
+ <script src="https://unpkg.com/morphdom@2.7.4/dist/morphdom-umd.min.js"></script>
451
+ <script>tailwind.config = ${tailwindConfig}</script>
452
+ <style>
453
+ ${css}
454
+ *{margin:0;padding:0;box-sizing:border-box}
455
+ html{scroll-behavior:smooth}
456
+ body{font-family:system-ui,-apple-system,sans-serif;background-color:var(--color-surface);color:var(--color-on-surface)}
457
+ img{max-width:100%}
458
+ [contenteditable="true"]{cursor:text}
459
+ </style>
460
+ </head>
461
+ <body class="bg-surface text-on-surface">
462
+ ${body}
463
+ <script>${getIframeScript()}</script>
464
+ </body>
465
+ </html>`;
466
+ }
467
+ function buildDeployHtml(sections, theme, customColors) {
468
+ const sorted = [...sections].sort((a, b) => a.order - b.order);
469
+ const body = sorted.map((s) => s.html).join("\n");
470
+ const isCustom = theme === "custom" && customColors;
471
+ const dataTheme = theme && theme !== "default" && !isCustom ? ` data-theme="${theme}"` : "";
472
+ const { css: baseCss, tailwindConfig } = isCustom ? (() => {
473
+ const ct = buildCustomTheme(customColors);
474
+ const vars = Object.entries(ct.colors).map(([k, v]) => ` --color-${k}: ${v};`).join("\n");
475
+ return { css: `:root {
476
+ ${vars}
477
+ }`, tailwindConfig: buildSingleThemeCss("default").tailwindConfig };
478
+ })() : buildSingleThemeCss(theme || "default");
479
+ return `<!DOCTYPE html>
480
+ <html lang="es"${dataTheme}>
481
+ <head>
482
+ <meta charset="UTF-8"/>
483
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
484
+ <title>Landing Page</title>
485
+ <script src="https://cdn.tailwindcss.com"></script>
486
+ <script>tailwind.config = ${tailwindConfig}</script>
487
+ <style>
488
+ ${baseCss}
489
+ *{margin:0;padding:0;box-sizing:border-box}
490
+ html{scroll-behavior:smooth}
491
+ body{font-family:system-ui,-apple-system,sans-serif;background-color:var(--color-surface);color:var(--color-on-surface)}
492
+ </style>
493
+ </head>
494
+ <body class="bg-surface text-on-surface">
495
+ ${body}
496
+ </body>
497
+ </html>`;
498
+ }
499
+
500
+ export {
501
+ LANDING_THEMES,
502
+ buildCustomTheme,
503
+ buildCustomThemeCss,
504
+ buildThemeCss,
505
+ buildSingleThemeCss,
506
+ getIframeScript,
507
+ buildPreviewHtml,
508
+ buildDeployHtml
509
+ };
510
+ //# sourceMappingURL=chunk-5TYGSZAF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/themes.ts","../src/iframeScript.ts","../src/buildHtml.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: \"default\",\n label: \"Neutral\",\n colors: {\n primary: \"#18181b\",\n \"primary-light\": \"#3f3f46\",\n \"primary-dark\": \"#09090b\",\n secondary: \"#a1a1aa\",\n accent: \"#18181b\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#fafafa\",\n \"on-surface\": \"#18181b\",\n \"on-surface-muted\": \"#71717a\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"dark\",\n label: \"Dark\",\n colors: {\n primary: \"#e4e4e7\",\n \"primary-light\": \"#f4f4f5\",\n \"primary-dark\": \"#a1a1aa\",\n secondary: \"#71717a\",\n accent: \"#a78bfa\",\n surface: \"#09090b\",\n \"surface-alt\": \"#18181b\",\n \"on-surface\": \"#fafafa\",\n \"on-surface-muted\": \"#a1a1aa\",\n \"on-primary\": \"#09090b\",\n },\n },\n {\n id: \"slate\",\n label: \"Slate\",\n colors: {\n primary: \"#3b82f6\",\n \"primary-light\": \"#60a5fa\",\n \"primary-dark\": \"#2563eb\",\n secondary: \"#64748b\",\n accent: \"#3b82f6\",\n surface: \"#ffffff\",\n \"surface-alt\": \"#f8fafc\",\n \"on-surface\": \"#0f172a\",\n \"on-surface-muted\": \"#64748b\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"midnight\",\n label: \"Midnight\",\n colors: {\n primary: \"#6366f1\",\n \"primary-light\": \"#818cf8\",\n \"primary-dark\": \"#4f46e5\",\n secondary: \"#94a3b8\",\n accent: \"#a78bfa\",\n surface: \"#0f172a\",\n \"surface-alt\": \"#1e293b\",\n \"on-surface\": \"#e2e8f0\",\n \"on-surface-muted\": \"#94a3b8\",\n \"on-primary\": \"#ffffff\",\n },\n },\n {\n id: \"warm\",\n label: \"Warm\",\n colors: {\n primary: \"#b45309\",\n \"primary-light\": \"#d97706\",\n \"primary-dark\": \"#92400e\",\n secondary: \"#78716c\",\n accent: \"#b45309\",\n surface: \"#fafaf9\",\n \"surface-alt\": \"#f5f5f4\",\n \"on-surface\": \"#1c1917\",\n \"on-surface-muted\": \"#78716c\",\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","/**\n * JavaScript injected into the landing v3 iframe.\n * Handles hover highlights, click selection, contentEditable text editing,\n * postMessage communication with the parent editor,\n * and incremental section injection from parent.\n */\nexport function getIframeScript(): string {\n return `\n(function() {\n let hoveredEl = null;\n let selectedEl = null;\n const OUTLINE_HOVER = '2px solid #3B82F6';\n const OUTLINE_SELECTED = '2px solid #8B5CF6';\n\n function getSectionId(el) {\n let node = el;\n while (node && node !== document.body) {\n if (node.dataset && node.dataset.sectionId) {\n return node.dataset.sectionId;\n }\n node = node.parentElement;\n }\n return null;\n }\n\n function getSectionElement(sectionId) {\n return document.querySelector('[data-section-id=\"' + sectionId + '\"]');\n }\n\n function getElementPath(el) {\n const parts = [];\n let node = el;\n while (node && node !== document.body) {\n let tag = node.tagName.toLowerCase();\n if (node.id) { tag += '#' + node.id; }\n const siblings = node.parentElement ? Array.from(node.parentElement.children).filter(function(c) { return c.tagName === node.tagName; }) : [];\n if (siblings.length > 1) { tag += ':nth(' + siblings.indexOf(node) + ')'; }\n parts.unshift(tag);\n node = node.parentElement;\n }\n return parts.join(' > ');\n }\n\n function isTextElement(el) {\n var textTags = ['H1','H2','H3','H4','H5','H6','P','SPAN','LI','A','BLOCKQUOTE','LABEL','TD','TH','FIGCAPTION','BUTTON'];\n return textTags.indexOf(el.tagName) !== -1;\n }\n\n // Hover\n document.addEventListener('mouseover', function(e) {\n var el = e.target;\n if (el === document.body || el === document.documentElement) return;\n if (el === selectedEl) return;\n if (hoveredEl && hoveredEl !== selectedEl) {\n hoveredEl.style.outline = '';\n hoveredEl.style.outlineOffset = '';\n }\n hoveredEl = el;\n if (el !== selectedEl) {\n el.style.outline = OUTLINE_HOVER;\n el.style.outlineOffset = '-2px';\n }\n });\n\n document.addEventListener('mouseout', function(e) {\n if (hoveredEl && hoveredEl !== selectedEl) {\n hoveredEl.style.outline = '';\n hoveredEl.style.outlineOffset = '';\n }\n hoveredEl = null;\n });\n\n // Click — select element\n document.addEventListener('click', function(e) {\n e.preventDefault();\n e.stopPropagation();\n var el = e.target;\n\n // Deselect previous\n if (selectedEl) {\n selectedEl.style.outline = '';\n selectedEl.style.outlineOffset = '';\n }\n\n if (selectedEl === el) {\n selectedEl = null;\n window.parent.postMessage({ type: 'element-deselected' }, '*');\n return;\n }\n\n selectedEl = el;\n\n // Clear hover styles BEFORE capturing openTag (so it matches source HTML)\n el.style.outline = '';\n el.style.outlineOffset = '';\n var openTag = el.outerHTML.substring(0, el.outerHTML.indexOf('>') + 1).substring(0, 120);\n\n el.style.outline = OUTLINE_SELECTED;\n el.style.outlineOffset = '-2px';\n\n var rect = el.getBoundingClientRect();\n var attrs = {};\n if (el.tagName === 'IMG') {\n attrs = { src: el.getAttribute('src') || '', alt: el.getAttribute('alt') || '' };\n }\n if (el.tagName === 'A') {\n attrs = { href: el.getAttribute('href') || '', target: el.getAttribute('target') || '' };\n }\n\n window.parent.postMessage({\n type: 'element-selected',\n sectionId: getSectionId(el),\n tagName: el.tagName,\n rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n text: (el.textContent || '').substring(0, 200),\n openTag: openTag,\n elementPath: getElementPath(el),\n isSectionRoot: el.dataset && el.dataset.sectionId ? true : false,\n attrs: attrs,\n }, '*');\n }, true);\n\n // Double-click — contentEditable for text\n document.addEventListener('dblclick', function(e) {\n e.preventDefault();\n e.stopPropagation();\n var el = e.target;\n if (!isTextElement(el)) return;\n\n el.contentEditable = 'true';\n el.focus();\n el.style.outline = '2px dashed #F59E0B';\n el.style.outlineOffset = '-2px';\n\n function onBlur() {\n el.contentEditable = 'false';\n el.style.outline = '';\n el.style.outlineOffset = '';\n el.removeEventListener('blur', onBlur);\n el.removeEventListener('keydown', onKeydown);\n\n var sid = getSectionId(el);\n var sectionEl = sid ? getSectionElement(sid) : null;\n window.parent.postMessage({\n type: 'text-edited',\n sectionId: sid,\n elementPath: getElementPath(el),\n newText: el.innerHTML,\n sectionHtml: sectionEl ? sectionEl.innerHTML : null,\n }, '*');\n\n selectedEl = null;\n }\n\n function onKeydown(ev) {\n if (ev.key === 'Escape') {\n el.blur();\n }\n }\n\n el.addEventListener('blur', onBlur);\n el.addEventListener('keydown', onKeydown);\n }, true);\n\n // Listen for messages FROM parent (incremental section injection)\n window.addEventListener('message', function(e) {\n var msg = e.data;\n if (!msg || !msg.action) return;\n\n if (msg.action === 'add-section') {\n var wrapper = document.createElement('div');\n wrapper.setAttribute('data-section-id', msg.id);\n wrapper.innerHTML = msg.html;\n wrapper.style.animation = 'fadeInUp 0.4s ease-out';\n document.body.appendChild(wrapper);\n wrapper.scrollIntoView({ behavior: 'smooth', block: 'end' });\n }\n\n if (msg.action === 'update-section') {\n var el = getSectionElement(msg.id);\n if (el && typeof window.morphdom === 'function') {\n var tmp = document.createElement('div');\n tmp.innerHTML = msg.html;\n window.morphdom(el, tmp, {\n childrenOnly: true,\n onBeforeElUpdated: function(fromEl, toEl) {\n if (fromEl.isEqualNode(toEl)) return false;\n return true;\n }\n });\n } else if (el) {\n el.innerHTML = msg.html;\n }\n }\n\n if (msg.action === 'remove-section') {\n var el = getSectionElement(msg.id);\n if (el) { el.remove(); }\n }\n\n if (msg.action === 'reorder-sections') {\n // msg.order = [id1, id2, id3, ...]\n var order = msg.order;\n for (var i = 0; i < order.length; i++) {\n var el = getSectionElement(order[i]);\n if (el) { document.body.appendChild(el); }\n }\n }\n\n if (msg.action === 'update-attribute') {\n var sectionEl = getSectionElement(msg.sectionId);\n if (sectionEl) {\n var target = null;\n if (msg.elementPath) {\n // Find element by matching path\n var allEls = sectionEl.querySelectorAll(msg.tagName || '*');\n for (var i = 0; i < allEls.length; i++) {\n if (getElementPath(allEls[i]) === msg.elementPath) {\n target = allEls[i];\n break;\n }\n }\n }\n if (target) {\n target.setAttribute(msg.attr, msg.value);\n window.parent.postMessage({\n type: 'section-html-updated',\n sectionId: msg.sectionId,\n sectionHtml: sectionEl.innerHTML,\n }, '*');\n }\n }\n }\n\n if (msg.action === 'set-theme') {\n if (msg.theme && msg.theme !== 'default') {\n document.documentElement.setAttribute('data-theme', msg.theme);\n } else {\n document.documentElement.removeAttribute('data-theme');\n }\n }\n\n if (msg.action === 'set-custom-css') {\n var customStyle = document.getElementById('custom-theme-css');\n if (!customStyle) {\n customStyle = document.createElement('style');\n customStyle.id = 'custom-theme-css';\n document.head.appendChild(customStyle);\n }\n customStyle.textContent = msg.css || '';\n }\n\n if (msg.action === 'scroll-to-section') {\n var el = getSectionElement(msg.id);\n if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }\n }\n\n if (msg.action === 'full-rewrite') {\n // Fallback: rewrite everything\n document.body.innerHTML = msg.html;\n }\n });\n\n // Inject animation keyframe\n var style = document.createElement('style');\n style.textContent = '@keyframes fadeInUp { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:translateY(0); } }';\n document.head.appendChild(style);\n\n // Notify parent we're ready\n window.parent.postMessage({ type: 'ready' }, '*');\n})();\n`;\n}\n","import type { Section3 } from \"./types\";\nimport { getIframeScript } from \"./iframeScript\";\nimport { buildThemeCss, buildSingleThemeCss, buildCustomTheme, type CustomColors } from \"./themes\";\n\n/**\n * Build the full HTML for the iframe preview (with editing script).\n */\nexport function buildPreviewHtml(sections: Section3[], theme?: string): string {\n const sorted = [...sections].sort((a, b) => a.order - b.order);\n const body = sorted\n .map((s) => `<div data-section-id=\"${s.id}\">${s.html}</div>`)\n .join(\"\\n\");\n\n const dataTheme = theme && theme !== \"default\" ? ` data-theme=\"${theme}\"` : \"\";\n const { css, tailwindConfig } = buildThemeCss();\n\n return `<!DOCTYPE html>\n<html lang=\"es\"${dataTheme}>\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n<script src=\"https://cdn.tailwindcss.com\"></script>\n<script src=\"https://unpkg.com/morphdom@2.7.4/dist/morphdom-umd.min.js\"></script>\n<script>tailwind.config = ${tailwindConfig}</script>\n<style>\n${css}\n*{margin:0;padding:0;box-sizing:border-box}\nhtml{scroll-behavior:smooth}\nbody{font-family:system-ui,-apple-system,sans-serif;background-color:var(--color-surface);color:var(--color-on-surface)}\nimg{max-width:100%}\n[contenteditable=\"true\"]{cursor:text}\n</style>\n</head>\n<body class=\"bg-surface text-on-surface\">\n${body}\n<script>${getIframeScript()}</script>\n</body>\n</html>`;\n}\n\n/**\n * Build the deploy HTML (no editing script, clean output).\n */\nexport function buildDeployHtml(sections: Section3[], theme?: string, customColors?: CustomColors): string {\n const sorted = [...sections].sort((a, b) => a.order - b.order);\n const body = sorted.map((s) => s.html).join(\"\\n\");\n\n const isCustom = theme === \"custom\" && customColors;\n const dataTheme = theme && theme !== \"default\" && !isCustom ? ` data-theme=\"${theme}\"` : \"\";\n\n // For custom theme, build CSS from the custom colors directly (no data-theme needed, inject as :root)\n const { css: baseCss, tailwindConfig } = isCustom\n ? (() => {\n const ct = buildCustomTheme(customColors);\n const vars = Object.entries(ct.colors).map(([k, v]) => ` --color-${k}: ${v};`).join(\"\\n\");\n return { css: `:root {\\n${vars}\\n}`, tailwindConfig: buildSingleThemeCss(\"default\").tailwindConfig };\n })()\n : buildSingleThemeCss(theme || \"default\");\n\n return `<!DOCTYPE html>\n<html lang=\"es\"${dataTheme}>\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n<title>Landing Page</title>\n<script src=\"https://cdn.tailwindcss.com\"></script>\n<script>tailwind.config = ${tailwindConfig}</script>\n<style>\n${baseCss}\n*{margin:0;padding:0;box-sizing:border-box}\nhtml{scroll-behavior:smooth}\nbody{font-family:system-ui,-apple-system,sans-serif;background-color:var(--color-surface);color:var(--color-on-surface)}\n</style>\n</head>\n<body class=\"bg-surface text-on-surface\">\n${body}\n</body>\n</html>`;\n}\n"],"mappings":"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","names":[]}