@easybits.cloud/html-tailwind-generator 0.2.125 → 0.2.127

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 (35) hide show
  1. package/dist/{ViewportToggle-DefLZWrz.d.ts → ViewportToggle-Dszv1gsL.d.ts} +3 -3
  2. package/dist/buildHtmlV4.d.ts +14 -0
  3. package/dist/buildHtmlV4.js +8 -0
  4. package/dist/buildHtmlV4.js.map +1 -0
  5. package/dist/chunk-7LKFTF32.js +54 -0
  6. package/dist/chunk-7LKFTF32.js.map +1 -0
  7. package/dist/{chunk-ZARS4VIK.js → chunk-CQXBVGVC.js} +4 -4
  8. package/dist/chunk-KXOAEC33.js +67 -0
  9. package/dist/chunk-KXOAEC33.js.map +1 -0
  10. package/dist/chunk-LI4UDDMT.js +117 -0
  11. package/dist/chunk-LI4UDDMT.js.map +1 -0
  12. package/dist/{chunk-T4F6F3HD.js → chunk-RJQKHWIH.js} +4 -4
  13. package/dist/{chunk-6LK7JW6Q.js → chunk-X5MEN76P.js} +52 -22
  14. package/dist/chunk-X5MEN76P.js.map +1 -0
  15. package/dist/components.d.ts +1 -1
  16. package/dist/components4.d.ts +121 -0
  17. package/dist/components4.js +1880 -0
  18. package/dist/components4.js.map +1 -0
  19. package/dist/directions.d.ts +8 -42
  20. package/dist/generate.js +2 -2
  21. package/dist/generateDocument.d.ts +40 -48
  22. package/dist/generateDocument.js +9 -3
  23. package/dist/grapesToSections.d.ts +12 -0
  24. package/dist/grapesToSections.js +7 -0
  25. package/dist/grapesToSections.js.map +1 -0
  26. package/dist/hooks/useThumbnailCapture.d.ts +12 -0
  27. package/dist/hooks/useThumbnailCapture.js +7 -0
  28. package/dist/hooks/useThumbnailCapture.js.map +1 -0
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +24 -12
  31. package/dist/refine.js +2 -2
  32. package/package.json +26 -2
  33. package/dist/chunk-6LK7JW6Q.js.map +0 -1
  34. /package/dist/{chunk-ZARS4VIK.js.map → chunk-CQXBVGVC.js.map} +0 -0
  35. /package/dist/{chunk-T4F6F3HD.js.map → chunk-RJQKHWIH.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React__default from 'react';
2
2
  import { S as Section3, I as IframeMessage } from './types-BIpbpCJr.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { C as CustomColors, a as LandingTheme } from './themes-DNTBHJUH.js';
@@ -11,10 +11,10 @@ interface CanvasProps {
11
11
  sections: Section3[];
12
12
  theme?: string;
13
13
  onMessage: (msg: IframeMessage) => void;
14
- iframeRectRef: React.MutableRefObject<DOMRect | null>;
14
+ iframeRectRef: React__default.MutableRefObject<DOMRect | null>;
15
15
  onReady?: () => void;
16
16
  }
17
- declare const Canvas: React.ForwardRefExoticComponent<CanvasProps & React.RefAttributes<CanvasHandle>>;
17
+ declare const Canvas: React__default.ForwardRefExoticComponent<CanvasProps & React__default.RefAttributes<CanvasHandle>>;
18
18
 
19
19
  interface SectionListProps {
20
20
  sections: Section3[];
@@ -0,0 +1,14 @@
1
+ import { S as Section3 } from './types-BIpbpCJr.js';
2
+
3
+ /**
4
+ * Build deploy HTML for Landings v4 (GrapesJS).
5
+ * Includes Tailwind CDN + theme CSS variables + any GrapesJS-generated CSS.
6
+ */
7
+ declare function buildDeployHtmlV4(sections: Section3[], opts?: {
8
+ showBranding?: boolean;
9
+ title?: string;
10
+ themeName?: string;
11
+ customColors?: Record<string, string>;
12
+ }): string;
13
+
14
+ export { buildDeployHtmlV4 };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildDeployHtmlV4
3
+ } from "./chunk-KXOAEC33.js";
4
+ import "./chunk-VV5I53WR.js";
5
+ export {
6
+ buildDeployHtmlV4
7
+ };
8
+ //# sourceMappingURL=buildHtmlV4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,54 @@
1
+ // src/grapesToSections.ts
2
+ var idCounter = 0;
3
+ function stableId() {
4
+ return `s4_${Date.now().toString(36)}_${(idCounter++).toString(36)}`;
5
+ }
6
+ function grapesToSections(html) {
7
+ if (!html || !html.trim()) return [];
8
+ const parser = new DOMParser();
9
+ const doc = parser.parseFromString(html, "text/html");
10
+ const styleTags = [
11
+ ...Array.from(doc.head.querySelectorAll("style")),
12
+ ...Array.from(doc.body.querySelectorAll("style"))
13
+ ];
14
+ const cssContent = styleTags.map((s) => s.textContent || "").join("\n").trim();
15
+ doc.body.querySelectorAll("style").forEach((s) => s.remove());
16
+ const children = Array.from(doc.body.children);
17
+ const sections = [];
18
+ if (cssContent) {
19
+ sections.push({
20
+ id: "__grapes_css__",
21
+ order: -1,
22
+ html: `<style>${cssContent}</style>`,
23
+ label: "__css__"
24
+ });
25
+ }
26
+ if (children.length === 0 && doc.body.innerHTML.trim()) {
27
+ sections.push({
28
+ id: stableId(),
29
+ order: 0,
30
+ html: `<section>${doc.body.innerHTML.trim()}</section>`,
31
+ label: "Section 1"
32
+ });
33
+ return sections;
34
+ }
35
+ let order = 0;
36
+ for (const el of children) {
37
+ if (el.tagName !== "SECTION" && !el.getAttribute("data-section-id") && !el.textContent?.trim() && !el.querySelector("img, video, svg, canvas, iframe")) {
38
+ continue;
39
+ }
40
+ const id = el.getAttribute("data-section-id") || stableId();
41
+ sections.push({
42
+ id,
43
+ order: order++,
44
+ html: el.outerHTML,
45
+ label: el.getAttribute("data-label") || (el.tagName === "SECTION" ? `Section ${order}` : `Block ${order}`)
46
+ });
47
+ }
48
+ return sections;
49
+ }
50
+
51
+ export {
52
+ grapesToSections
53
+ };
54
+ //# sourceMappingURL=chunk-7LKFTF32.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/grapesToSections.ts"],"sourcesContent":["import type { Section3 } from \"./types\";\n\n/** Simple counter-based ID to keep stable across saves within a session */\nlet idCounter = 0;\nfunction stableId() {\n return `s4_${Date.now().toString(36)}_${(idCounter++).toString(36)}`;\n}\n\n/**\n * Extract Section3[] from GrapesJS editor HTML (which may include a <style> block).\n * The <style> block with GrapesJS CSS is stored as a special first section.\n * Each top-level <section> or element becomes one Section3 entry.\n *\n * NOTE: Browser-only — uses DOMParser.\n */\nexport function grapesToSections(html: string): Section3[] {\n if (!html || !html.trim()) return [];\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(html, \"text/html\");\n\n // Extract <style> tags (GrapesJS CSS) from head and body\n const styleTags = [\n ...Array.from(doc.head.querySelectorAll(\"style\")),\n ...Array.from(doc.body.querySelectorAll(\"style\")),\n ];\n const cssContent = styleTags.map((s) => s.textContent || \"\").join(\"\\n\").trim();\n // Remove style tags from body before processing elements\n doc.body.querySelectorAll(\"style\").forEach((s) => s.remove());\n\n const children = Array.from(doc.body.children);\n const sections: Section3[] = [];\n\n // Store CSS as a special section if present\n if (cssContent) {\n sections.push({\n id: \"__grapes_css__\",\n order: -1,\n html: `<style>${cssContent}</style>`,\n label: \"__css__\",\n });\n }\n\n if (children.length === 0 && doc.body.innerHTML.trim()) {\n sections.push({\n id: stableId(),\n order: 0,\n html: `<section>${doc.body.innerHTML.trim()}</section>`,\n label: \"Section 1\",\n });\n return sections;\n }\n\n let order = 0;\n for (const el of children) {\n // Skip empty non-section elements without data-section-id (GrapesJS ghost wrappers)\n if (\n el.tagName !== \"SECTION\" &&\n !el.getAttribute(\"data-section-id\") &&\n !el.textContent?.trim() &&\n !el.querySelector(\"img, video, svg, canvas, iframe\")\n ) {\n continue;\n }\n\n const id =\n el.getAttribute(\"data-section-id\") || stableId();\n\n sections.push({\n id,\n order: order++,\n html: el.outerHTML,\n label:\n el.getAttribute(\"data-label\") ||\n (el.tagName === \"SECTION\" ? `Section ${order}` : `Block ${order}`),\n });\n }\n\n return sections;\n}\n"],"mappings":";AAGA,IAAI,YAAY;AAChB,SAAS,WAAW;AAClB,SAAO,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,KAAK,aAAa,SAAS,EAAE,CAAC;AACpE;AASO,SAAS,iBAAiB,MAA0B;AACzD,MAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAG,QAAO,CAAC;AAEnC,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,MAAM,OAAO,gBAAgB,MAAM,WAAW;AAGpD,QAAM,YAAY;AAAA,IAChB,GAAG,MAAM,KAAK,IAAI,KAAK,iBAAiB,OAAO,CAAC;AAAA,IAChD,GAAG,MAAM,KAAK,IAAI,KAAK,iBAAiB,OAAO,CAAC;AAAA,EAClD;AACA,QAAM,aAAa,UAAU,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,KAAK,IAAI,EAAE,KAAK;AAE7E,MAAI,KAAK,iBAAiB,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;AAE5D,QAAM,WAAW,MAAM,KAAK,IAAI,KAAK,QAAQ;AAC7C,QAAM,WAAuB,CAAC;AAG9B,MAAI,YAAY;AACd,aAAS,KAAK;AAAA,MACZ,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,UAAU,UAAU;AAAA,MAC1B,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,MAAI,SAAS,WAAW,KAAK,IAAI,KAAK,UAAU,KAAK,GAAG;AACtD,aAAS,KAAK;AAAA,MACZ,IAAI,SAAS;AAAA,MACb,OAAO;AAAA,MACP,MAAM,YAAY,IAAI,KAAK,UAAU,KAAK,CAAC;AAAA,MAC3C,OAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ;AACZ,aAAW,MAAM,UAAU;AAEzB,QACE,GAAG,YAAY,aACf,CAAC,GAAG,aAAa,iBAAiB,KAClC,CAAC,GAAG,aAAa,KAAK,KACtB,CAAC,GAAG,cAAc,iCAAiC,GACnD;AACA;AAAA,IACF;AAEA,UAAM,KACJ,GAAG,aAAa,iBAAiB,KAAK,SAAS;AAEjD,aAAS,KAAK;AAAA,MACZ;AAAA,MACA,OAAO;AAAA,MACP,MAAM,GAAG;AAAA,MACT,OACE,GAAG,aAAa,YAAY,MAC3B,GAAG,YAAY,YAAY,WAAW,KAAK,KAAK,SAAS,KAAK;AAAA,IACnE,CAAC;AAAA,EACH;AAEA,SAAO;AACT;","names":[]}
@@ -1,10 +1,10 @@
1
- import {
2
- buildThemePromptContext
3
- } from "./chunk-VV5I53WR.js";
4
1
  import {
5
2
  dataUrlToImagePart,
6
3
  streamGenerate
7
4
  } from "./chunk-YZHRLDQF.js";
5
+ import {
6
+ buildThemePromptContext
7
+ } from "./chunk-VV5I53WR.js";
8
8
 
9
9
  // src/generate.ts
10
10
  var 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.
@@ -196,4 +196,4 @@ export {
196
196
  PROMPT_SUFFIX,
197
197
  generateLanding
198
198
  };
199
- //# sourceMappingURL=chunk-ZARS4VIK.js.map
199
+ //# sourceMappingURL=chunk-CQXBVGVC.js.map
@@ -0,0 +1,67 @@
1
+ import {
2
+ buildCustomTheme,
3
+ buildSingleThemeCss
4
+ } from "./chunk-VV5I53WR.js";
5
+
6
+ // src/buildHtmlV4.ts
7
+ function buildDeployHtmlV4(sections, opts) {
8
+ const cssSection = sections.find((s) => s.id === "__grapes_css__");
9
+ const contentSections = sections.filter((s) => s.id !== "__grapes_css__").sort((a, b) => a.order - b.order);
10
+ let grapesCSS = "";
11
+ if (cssSection) {
12
+ const match = cssSection.html.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
13
+ grapesCSS = match?.[1] || "";
14
+ }
15
+ let themeCss = "";
16
+ if (opts?.customColors && Object.keys(opts.customColors).length) {
17
+ let colors = opts.customColors;
18
+ if (!colors["on-primary"] && colors.primary) {
19
+ const full = buildCustomTheme(colors);
20
+ colors = full.colors;
21
+ }
22
+ const vars = Object.entries(colors).map(([k, v]) => ` --color-${k}: ${v};`).join("\n");
23
+ themeCss = `:root {
24
+ ${vars}
25
+ }`;
26
+ } else if (opts?.themeName && opts.themeName !== "custom") {
27
+ try {
28
+ themeCss = buildSingleThemeCss(opts.themeName).css || "";
29
+ } catch {
30
+ }
31
+ }
32
+ const body = contentSections.map((s) => {
33
+ return s.html.replace(/\s+contenteditable="[^"]*"/gi, "").replace(/\s+data-section-id="[^"]*"/gi, "").replace(/\s+data-label="[^"]*"/gi, "").replace(/\s+data-gjs[^=]*="[^"]*"/gi, "");
34
+ }).join("\n");
35
+ const branding = opts?.showBranding !== false ? `<div style="text-align:center;padding:12px;font-size:11px;opacity:.5;font-family:system-ui">
36
+ Built with <a href="https://easybits.cloud" target="_blank" style="color:inherit;text-decoration:underline">EasyBits</a>
37
+ </div>` : "";
38
+ return `<!DOCTYPE html>
39
+ <html lang="es">
40
+ <head>
41
+ <meta charset="UTF-8"/>
42
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
43
+ <title>${opts?.title || "Landing Page"}</title>
44
+ <script src="https://cdn.tailwindcss.com"></script>
45
+ <script>
46
+ tailwind.config={theme:{extend:{colors:{primary:'var(--color-primary)','primary-light':'var(--color-primary-light)','primary-dark':'var(--color-primary-dark)',secondary:'var(--color-secondary)',accent:'var(--color-accent)',surface:'var(--color-surface)','surface-alt':'var(--color-surface-alt)','on-primary':'var(--color-on-primary)','on-secondary':'var(--color-on-secondary)','on-accent':'var(--color-on-accent)','on-surface':'var(--color-on-surface)','on-surface-muted':'var(--color-on-surface-muted)'}}}}
47
+ </script>
48
+ <style>
49
+ ${themeCss}
50
+ *{margin:0;padding:0;box-sizing:border-box}
51
+ html{scroll-behavior:smooth}
52
+ body{font-family:system-ui,-apple-system,sans-serif}
53
+ img{max-width:100%;height:auto}
54
+ ${grapesCSS}
55
+ </style>
56
+ </head>
57
+ <body>
58
+ ${body}
59
+ ${branding}
60
+ </body>
61
+ </html>`;
62
+ }
63
+
64
+ export {
65
+ buildDeployHtmlV4
66
+ };
67
+ //# sourceMappingURL=chunk-KXOAEC33.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/buildHtmlV4.ts"],"sourcesContent":["import type { Section3 } from \"./types\";\nimport { buildSingleThemeCss, buildCustomTheme } from \"./themes\";\n\n/**\n * Build deploy HTML for Landings v4 (GrapesJS).\n * Includes Tailwind CDN + theme CSS variables + any GrapesJS-generated CSS.\n */\nexport function buildDeployHtmlV4(\n sections: Section3[],\n opts?: {\n showBranding?: boolean;\n title?: string;\n themeName?: string;\n customColors?: Record<string, string>;\n }\n): string {\n const cssSection = sections.find((s) => s.id === \"__grapes_css__\");\n const contentSections = sections\n .filter((s) => s.id !== \"__grapes_css__\")\n .sort((a, b) => a.order - b.order);\n\n // Extract raw CSS from the <style> wrapper\n let grapesCSS = \"\";\n if (cssSection) {\n const match = cssSection.html.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/i);\n grapesCSS = match?.[1] || \"\";\n }\n\n // Build theme CSS variables\n let themeCss = \"\";\n if (opts?.customColors && Object.keys(opts.customColors).length) {\n let colors = opts.customColors;\n // Brand kit (only 4 colors) → derive full theme with on-* colors\n if (!colors[\"on-primary\"] && colors.primary) {\n const full = buildCustomTheme(colors as any);\n colors = full.colors;\n }\n const vars = Object.entries(colors)\n .map(([k, v]) => ` --color-${k}: ${v};`)\n .join(\"\\n\");\n themeCss = `:root {\\n${vars}\\n}`;\n } else if (opts?.themeName && opts.themeName !== \"custom\") {\n try {\n themeCss = buildSingleThemeCss(opts.themeName).css || \"\";\n } catch { /* fallback: no theme */ }\n }\n\n const body = contentSections\n .map((s) => {\n return s.html\n .replace(/\\s+contenteditable=\"[^\"]*\"/gi, \"\")\n .replace(/\\s+data-section-id=\"[^\"]*\"/gi, \"\")\n .replace(/\\s+data-label=\"[^\"]*\"/gi, \"\")\n .replace(/\\s+data-gjs[^=]*=\"[^\"]*\"/gi, \"\");\n })\n .join(\"\\n\");\n\n const branding = opts?.showBranding !== false\n ? `<div style=\"text-align:center;padding:12px;font-size:11px;opacity:.5;font-family:system-ui\">\n Built with <a href=\"https://easybits.cloud\" target=\"_blank\" style=\"color:inherit;text-decoration:underline\">EasyBits</a>\n </div>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n<title>${opts?.title || \"Landing Page\"}</title>\n<script src=\"https://cdn.tailwindcss.com\"><\\/script>\n<script>\ntailwind.config={theme:{extend:{colors:{primary:'var(--color-primary)','primary-light':'var(--color-primary-light)','primary-dark':'var(--color-primary-dark)',secondary:'var(--color-secondary)',accent:'var(--color-accent)',surface:'var(--color-surface)','surface-alt':'var(--color-surface-alt)','on-primary':'var(--color-on-primary)','on-secondary':'var(--color-on-secondary)','on-accent':'var(--color-on-accent)','on-surface':'var(--color-on-surface)','on-surface-muted':'var(--color-on-surface-muted)'}}}}\n<\\/script>\n<style>\n${themeCss}\n*{margin:0;padding:0;box-sizing:border-box}\nhtml{scroll-behavior:smooth}\nbody{font-family:system-ui,-apple-system,sans-serif}\nimg{max-width:100%;height:auto}\n${grapesCSS}\n</style>\n</head>\n<body>\n${body}\n${branding}\n</body>\n</html>`;\n}\n"],"mappings":";;;;;;AAOO,SAAS,kBACd,UACA,MAMQ;AACR,QAAM,aAAa,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,gBAAgB;AACjE,QAAM,kBAAkB,SACrB,OAAO,CAAC,MAAM,EAAE,OAAO,gBAAgB,EACvC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAGnC,MAAI,YAAY;AAChB,MAAI,YAAY;AACd,UAAM,QAAQ,WAAW,KAAK,MAAM,kCAAkC;AACtE,gBAAY,QAAQ,CAAC,KAAK;AAAA,EAC5B;AAGA,MAAI,WAAW;AACf,MAAI,MAAM,gBAAgB,OAAO,KAAK,KAAK,YAAY,EAAE,QAAQ;AAC/D,QAAI,SAAS,KAAK;AAElB,QAAI,CAAC,OAAO,YAAY,KAAK,OAAO,SAAS;AAC3C,YAAM,OAAO,iBAAiB,MAAa;AAC3C,eAAS,KAAK;AAAA,IAChB;AACA,UAAM,OAAO,OAAO,QAAQ,MAAM,EAC/B,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,EACvC,KAAK,IAAI;AACZ,eAAW;AAAA,EAAY,IAAI;AAAA;AAAA,EAC7B,WAAW,MAAM,aAAa,KAAK,cAAc,UAAU;AACzD,QAAI;AACF,iBAAW,oBAAoB,KAAK,SAAS,EAAE,OAAO;AAAA,IACxD,QAAQ;AAAA,IAA2B;AAAA,EACrC;AAEA,QAAM,OAAO,gBACV,IAAI,CAAC,MAAM;AACV,WAAO,EAAE,KACN,QAAQ,gCAAgC,EAAE,EAC1C,QAAQ,gCAAgC,EAAE,EAC1C,QAAQ,2BAA2B,EAAE,EACrC,QAAQ,8BAA8B,EAAE;AAAA,EAC7C,CAAC,EACA,KAAK,IAAI;AAEZ,QAAM,WAAW,MAAM,iBAAiB,QACpC;AAAA;AAAA,gBAGA;AAEJ,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,SAKA,MAAM,SAAS,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpC,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKR,SAAS;AAAA;AAAA;AAAA;AAAA,EAIT,IAAI;AAAA,EACJ,QAAQ;AAAA;AAAA;AAGV;","names":[]}
@@ -0,0 +1,117 @@
1
+ // src/hooks/useThumbnailCapture.ts
2
+ import { useState, useRef, useCallback, useEffect } from "react";
3
+ function buildCaptureHtml(sectionHtml, themeCssData) {
4
+ return `<!DOCTYPE html><html><head>
5
+ <meta charset="UTF-8">
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ ${themeCssData ? `<script>tailwind.config = ${themeCssData.tailwindConfig}</script>` : ""}
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
9
+ <style>
10
+ * { box-sizing: border-box; margin: 0; padding: 0; }
11
+ body { font-family: 'Inter', sans-serif; width: 8.5in; height: 11in; overflow: hidden; }
12
+ ${themeCssData?.css || ""}
13
+ </style>
14
+ </head><body>${sectionHtml}</body></html>`;
15
+ }
16
+ var THUMB_W = 200;
17
+ var THUMB_H = Math.round(THUMB_W * (11 / 8.5));
18
+ function useThumbnailCapture(sections, themeCssData) {
19
+ const [thumbs, setThumbs] = useState({});
20
+ const iframeRef = useRef(null);
21
+ const queueRef = useRef([]);
22
+ const busyRef = useRef(false);
23
+ const lastHtmlRef = useRef({});
24
+ const debounceRef = useRef(void 0);
25
+ const processQueue = useCallback(() => {
26
+ if (busyRef.current || queueRef.current.length === 0) return;
27
+ busyRef.current = true;
28
+ const item = queueRef.current.shift();
29
+ if (!iframeRef.current) {
30
+ const iframe2 = document.createElement("iframe");
31
+ iframe2.style.cssText = "position:fixed;top:-9999px;left:-9999px;width:816px;height:1056px;opacity:0;pointer-events:none;border:none;";
32
+ document.body.appendChild(iframe2);
33
+ iframeRef.current = iframe2;
34
+ }
35
+ const iframe = iframeRef.current;
36
+ const html = buildCaptureHtml(item.html, themeCssData);
37
+ const onLoad = () => {
38
+ iframe.removeEventListener("load", onLoad);
39
+ setTimeout(() => {
40
+ try {
41
+ const doc = iframe.contentDocument;
42
+ if (!doc?.body) throw new Error("no doc");
43
+ const canvas = document.createElement("canvas");
44
+ canvas.width = THUMB_W * 2;
45
+ canvas.height = THUMB_H * 2;
46
+ const ctx = canvas.getContext("2d");
47
+ ctx.scale(2, 2);
48
+ const svgData = `<svg xmlns="http://www.w3.org/2000/svg" width="${THUMB_W}" height="${THUMB_H}">
49
+ <foreignObject width="816" height="1056" transform="scale(${THUMB_W / 816})">
50
+ ${new XMLSerializer().serializeToString(doc.documentElement)}
51
+ </foreignObject>
52
+ </svg>`;
53
+ const img = new Image();
54
+ img.onload = () => {
55
+ ctx.drawImage(img, 0, 0, THUMB_W, THUMB_H);
56
+ const dataUrl = canvas.toDataURL("image/png");
57
+ setThumbs((prev) => ({ ...prev, [item.id]: dataUrl }));
58
+ busyRef.current = false;
59
+ processQueue();
60
+ };
61
+ img.onerror = () => {
62
+ busyRef.current = false;
63
+ processQueue();
64
+ };
65
+ img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgData);
66
+ } catch {
67
+ busyRef.current = false;
68
+ processQueue();
69
+ }
70
+ }, 800);
71
+ };
72
+ iframe.addEventListener("load", onLoad);
73
+ iframe.srcdoc = html;
74
+ }, [themeCssData]);
75
+ useEffect(() => {
76
+ clearTimeout(debounceRef.current);
77
+ debounceRef.current = setTimeout(() => {
78
+ const content = sections.filter((s) => s.id !== "__grapes_css__" && s.label !== "__css__");
79
+ let changed = false;
80
+ for (const s of content) {
81
+ if (lastHtmlRef.current[s.id] !== s.html) {
82
+ lastHtmlRef.current[s.id] = s.html;
83
+ queueRef.current = queueRef.current.filter((q) => q.id !== s.id);
84
+ queueRef.current.push({ id: s.id, html: s.html });
85
+ changed = true;
86
+ }
87
+ }
88
+ const ids = new Set(content.map((s) => s.id));
89
+ for (const id of Object.keys(lastHtmlRef.current)) {
90
+ if (!ids.has(id)) delete lastHtmlRef.current[id];
91
+ }
92
+ if (changed) processQueue();
93
+ }, 500);
94
+ return () => clearTimeout(debounceRef.current);
95
+ }, [sections, processQueue]);
96
+ const prevThemeRef = useRef(themeCssData);
97
+ useEffect(() => {
98
+ if (prevThemeRef.current === themeCssData) return;
99
+ prevThemeRef.current = themeCssData;
100
+ lastHtmlRef.current = {};
101
+ setThumbs({});
102
+ }, [themeCssData]);
103
+ useEffect(() => {
104
+ return () => {
105
+ if (iframeRef.current) {
106
+ iframeRef.current.remove();
107
+ iframeRef.current = null;
108
+ }
109
+ };
110
+ }, []);
111
+ return thumbs;
112
+ }
113
+
114
+ export {
115
+ useThumbnailCapture
116
+ };
117
+ //# sourceMappingURL=chunk-LI4UDDMT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useThumbnailCapture.ts"],"sourcesContent":["import { useState, useRef, useCallback, useEffect } from \"react\";\nimport type { Section3 } from \"../types\";\n\n/** Build HTML for the off-screen capture iframe */\nfunction buildCaptureHtml(sectionHtml: string, themeCssData?: { css: string; tailwindConfig: string }): string {\n return `<!DOCTYPE html><html><head>\n<meta charset=\"UTF-8\">\n<script src=\"https://cdn.tailwindcss.com\"><\\/script>\n${themeCssData ? `<script>tailwind.config = ${themeCssData.tailwindConfig}<\\/script>` : \"\"}\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap\" rel=\"stylesheet\">\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody { font-family: 'Inter', sans-serif; width: 8.5in; height: 11in; overflow: hidden; }\n${themeCssData?.css || \"\"}\n</style>\n</head><body>${sectionHtml}</body></html>`;\n}\n\nconst THUMB_W = 200;\nconst THUMB_H = Math.round(THUMB_W * (11 / 8.5));\n\n/**\n * Captures static thumbnail images from sections using a single off-screen iframe.\n * Processes one section at a time via a queue to avoid N simultaneous Tailwind CDN loads.\n */\nexport function useThumbnailCapture(\n sections: Section3[],\n themeCssData?: { css: string; tailwindConfig: string }\n) {\n const [thumbs, setThumbs] = useState<Record<string, string>>({});\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const queueRef = useRef<{ id: string; html: string }[]>([]);\n const busyRef = useRef(false);\n const lastHtmlRef = useRef<Record<string, string>>({});\n const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const processQueue = useCallback(() => {\n if (busyRef.current || queueRef.current.length === 0) return;\n busyRef.current = true;\n\n const item = queueRef.current.shift()!;\n\n // Create iframe on demand\n if (!iframeRef.current) {\n const iframe = document.createElement(\"iframe\");\n iframe.style.cssText = \"position:fixed;top:-9999px;left:-9999px;width:816px;height:1056px;opacity:0;pointer-events:none;border:none;\";\n document.body.appendChild(iframe);\n iframeRef.current = iframe;\n }\n\n const iframe = iframeRef.current;\n const html = buildCaptureHtml(item.html, themeCssData);\n\n const onLoad = () => {\n iframe.removeEventListener(\"load\", onLoad);\n // Wait for Tailwind CDN to process + images to load\n setTimeout(() => {\n try {\n const doc = iframe.contentDocument;\n if (!doc?.body) throw new Error(\"no doc\");\n const canvas = document.createElement(\"canvas\");\n canvas.width = THUMB_W * 2; // 2x for retina\n canvas.height = THUMB_H * 2;\n const ctx = canvas.getContext(\"2d\")!;\n ctx.scale(2, 2);\n\n // Use svg foreignObject to render HTML to canvas\n const svgData = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${THUMB_W}\" height=\"${THUMB_H}\">\n <foreignObject width=\"816\" height=\"1056\" transform=\"scale(${THUMB_W / 816})\">\n ${new XMLSerializer().serializeToString(doc.documentElement)}\n </foreignObject>\n </svg>`;\n const img = new Image();\n img.onload = () => {\n ctx.drawImage(img, 0, 0, THUMB_W, THUMB_H);\n const dataUrl = canvas.toDataURL(\"image/png\");\n setThumbs((prev) => ({ ...prev, [item.id]: dataUrl }));\n busyRef.current = false;\n processQueue();\n };\n img.onerror = () => {\n busyRef.current = false;\n processQueue();\n };\n img.src = \"data:image/svg+xml;charset=utf-8,\" + encodeURIComponent(svgData);\n } catch {\n busyRef.current = false;\n processQueue();\n }\n }, 800);\n };\n\n iframe.addEventListener(\"load\", onLoad);\n iframe.srcdoc = html;\n }, [themeCssData]);\n\n // Queue sections that changed\n useEffect(() => {\n clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n const content = sections.filter((s) => s.id !== \"__grapes_css__\" && s.label !== \"__css__\");\n let changed = false;\n for (const s of content) {\n if (lastHtmlRef.current[s.id] !== s.html) {\n lastHtmlRef.current[s.id] = s.html;\n queueRef.current = queueRef.current.filter((q) => q.id !== s.id);\n queueRef.current.push({ id: s.id, html: s.html });\n changed = true;\n }\n }\n // Clean up removed sections\n const ids = new Set(content.map((s) => s.id));\n for (const id of Object.keys(lastHtmlRef.current)) {\n if (!ids.has(id)) delete lastHtmlRef.current[id];\n }\n if (changed) processQueue();\n }, 500);\n\n return () => clearTimeout(debounceRef.current);\n }, [sections, processQueue]);\n\n // Re-capture all when theme changes\n const prevThemeRef = useRef(themeCssData);\n useEffect(() => {\n if (prevThemeRef.current === themeCssData) return;\n prevThemeRef.current = themeCssData;\n lastHtmlRef.current = {};\n setThumbs({});\n }, [themeCssData]);\n\n // Cleanup iframe on unmount\n useEffect(() => {\n return () => {\n if (iframeRef.current) {\n iframeRef.current.remove();\n iframeRef.current = null;\n }\n };\n }, []);\n\n return thumbs;\n}\n"],"mappings":"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","names":["iframe"]}
@@ -1,12 +1,12 @@
1
- import {
2
- buildThemePromptContext
3
- } from "./chunk-VV5I53WR.js";
4
1
  import {
5
2
  currentDateLine,
6
3
  enrichImages,
7
4
  resolveModel,
8
5
  sanitizeSemanticColors
9
6
  } from "./chunk-YZHRLDQF.js";
7
+ import {
8
+ buildThemePromptContext
9
+ } from "./chunk-VV5I53WR.js";
10
10
 
11
11
  // src/refine.ts
12
12
  import { streamText } from "ai";
@@ -161,4 +161,4 @@ export {
161
161
  extractSectionDescription,
162
162
  refineLanding
163
163
  };
164
- //# sourceMappingURL=chunk-T4F6F3HD.js.map
164
+ //# sourceMappingURL=chunk-RJQKHWIH.js.map
@@ -16,12 +16,31 @@ import {
16
16
  import { generateObject, streamText } from "ai";
17
17
  import { z } from "zod";
18
18
  import { nanoid } from "nanoid";
19
- var DOCUMENT_SYSTEM_PROMPT = `You are a professional document designer who creates stunning letter-sized (8.5" \xD7 11") document pages using HTML + Tailwind CSS.
19
+ var PAGE_FORMAT_CONFIG = {
20
+ letter: {
21
+ container: "w-[8.5in] h-[11in]",
22
+ bodyText: "text-sm or text-base (10-11px)",
23
+ maxColumns: 2,
24
+ heightMode: "fixed at 11in",
25
+ description: 'letter-sized (8.5" \xD7 11") document pages'
26
+ },
27
+ web: {
28
+ container: "w-[1280px] min-h-[800px]",
29
+ bodyText: "text-base or text-lg (16-18px)",
30
+ maxColumns: 1,
31
+ heightMode: "flexible, no max height",
32
+ description: "web-optimized document sections"
33
+ }
34
+ };
35
+ function getDocumentSystemPrompt(format = "letter") {
36
+ const cfg = PAGE_FORMAT_CONFIG[format];
37
+ const isWeb = format === "web";
38
+ return `You are a professional document designer who creates stunning ${cfg.description} using HTML + Tailwind CSS.
20
39
 
21
40
  RULES:
22
- - Each page is a <section> element sized for letter paper
23
- - Page structure: <section class="w-[8.5in] h-[11in] relative overflow-hidden flex flex-col">
24
- - The section is EXACTLY 11in tall \u2014 content MUST fit, never exceed. Use flex flex-col so children can use flex-1.
41
+ - Each page is a <section> element${isWeb ? " optimized for web viewing" : " sized for letter paper"}
42
+ - Page structure: <section class="${cfg.container} relative overflow-hidden flex flex-col">
43
+ - ${isWeb ? "Each section has flexible height \u2014 content determines the height. Use min-h-[800px] but allow natural growth." : "The section is EXACTLY 11in tall \u2014 content MUST fit, never exceed. Use flex flex-col so children can use flex-1."}
25
44
  - The section itself has NO padding \u2014 backgrounds, gradients, and decorative elements go edge-to-edge
26
45
  - Use slot layout: shrink-0 for header/footer bands, flex-1 overflow-hidden for main content area
27
46
  - For text content, use an inner wrapper: <div class="flex-1 overflow-hidden px-[0.75in] py-[0.5in]">...content...</div>
@@ -32,28 +51,28 @@ RULES:
32
51
  - NO JavaScript, only HTML+Tailwind
33
52
  - All text content in Spanish unless the prompt specifies otherwise
34
53
  - Use real content from the source material, not Lorem ipsum
35
- - NOT responsive \u2014 fixed letter size, no breakpoints needed
54
+ - NOT responsive \u2014 fixed ${isWeb ? "width" : "letter size"}, no breakpoints needed
36
55
  - Sections can have ANY background \u2014 full-bleed color, gradients, or white. Not limited to white paper.
37
56
 
38
57
  STRICT PROHIBITIONS:
39
58
  1. **NO EMOJI** \u2014 Never use emoji characters (\u{1F680}\u274C\u2705\u{1F4CA} etc.). Instead use inline SVG icons or colored divs. For bullet decorators use small colored circles (<span class="w-2 h-2 rounded-full bg-primary inline-block"></span>) or simple SVG.
40
59
  2. **NO Chart.js / NO JavaScript** \u2014 Never reference Chart.js, canvas, or any JS library. For data visualization use pure CSS: progress bars (div with percentage width + bg-primary), horizontal bars, styled tables with colored cells. Never use <canvas> or <script>.
41
- 3. **NO buttons or CTAs** \u2014 This is a print document, not a web page. No "Contactar", "Ver m\xE1s", "Comprar" buttons. Use text with contact info instead.
60
+ 3. **NO buttons or CTAs** \u2014 This is a ${isWeb ? "document" : "print document"}, not a ${isWeb ? "landing page" : "web page"}. No "Contactar", "Ver m\xE1s", "Comprar" buttons. Use text with contact info instead.
42
61
  4. **CONTRAST IS MANDATORY** \u2014 Dark/colored backgrounds (bg-primary, bg-primary-dark, bg-secondary, dark gradients) MUST use text-white or text-on-primary. Light backgrounds (white, bg-surface, bg-surface-alt) MUST use text-gray-900 or text-on-surface. NEVER use dark text on dark backgrounds or light text on light backgrounds.
43
62
  5. **Max 2 font weights per page** \u2014 Pick 2 (e.g. font-semibold + font-normal, or font-bold + font-light). Don't mix 4-5 weights.
44
63
  6. **Generous whitespace** \u2014 Don't fill every centimeter. Leave breathing room. Use py-8, py-12, gap-6, gap-8 liberally. Less content per page = more professional.
45
64
 
46
65
  LAYOUT OVERFLOW PREVENTION \u2014 CRITICAL:
47
- - Max 2 columns side by side \u2014 each with w-1/2. NEVER use 3+ columns.
66
+ - Max ${cfg.maxColumns} column${cfg.maxColumns === 1 ? "" : "s"} side by side${cfg.maxColumns === 2 ? " \u2014 each with w-1/2" : ""}. NEVER use ${cfg.maxColumns + 1}+ columns.
48
67
  - Decorative sidebars: max w-16 (4rem). NEVER use w-[2.5in] or wider sidebars \u2014 they steal too much space.
49
68
  - Stats/metric grids: max 3 items per row (grid-cols-3). Use gap-4 or gap-6.
50
69
  - Tables: max 4 columns, use text-xs or text-sm for cell text, px-3 py-2 cell padding.
51
70
  - Images: always w-full or max-w-[50%] \u2014 never fixed pixel widths.
52
- - Text: never use text-6xl or larger except for cover page title. Body text: text-sm or text-base.
71
+ - Text: never use text-6xl or larger except for cover page title. Body text: ${cfg.bodyText}.
53
72
  - NEVER use absolute positioning that could overflow \u2014 prefer flex/grid layouts.
54
73
  - Decorative shapes with absolute positioning MUST stay fully inside the page. Use overflow-hidden on parent AND keep coordinates positive (no negative right/left values).
55
74
  - Large decorative text (text-[200px], text-[10rem] etc.) MUST have opacity-5 or lower AND overflow-hidden on its container. These giant texts frequently overflow \u2014 be extra careful.
56
- - NEVER place elements beyond the right edge \u2014 all content and decorations must fit within 8.5in width.
75
+ - NEVER place elements beyond the right edge \u2014 all content and decorations must fit within ${isWeb ? "1280px" : "8.5in"} width.
57
76
 
58
77
  DESIGN \u2014 ADAPT to the document type. Read the prompt carefully and match the visual style:
59
78
 
@@ -125,7 +144,7 @@ TAILWIND v3 NOTES:
125
144
  - Borders: border + border-gray-200 for visible borders
126
145
 
127
146
  EXAMPLE \u2014 Cover page (simple, no wide sidebars):
128
- <section class="w-[8.5in] h-[11in] relative overflow-hidden flex flex-col bg-white">
147
+ <section class="${cfg.container} relative overflow-hidden flex flex-col bg-white">
129
148
  <div class="absolute left-0 top-0 w-2 h-full bg-primary"></div>
130
149
  <div class="flex-1 overflow-hidden flex flex-col justify-center px-[1in]">
131
150
  <div class="text-sm font-normal text-primary mb-4">Marzo 2026 \xB7 Versi\xF3n 1.0</div>
@@ -136,9 +155,9 @@ EXAMPLE \u2014 Cover page (simple, no wide sidebars):
136
155
  </section>
137
156
 
138
157
  EXAMPLE \u2014 Marketing/brochure page (bold, visual):
139
- <section class="w-[8.5in] h-[11in] relative overflow-hidden flex flex-col bg-primary">
158
+ <section class="${cfg.container} relative overflow-hidden flex flex-col bg-primary">
140
159
  <div class="flex flex-1 overflow-hidden">
141
- <div class="w-1/2 flex flex-col justify-center px-[0.75in]">
160
+ <div class="${isWeb ? "flex-1" : "w-1/2"} flex flex-col justify-center px-[0.75in]">
142
161
  <span class="text-sm font-normal text-on-primary opacity-70 uppercase tracking-widest mb-3">Soluci\xF3n Premium</span>
143
162
  <h2 class="text-4xl font-bold text-on-primary leading-tight mb-6">Transforma tu negocio digital</h2>
144
163
  <p class="text-base font-normal text-on-primary opacity-80 mb-8">Herramientas inteligentes que simplifican la gesti\xF3n de tus activos digitales.</p>
@@ -147,14 +166,14 @@ EXAMPLE \u2014 Marketing/brochure page (bold, visual):
147
166
  <div><div class="text-3xl font-bold text-accent">2.4K</div><div class="text-xs text-on-primary opacity-70">Empresas</div></div>
148
167
  </div>
149
168
  </div>
150
- <div class="w-1/2 relative">
169
+ <div class="${isWeb ? "flex-1" : "w-1/2"} relative">
151
170
  <img data-image-query="modern office team collaboration technology" alt="Team working" class="absolute inset-0 w-full h-full object-cover" />
152
171
  </div>
153
172
  </div>
154
173
  </section>
155
174
 
156
175
  EXAMPLE \u2014 Catalog/product grid page:
157
- <section class="w-[8.5in] h-[11in] relative overflow-hidden flex flex-col bg-surface">
176
+ <section class="${cfg.container} relative overflow-hidden flex flex-col bg-surface">
158
177
  <div class="shrink-0 h-1 bg-primary w-full"></div>
159
178
  <div class="flex-1 overflow-hidden px-[0.75in] py-[0.5in]">
160
179
  <div class="flex justify-between items-baseline mb-6">
@@ -180,7 +199,7 @@ EXAMPLE \u2014 Catalog/product grid page:
180
199
  </section>
181
200
 
182
201
  EXAMPLE \u2014 Content page with table + progress bars:
183
- <section class="w-[8.5in] h-[11in] relative overflow-hidden flex flex-col bg-white">
202
+ <section class="${cfg.container} relative overflow-hidden flex flex-col bg-white">
184
203
  <div class="shrink-0 h-1.5 bg-primary w-full"></div>
185
204
  <div class="flex-1 overflow-hidden px-[0.75in] py-[0.5in]">
186
205
  <h2 class="text-2xl font-bold text-gray-900 mb-1">M\xE9tricas de Rendimiento</h2>
@@ -190,7 +209,7 @@ EXAMPLE \u2014 Content page with table + progress bars:
190
209
  <tbody>
191
210
  <tr class="bg-surface-alt"><td class="px-4 py-3 text-gray-900">Ingresos</td><td class="px-4 py-3 text-gray-900">$1.2M</td><td class="px-4 py-3 text-gray-900">$1.5M</td></tr>
192
211
  <tr><td class="px-4 py-3 text-gray-900">Clientes nuevos</td><td class="px-4 py-3 text-gray-900">340</td><td class="px-4 py-3 text-gray-900">300</td></tr>
193
- <tr class="bg-surface-alt"><td class="px-4 py-3 text-gray-900">Retenci\xF3n</td><td class="px-4 py-3 text-gray-900">92%</td><td class="px-4 py-3 text-gray-900">90%</td></tr>
212
+ <tr class="bg-surface-alt"><td class="px-4 py-3 text-gray-900">Retenci\xF3n</td><td class="px-4 py-3 text-gray-900">92%</td><td class="px-4 py-3 text-gray-900">${"90%"}</td></tr>
194
213
  </tbody>
195
214
  </table>
196
215
  <h3 class="text-lg font-bold text-gray-900 mb-4">Progreso por \xC1rea</h3>
@@ -201,15 +220,22 @@ EXAMPLE \u2014 Content page with table + progress bars:
201
220
  </div>
202
221
  </div>
203
222
  </section>`;
204
- var DOCUMENT_PROMPT_SUFFIX = `
223
+ }
224
+ var DOCUMENT_SYSTEM_PROMPT = getDocumentSystemPrompt("letter");
225
+ function getDocumentPromptSuffix(format = "letter") {
226
+ const cfg = PAGE_FORMAT_CONFIG[format];
227
+ const isWeb = format === "web";
228
+ return `
205
229
 
206
230
  OUTPUT FORMAT: NDJSON \u2014 one JSON object per line, NO wrapper array, NO markdown fences.
207
- Each line: {"label": "Page Title", "html": "<section class='w-[8.5in] h-[11in] relative overflow-hidden flex flex-col'>...</section>"}
231
+ Each line: {"label": "Page Title", "html": "<section class='${cfg.container} relative overflow-hidden flex flex-col'>...</section>"}
208
232
 
209
233
  Generate 3-8 pages depending on content length. First page = cover/title page.
210
- Each page must fit within letter size (8.5" \xD7 11"). Be conservative with spacing.
234
+ ${isWeb ? "Each section should have comfortable spacing for web reading." : 'Each page must fit within letter size (8.5" \xD7 11"). Be conservative with spacing.'}
211
235
  Make each page visually distinct \u2014 different layouts, different accent placements.
212
236
  IMPORTANT: Adapt your design style to match the type of document \u2014 not everything is a report. Brochures should feel bold and visual, catalogs should showcase products, invitations should feel elegant, etc.`;
237
+ }
238
+ var DOCUMENT_PROMPT_SUFFIX = getDocumentPromptSuffix("letter");
213
239
  async function generateDocument(options) {
214
240
  const {
215
241
  prompt,
@@ -325,6 +351,7 @@ async function generateDocumentParallel(options) {
325
351
  persistImage,
326
352
  pageCount,
327
353
  skipCover,
354
+ pageFormat = "letter",
328
355
  onOutline,
329
356
  onPageChunk,
330
357
  onPageComplete,
@@ -426,12 +453,12 @@ Small logo header: <img src="${logoUrl}" alt="Logo" class="h-8 object-contain" /
426
453
  ${directionInstruction}
427
454
 
428
455
  OUTPUT: A single JSON object on ONE line, no markdown fences:
429
- {"label": "${page.label}", "html": "<section class='w-[8.5in] h-[11in] relative overflow-hidden flex flex-col'>...</section>"}`
456
+ {"label": "${page.label}", "html": "<section class='${PAGE_FORMAT_CONFIG[pageFormat].container} relative overflow-hidden flex flex-col'>...</section>"}`
430
457
  });
431
458
  try {
432
459
  const result = streamText({
433
460
  model: pageModel,
434
- system: DOCUMENT_SYSTEM_PROMPT + currentDateLine(),
461
+ system: getDocumentSystemPrompt(pageFormat || "letter") + currentDateLine(),
435
462
  messages: [{ role: "user", content: userContent }]
436
463
  });
437
464
  let buffer = "";
@@ -521,9 +548,12 @@ OUTPUT: A single JSON object on ONE line, no markdown fences:
521
548
  }
522
549
 
523
550
  export {
551
+ PAGE_FORMAT_CONFIG,
552
+ getDocumentSystemPrompt,
524
553
  DOCUMENT_SYSTEM_PROMPT,
554
+ getDocumentPromptSuffix,
525
555
  DOCUMENT_PROMPT_SUFFIX,
526
556
  generateDocument,
527
557
  generateDocumentParallel
528
558
  };
529
- //# sourceMappingURL=chunk-6LK7JW6Q.js.map
559
+ //# sourceMappingURL=chunk-X5MEN76P.js.map