best-email-builder 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -0
- package/README.md +214 -19
- package/dist/builder.js +4 -4
- package/dist/{chunk-ZSUHOCCE.js → chunk-AT73Y7JA.js} +159 -83
- package/dist/chunk-AT73Y7JA.js.map +1 -0
- package/dist/chunk-HHLOE6OZ.js +1682 -0
- package/dist/chunk-HHLOE6OZ.js.map +1 -0
- package/dist/chunk-JNHFZXDL.js +1776 -0
- package/dist/chunk-JNHFZXDL.js.map +1 -0
- package/dist/chunk-MY6WPDT4.js +6110 -0
- package/dist/chunk-MY6WPDT4.js.map +1 -0
- package/dist/{chunk-JOC752I3.js → chunk-NAEGU6PR.js} +13 -4
- package/dist/{chunk-JOC752I3.js.map → chunk-NAEGU6PR.js.map} +1 -1
- package/dist/{chunk-BVOVAEEJ.js → chunk-TGDZ4NFI.js} +44 -3
- package/dist/chunk-TGDZ4NFI.js.map +1 -0
- package/dist/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/dist/library.js +3 -3
- package/dist/server.js +544 -204
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/blocks/shared.d.ts +20 -0
- package/dist/types/core/EmailEditor.d.ts +72 -19
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/core/mjmlDefaults.d.ts +13 -0
- package/dist/types/core/scrollbars.d.ts +1 -1
- package/dist/types/defaultEditor.d.ts +4 -1
- package/dist/types/engine/NativeAdapter.d.ts +28 -0
- package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
- package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
- package/dist/types/engine/canvas/icons.d.ts +14 -0
- package/dist/types/engine/canvas/morph.d.ts +21 -0
- package/dist/types/engine/canvasMjml.d.ts +30 -0
- package/dist/types/engine/mjml.d.ts +31 -0
- package/dist/types/engine/model.d.ts +130 -0
- package/dist/types/engine/placement.d.ts +69 -0
- package/dist/types/engine/richText.d.ts +15 -0
- package/dist/types/engine/unwrapModule.d.ts +29 -0
- package/dist/types/features/brand/BrandButton.d.ts +4 -0
- package/dist/types/features/brand/brand.d.ts +108 -0
- package/dist/types/features/brand/extract.d.ts +61 -0
- package/dist/types/features/brand/index.d.ts +2 -0
- package/dist/types/features/brand/messages.d.ts +13 -0
- package/dist/types/features/brand/useBrand.d.ts +22 -0
- package/dist/types/features/generate/GenerateButton.d.ts +3 -0
- package/dist/types/features/generate/RewriteText.d.ts +5 -0
- package/dist/types/features/generate/SubjectIdeas.d.ts +16 -0
- package/dist/types/features/generate/chip.d.ts +1 -0
- package/dist/types/features/generate/claude.d.ts +21 -0
- package/dist/types/features/generate/draft.d.ts +122 -0
- package/dist/types/features/generate/generator.d.ts +118 -0
- package/dist/types/features/generate/index.d.ts +2 -0
- package/dist/types/features/generate/prompt.d.ts +76 -0
- package/dist/types/features/generate/purposes.d.ts +48 -0
- package/dist/types/features/generate/rewrite.d.ts +83 -0
- package/dist/types/features/generate/subjects.d.ts +83 -0
- package/dist/types/features/preview/PreviewButton.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +48 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/server/brand.d.ts +32 -0
- package/dist/types/server/brandImport.d.ts +93 -0
- package/dist/types/server/compileMjml.d.ts +2 -0
- package/dist/types/server/handler.d.ts +25 -0
- package/dist/types/server/index.d.ts +11 -0
- package/dist/types/storage/client.d.ts +75 -3
- package/dist/types/ui/fields.d.ts +2 -1
- package/dist/types/ui/primitives/button.d.ts +1 -1
- package/dist/types/ui/primitives/color-picker.d.ts +2 -1
- package/dist/types/ui/useLayoutWidth.d.ts +1 -1
- package/package.json +13 -7
- package/dist/chunk-3M36VVXS.js +0 -665
- package/dist/chunk-3M36VVXS.js.map +0 -1
- package/dist/chunk-57CTQNTK.js +0 -56
- package/dist/chunk-57CTQNTK.js.map +0 -1
- package/dist/chunk-BVOVAEEJ.js.map +0 -1
- package/dist/chunk-ILRAQTHS.js +0 -3677
- package/dist/chunk-ILRAQTHS.js.map +0 -1
- package/dist/chunk-ZSUHOCCE.js.map +0 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
- package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validation/urls.js","../src/validation/mjmlValidator.js","../src/core/mjmlAttributes.js","../src/features/generate/draft.js","../src/features/generate/prompt.js","../src/features/generate/purposes.js","../src/features/generate/rewrite.js","../src/features/generate/subjects.js","../src/features/generate/generator.js","../src/features/brand/extract.js","../src/features/brand/messages.js"],"sourcesContent":["// @ts-check\n/**\n * URLs accepted in an email template.\n *\n * link (`href`) https, http, mailto:, tel:, `#…` anchor,\n * or a `{{ … }}` merge tag (unsubscribe,\n * variable) replaced at render time;\n * image (`src`, `background-url`…) https only: an http image is blocked or\n * flagged by most email clients, and\n * `data:` bloats the message until Gmail\n * clips it. Or an UPLOADED image (asset\n * path, see `isAssetPath`), made absolute\n * at render time (renderer/links.js).\n *\n * Everything else (`javascript:`, `data:`, `vbscript:`, `file:`…) is rejected.\n * The scheme is read AFTER decoding entities and stripping whitespace and\n * control characters: `java	script:` and `javascript:` are classics.\n */\nimport { isAssetPath } from '../renderer/links.js';\n\n/** Attributes carrying a LINK URL. */\nexport const LINK_ATTRIBUTES = new Set(['href', 'action', 'formaction', 'xlink:href']);\n\n/** Attributes carrying an IMAGE URL (MJML and HTML). */\nexport const IMAGE_ATTRIBUTES = new Set([\n 'src',\n 'srcset',\n 'background-url',\n 'background',\n 'poster',\n 'icon-unwrapped-url',\n 'icon-wrapped-url',\n]);\n\nconst NAMED_ENTITIES = {\n colon: ':',\n tab: '\\t',\n newline: '\\n',\n lpar: '(',\n rpar: ')',\n amp: '&',\n quot: '\"',\n apos: \"'\",\n lt: '<',\n gt: '>',\n nbsp: ' ',\n};\n\n/** Decodes the HTML entities of an attribute value. @param {string} value */\nexport function decodeEntities(value) {\n return String(value ?? '').replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);?/gi, (whole, body) => {\n const key = String(body).toLowerCase();\n if (key.startsWith('#x')) return String.fromCodePoint(Number.parseInt(key.slice(2), 16) || 0);\n if (key.startsWith('#')) return String.fromCodePoint(Number.parseInt(key.slice(1), 10) || 0);\n return NAMED_ENTITIES[key] ?? whole;\n });\n}\n\n/** A `{{ … }}` merge tag (variable, unsubscribe, web version). @param {string} value */\nexport function isMergeTag(value) {\n return /^\\{\\{\\s*[a-zA-Z][\\w.]*\\s*\\}\\}$/.test(String(value || '').trim());\n}\n\n/**\n * @param {string} raw\n * @param {'link'|'image'} kind\n * @returns {boolean}\n */\nexport function isSafeUrl(raw, kind) {\n const value = decodeEntities(raw).trim();\n if (!value) return true;\n if (isMergeTag(value)) return true;\n // Scheme read without whitespace or control characters (\"java\\tscript:\").\n // eslint-disable-next-line no-control-regex -- control characters are exactly what is stripped\n const compact = value.replace(/[\\s\\u0000-\\u001f\\u007f]+/g, '').toLowerCase();\n if (kind === 'image') return compact.startsWith('https://') || isAssetPath(value);\n if (compact.startsWith('#')) return true;\n return /^(https?:\\/\\/|mailto:|tel:)/.test(compact);\n}\n","// @ts-check\n/**\n * What an MJML document may contain before the server compiles it.\n *\n * The MJML comes from the browser (editor export). The server does not take it\n * at face value: it compiles it, stores it and will send it to recipients.\n * Three families of rejection:\n *\n * 1. tags that would take the template out of bounds: `mj-include` (would\n * read a server FILE at compile time), `mj-raw` (free HTML, without any of\n * MJML's guarantees), `mj-style` / `mj-font` (external CSS and fonts,\n * outside the builder's scope);\n * 2. anything that executes or embeds: `<script>`, `<iframe>`, `<object>`,\n * `<form>`, `on*` attributes, CSS `expression()`;\n * 3. dangerous URLs (see `urls.js`).\n *\n * Only tags from two closed lists pass: the MJML components produced by the\n * blocks, and the formatting HTML written by the rich text editor inside an\n * `mj-text` or `mj-button`.\n *\n * Dependency-free module (no parser): it can be tested outside a browser, and\n * runs before compilation, which alone truly parses the MJML.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { IMAGE_ATTRIBUTES, LINK_ATTRIBUTES, decodeEntities, isSafeUrl } from './urls.js';\n\n/** Maximum MJML size (UTF-16 units, approximated by the string length). */\nexport const MJML_MAX_LENGTH = 512 * 1024;\n\n/** Accepted MJML components. */\nexport const MJML_TAGS = new Set([\n 'mjml',\n 'mj-head',\n 'mj-body',\n 'mj-attributes',\n 'mj-all',\n 'mj-class',\n 'mj-preview',\n 'mj-title',\n 'mj-breakpoint',\n 'mj-wrapper',\n 'mj-section',\n 'mj-group',\n 'mj-column',\n 'mj-text',\n 'mj-image',\n 'mj-button',\n 'mj-divider',\n 'mj-spacer',\n 'mj-table',\n 'mj-social',\n 'mj-social-element',\n 'mj-navbar',\n 'mj-navbar-link',\n 'mj-hero',\n]);\n\n/** Formatting HTML accepted inside a text or a button. */\nexport const INLINE_TAGS = new Set([\n 'a',\n 'b',\n 'strong',\n 'i',\n 'em',\n 'u',\n 's',\n 'strike',\n 'del',\n 'br',\n 'span',\n 'p',\n 'div',\n 'font',\n 'sup',\n 'sub',\n 'small',\n 'big',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'blockquote',\n 'code',\n 'pre',\n 'hr',\n 'img',\n 'table',\n 'tbody',\n 'thead',\n 'tr',\n 'td',\n 'th',\n]);\n\n/**\n * Tags rejected by name (explicit message rather than \"tag not allowed\").\n * Values are English message templates, translated when the issue is built.\n */\nconst FORBIDDEN_TAGS = new Map([\n ['mj-include', 'mj-include would read a file from the server'],\n ['mj-raw', 'mj-raw (free HTML) is not allowed'],\n ['mj-style', 'mj-style (free CSS) is outside the builder'],\n ['mj-font', 'mj-font (external font) is outside the builder'],\n ['script', 'script not allowed'],\n ['iframe', 'iframe not allowed'],\n ['frame', 'frame not allowed'],\n ['object', 'object not allowed'],\n ['embed', 'embed not allowed'],\n ['form', 'form not allowed'],\n ['input', 'form field not allowed'],\n ['button', 'HTML button not allowed (use the Button block)'],\n ['textarea', 'form field not allowed'],\n ['select', 'form field not allowed'],\n ['style', 'style sheet not allowed'],\n ['link', 'external resource not allowed'],\n ['meta', 'meta tag not allowed'],\n ['base', 'base tag not allowed'],\n ['svg', 'SVG not allowed'],\n ['math', 'MathML not allowed'],\n]);\n\n/**\n * @typedef {{code: string, message: string, tag?: string, attribute?: string}} ValidationIssue\n */\n\nconst TAG_RE = /<\\s*(\\/?)\\s*([a-zA-Z][\\w:-]*)([^>]*)>/g;\nconst ATTR_RE = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+)))?/g;\n\n/** @param {string} source */\nfunction* attributesOf(source) {\n ATTR_RE.lastIndex = 0;\n let match;\n while ((match = ATTR_RE.exec(source))) {\n const value = match[2] ?? match[3] ?? match[4] ?? '';\n yield { name: match[1].toLowerCase(), value };\n }\n}\n\n/**\n * Inline style check: no `expression()`, no non-https `url()`.\n * @param {string} style\n */\nfunction styleIsSafe(style) {\n const css = decodeEntities(style).toLowerCase().replace(/\\s+/g, '');\n if (css.includes('expression(') || css.includes('javascript:') || css.includes('behavior:')) return false;\n const urls = css.match(/url\\(([^)]*)\\)/g) || [];\n return urls.every((u) => isSafeUrl(u.slice(4, -1).replace(/^['\"]|['\"]$/g, ''), 'image'));\n}\n\n/**\n * Validates an MJML document. Does not throw: returns the list of issues.\n *\n * @param {string} mjml\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n * @returns {{ok: boolean, issues: ValidationIssue[]}}\n */\nexport function validateMjml(mjml, { t = identity } = {}) {\n /** @type {ValidationIssue[]} */\n const issues = [];\n const source = typeof mjml === 'string' ? mjml : '';\n\n if (!source.trim()) {\n return { ok: false, issues: [{ code: 'EMPTY', message: t('Empty MJML') }] };\n }\n if (source.length > MJML_MAX_LENGTH) {\n issues.push({\n code: 'TOO_LARGE',\n message: t('MJML too large ({size} KB, maximum {max} KB)', {\n size: Math.round(source.length / 1024),\n max: MJML_MAX_LENGTH / 1024,\n }),\n });\n }\n\n // Comments are not parsed as tags, but an Outlook conditional comment\n // (`<!--[if mso]>`) has no business in MJML written by the builder.\n const withoutComments = source.replace(/<!--([\\s\\S]*?)-->/g, (whole, body) => {\n if (/\\[if\\s/i.test(body)) {\n issues.push({ code: 'CONDITIONAL_COMMENT', message: t('conditional comment not allowed') });\n }\n return '';\n });\n if (/<!\\[CDATA\\[/i.test(withoutComments)) {\n issues.push({ code: 'CDATA', message: t('CDATA section not allowed') });\n }\n\n const trimmed = withoutComments.trim();\n if (!/^<mjml[\\s>]/i.test(trimmed)) {\n issues.push({ code: 'ROOT', message: t('the document must start with <mjml>') });\n }\n if (!/<mj-body[\\s>]/i.test(trimmed)) {\n issues.push({ code: 'BODY', message: t('missing <mj-body>') });\n }\n\n TAG_RE.lastIndex = 0;\n let match;\n while ((match = TAG_RE.exec(withoutComments))) {\n const closing = match[1] === '/';\n const tag = match[2].toLowerCase();\n if (FORBIDDEN_TAGS.has(tag)) {\n const template = FORBIDDEN_TAGS.get(tag);\n issues.push({ code: 'FORBIDDEN_TAG', message: template ? t(template) : tag, tag });\n continue;\n }\n if (!MJML_TAGS.has(tag) && !INLINE_TAGS.has(tag)) {\n issues.push({ code: 'UNKNOWN_TAG', message: t('tag not allowed: <{tag}>', { tag }), tag });\n continue;\n }\n if (closing) continue;\n\n for (const { name, value } of attributesOf(match[3])) {\n if (name.startsWith('on')) {\n issues.push({\n code: 'EVENT_ATTRIBUTE',\n message: t('attribute {name} not allowed', { name }),\n tag,\n attribute: name,\n });\n } else if (LINK_ATTRIBUTES.has(name) && !isSafeUrl(value, 'link')) {\n issues.push({ code: 'UNSAFE_URL', message: t('URL rejected ({name})', { name }), tag, attribute: name });\n } else if (IMAGE_ATTRIBUTES.has(name) && !isSafeUrl(value, 'image')) {\n issues.push({\n code: 'UNSAFE_IMAGE',\n message: t('image rejected ({name}): https only', { name }),\n tag,\n attribute: name,\n });\n } else if (name === 'style' && !styleIsSafe(value)) {\n issues.push({ code: 'UNSAFE_STYLE', message: t('inline style rejected'), tag, attribute: name });\n }\n }\n }\n\n return { ok: issues.length === 0, issues };\n}\n\n/**\n * Validates and throws a readable 422 error listing the first rejections.\n * @param {string} mjml\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function assertValidMjml(mjml, { t = identity } = {}) {\n const { ok, issues } = validateMjml(mjml, { t });\n if (ok) return;\n const shown = issues.slice(0, 3).map((i) => i.message).join('; ');\n const more = issues.length - 3;\n const error = /** @type {Error & {status?: number, issues?: ValidationIssue[]}} */ (\n new Error(\n more > 0\n ? t('Template rejected: {issues} (+{more})', { issues: shown, more })\n : t('Template rejected: {issues}', { issues: shown })\n )\n );\n error.status = 422;\n error.issues = issues;\n throw error;\n}\n","// @ts-check\n/**\n * MJML attributes — reading and writing properties panel values.\n *\n * This is knowledge of the LANGUAGE (MJML), not of the engine: it lives in\n * the core, and every adapter benefits from it.\n *\n * ── PADDING IS WRITTEN IN LONGHAND ─────────────────────────────────────────\n * An MJML element carries both `padding` and `padding-top/-right/-bottom/\n * -left`, and the longhand wins. The canvas, starting from the defaults\n * (`padding-top=\"10px\"`…), ignored a `padding=\"40px 60px\"` that the sent\n * email did apply: the screen lied. So `padding` is always written as four\n * attributes, and read back the same way.\n *\n * `inner-padding` (button) has no longhand in MJML: it stays as is.\n *\n * ── NO QUOTES IN A VALUE ───────────────────────────────────────────────────\n * The engine export writes attributes unescaped: a `\"` in a value would break\n * the MJML. Double quotes, angle brackets and line breaks are stripped on\n * write.\n */\n\n/** Spacing attributes that have a longhand form in MJML. */\nexport const LONGHAND_SPACING = new Set(['padding']);\n\nexport const SIDES = /** @type {const} */ (['top', 'right', 'bottom', 'left']);\n\n/**\n * \"10px 25px\" → ['10px', '25px', '10px', '25px'] (CSS 1-to-4 value rule).\n * @param {string|null|undefined} value\n * @returns {[string, string, string, string]}\n */\nexport function parseSpacing(value) {\n const parts = String(value || '')\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n const [a = '0px', b = a, c = a, d = b] = parts;\n return [a, b, c, d];\n}\n\n/**\n * Shortest form of a four-sided spacing.\n * @param {string[]} sides\n */\nexport function formatSpacing(sides) {\n const [t, r, b, l] = sides.map((s) => String(s || '0px'));\n if (t === b && r === l) return t === r ? t : `${t} ${r}`;\n if (r === l) return `${t} ${r} ${b}`;\n return `${t} ${r} ${b} ${l}`;\n}\n\n/** Attribute value safe for an unescaped export. @param {unknown} value */\nexport function cleanAttributeValue(value) {\n return String(value ?? '')\n .replace(/[\"<>\\r\\n]/g, '')\n .trim();\n}\n\n/**\n * Entered values → MJML attributes to set (`null` = attribute to remove).\n *\n * @param {Record<string, string|null|undefined>} values\n * @returns {Record<string, string|null>}\n */\nexport function toAttributes(values) {\n /** @type {Record<string, string|null>} */\n const out = {};\n for (const [key, raw] of Object.entries(values || {})) {\n const value = raw === null || raw === undefined ? '' : cleanAttributeValue(raw);\n if (LONGHAND_SPACING.has(key)) {\n const sides = value ? parseSpacing(value) : null;\n SIDES.forEach((side, i) => {\n out[`${key}-${side}`] = sides ? sides[i] : null;\n });\n out[key] = null;\n } else {\n out[key] = value || null;\n }\n }\n return out;\n}\n\n/**\n * Element attributes → a block's property values.\n *\n * @param {Record<string, unknown>} attributes\n * @param {{key: string}[]} properties\n * @returns {Record<string, string>}\n */\nexport function toValues(attributes, properties) {\n /** @type {Record<string, string>} */\n const values = {};\n for (const { key } of properties) {\n if (LONGHAND_SPACING.has(key)) {\n const base = parseSpacing(String(attributes[key] ?? ''));\n const sides = SIDES.map((side, i) => {\n const v = attributes[`${key}-${side}`];\n return v === undefined || v === null || v === '' ? base[i] : String(v);\n });\n const hasAny = attributes[key] || SIDES.some((side) => attributes[`${key}-${side}`]);\n values[key] = hasAny ? formatSpacing(sides) : '';\n } else {\n const v = attributes[key];\n values[key] = v === undefined || v === null ? '' : String(v);\n }\n }\n return values;\n}\n","// @ts-check\n/**\n * A GENERATED EMAIL, made fit for the builder.\n *\n * A model follows the prompt most of the time, not always. What it gets\n * wrong in a way that can be fixed without asking it again is fixed here:\n *\n * the answer JSON found inside code fences or prose; a bare MJML\n * document is accepted too;\n * padding=\"...\" rewritten as the four longhand attributes the panel and\n * the canvas read (see core/mjmlAttributes);\n * structure content outside a column is put in one, a column outside\n * a section too; `mj-hero`, `mj-wrapper` and `mj-group`\n * become plain sections and columns (the canvas draws\n * them wrong or the panel cannot edit them); `mj-head`,\n * classes and CSS are removed;\n * images every source is emptied, except the sender's logo: a\n * stock photo from a third-party service would end up in\n * a real send one day;\n * variables a `{{tag}}` the catalog does not know becomes a\n * `[placeholder]` the user sees and replaces (an https\n * example link inside a URL).\n *\n * What cannot be fixed here (forbidden HTML, a missing unsubscribe link) is\n * listed by `reviewDraft` and sent back to the model.\n *\n * Dependency-free apart from the engine's MJML reader: runs in the browser,\n * on the server and in unit tests.\n */\n\nimport { parseMjml, serializeMjml } from '../../engine/mjml.js';\nimport { createNode } from '../../engine/model.js';\nimport { LONGHAND_SPACING, SIDES, parseSpacing } from '../../core/mjmlAttributes.js';\nimport { PALETTE } from '../../blocks/shared.js';\nimport { validateMjml } from '../../validation/mjmlValidator.js';\n\n/**\n * @typedef {import('../../engine/model.js').EmailNode} EmailNode\n * @typedef {{name: string, subject: string, preheader: string, mjml: string}} EmailDraft\n */\n\n/**\n * Why a generation failed, in English keys the interface translates. Status:\n * the HTTP status the server answers with.\n */\nexport const GENERATION_ERRORS = {\n NOTHING_TO_GENERATE: { status: 400, message: 'Pick what the email is for, or describe it.' },\n REFUSED: { status: 422, message: 'The AI declined to write this email. Try other details.' },\n TRUNCATED: { status: 502, message: 'The email was too long to finish. Ask for a shorter one.' },\n INVALID_ANSWER: { status: 502, message: 'The AI answer could not be read. Try again.' },\n INVALID_OUTPUT: { status: 502, message: 'The generated email could not be used. Try again.' },\n NOTHING_TO_REWRITE: { status: 400, message: 'There is no text to rewrite.' },\n REWRITE_REFUSED: { status: 422, message: 'The AI declined to rewrite this text.' },\n REWRITE_UNUSABLE: { status: 502, message: 'The rewritten text lost a link or a variable, so nothing was changed. Try again.' },\n NOTHING_TO_SUGGEST: { status: 400, message: 'Write the email or a subject first: the ideas are drawn from it.' },\n SUBJECTS_REFUSED: { status: 422, message: 'The AI declined to suggest subjects for this email.' },\n SUBJECTS_UNUSABLE: { status: 502, message: 'No usable subject came back. Try again.' },\n};\n\nexport class EmailGenerationError extends Error {\n /**\n * @param {keyof typeof GENERATION_ERRORS} code\n * @param {string} [detail] what went wrong, for logs and for the model\n */\n constructor(code, detail = '') {\n const known = GENERATION_ERRORS[code] || GENERATION_ERRORS.INVALID_OUTPUT;\n super(known.message);\n this.name = 'EmailGenerationError';\n this.code = code;\n this.status = known.status;\n this.detail = detail || known.message;\n }\n}\n\n/* ── The answer ──────────────────────────────────────────────────────────── */\n\nconst LIMITS = { name: 80, subject: 150, preheader: 200 };\n\n/** One line of plain text: no tags, no line breaks, bounded. @param {unknown} value @param {number} max */\nfunction plainLine(value, max) {\n return String(value ?? '')\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, max);\n}\n\n/**\n * The email in a model's answer.\n * @param {string} text\n * @returns {EmailDraft}\n */\nexport function parseDraft(text) {\n const source = String(text ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n /** @type {any} */\n let data = null;\n const start = unfenced.indexOf('{');\n const end = unfenced.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n data = JSON.parse(unfenced.slice(start, end + 1));\n } catch {\n data = null;\n }\n }\n if (!data && /^<mjml[\\s>]/i.test(unfenced)) data = { mjml: unfenced };\n if (!data || typeof data.mjml !== 'string' || !data.mjml.trim()) {\n throw new EmailGenerationError('INVALID_ANSWER', 'The answer is not the expected JSON object with an \"mjml\" field.');\n }\n return {\n name: plainLine(data.name, LIMITS.name),\n subject: plainLine(data.subject, LIMITS.subject),\n preheader: plainLine(data.preheader, LIMITS.preheader),\n mjml: data.mjml.trim(),\n };\n}\n\n/* ── The MJML ────────────────────────────────────────────────────────────── */\n\nconst CONTENT_TAGS = new Set(['mj-text', 'mj-image', 'mj-button', 'mj-divider', 'mj-spacer', 'mj-social', 'mj-navbar', 'mj-table']);\nconst REMOVED_TAGS = new Set(['mj-head', 'mj-raw', 'mj-style', 'mj-font', 'mj-include', 'mj-attributes']);\nconst REMOVED_ATTRIBUTES = ['css-class', 'mj-class'];\n/** Attributes that take a length: a bare number is read as pixels. */\nconst PIXEL_ATTRIBUTES = new Set(['font-size', 'border-radius', 'icon-size', 'height', 'width', 'letter-spacing']);\n/** Section attributes kept when an `mj-hero` becomes a section. */\nconst HERO_KEPT = ['background-color', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'padding', 'border-radius'];\n\nconst whiteSection = (/** @type {EmailNode[]} */ children) =>\n createNode('mj-section', { attributes: { 'background-color': PALETTE.surface }, children });\nconst column = (/** @type {EmailNode[]} */ children) => createNode('mj-column', { children });\n\n/**\n * Attributes as the builder writes them.\n * @param {EmailNode} node @param {Set<string>} keepImages\n */\nfunction cleanAttributes(node, keepImages) {\n const attributes = { ...node.attributes };\n for (const name of REMOVED_ATTRIBUTES) delete attributes[name];\n for (const key of LONGHAND_SPACING) {\n if (attributes[key] === undefined) continue;\n const sides = parseSpacing(attributes[key]);\n SIDES.forEach((side, i) => {\n const longhand = `${key}-${side}`;\n if (attributes[longhand] === undefined) attributes[longhand] = sides[i];\n });\n delete attributes[key];\n }\n for (const [name, value] of Object.entries(attributes)) {\n const unitless = /^-?\\d+(\\.\\d+)?$/.test(value.trim());\n if (unitless && (PIXEL_ATTRIBUTES.has(name) || /^padding-/.test(name))) attributes[name] = `${value.trim()}px`;\n }\n if (node.tag === 'mj-image' && attributes.src !== undefined && !keepImages.has(attributes.src.trim())) attributes.src = '';\n delete attributes['background-url'];\n return attributes;\n}\n\n/**\n * Groups runs of `isLoose` children into containers made by `wrap`.\n * @param {EmailNode[]} children @param {(node: EmailNode) => boolean} isLoose @param {(nodes: EmailNode[]) => EmailNode} wrap\n */\nfunction wrapLoose(children, isLoose, wrap) {\n /** @type {EmailNode[]} */\n const out = [];\n /** @type {EmailNode[]} */\n let run = [];\n const flush = () => {\n if (run.length) out.push(wrap(run));\n run = [];\n };\n for (const child of children) {\n if (isLoose(child)) run.push(child);\n else {\n flush();\n out.push(child);\n }\n }\n flush();\n return out;\n}\n\n/**\n * The node and its subtree, cleaned. Returns a LIST: a removed node gives\n * none, an unwrapped one gives its children.\n * @param {EmailNode} node @param {Set<string>} keepImages\n * @returns {EmailNode[]}\n */\nfunction cleanNode(node, keepImages) {\n if (REMOVED_TAGS.has(node.tag)) return [];\n const children = node.children.flatMap((child) => cleanNode(child, keepImages));\n const attributes = cleanAttributes(node, keepImages);\n\n if (node.tag === 'mj-wrapper') {\n // Its sections take its place; a background it had goes to those without one.\n return children.map((child) =>\n child.attributes['background-color'] || !attributes['background-color']\n ? child\n : { ...child, attributes: { ...child.attributes, 'background-color': attributes['background-color'] } }\n );\n }\n if (node.tag === 'mj-group') return children;\n if (node.tag === 'mj-hero') {\n const kept = Object.fromEntries(Object.entries(attributes).filter(([name]) => HERO_KEPT.includes(name)));\n const columns = children.some((child) => child.tag === 'mj-column') ? children : [column(children)];\n return [createNode('mj-section', { id: node.id, attributes: { 'background-color': PALETTE.navy, ...kept }, children: columns })];\n }\n if (node.tag === 'mj-section') {\n return [{ ...node, attributes, children: wrapLoose(children, (child) => child.tag !== 'mj-column', column) }];\n }\n if (node.tag === 'mj-body') {\n const sectioned = wrapLoose(children, (child) => child.tag === 'mj-column', (nodes) => whiteSection(nodes));\n const wrapped = wrapLoose(sectioned, (child) => CONTENT_TAGS.has(child.tag), (nodes) => whiteSection([column(nodes)]));\n return [{ ...node, attributes: { 'background-color': PALETTE.canvas, ...attributes, width: '600px' }, children: wrapped }];\n }\n return [{ ...node, attributes, children }];\n}\n\n/** \"order.number\" → \"order number\". @param {string} key */\nconst placeholderOf = (key) => `[${key.replace(/[._]+/g, ' ').trim()}]`;\n\n/**\n * Unknown `{{tags}}` replaced: inside a URL attribute by an https example\n * link, elsewhere by a visible `[placeholder]`.\n * @param {string} mjml @param {Set<string>} known\n */\nfunction replaceUnknownVariables(mjml, known) {\n const unknown = (/** @type {string} */ key) => !known.has(key);\n const inUrls = mjml.replace(/\\s(href|src)=\"([^\"]*)\"/g, (whole, name, value) =>\n /\\{\\{\\s*([A-Za-z][\\w.]*)/.test(value) && [...value.matchAll(/\\{\\{\\s*([A-Za-z][\\w.]*)/g)].some((m) => unknown(m[1]))\n ? ` ${name}=\"${name === 'href' ? 'https://example.com' : ''}\"`\n : whole\n );\n return inUrls.replace(/\\{\\{\\s*([A-Za-z][\\w.]*)\\s*(?:\\|[^{}]*)?\\}\\}/g, (whole, key) => (unknown(key) ? placeholderOf(key) : whole));\n}\n\n/**\n * The MJML of a generated email, rewritten as the builder writes it.\n *\n * @param {string} mjml\n * @param {{variables?: {key: string}[], keepImages?: string[]}} [options]\n * `variables`: the catalog (`unsubscribe` is always known);\n * `keepImages`: image sources kept as they are (the sender's logo).\n */\nexport function normalizeGeneratedMjml(mjml, { variables = [], keepImages = [] } = {}) {\n const tree = parseMjml(String(mjml || ''));\n const [root] = cleanNode(tree, new Set(keepImages.filter(Boolean)));\n const known = new Set(['unsubscribe', ...variables.map((variable) => variable.key)]);\n return replaceUnknownVariables(serializeMjml(root), known);\n}\n\n/* ── Review ──────────────────────────────────────────────────────────────── */\n\n/**\n * What still keeps the email from being used as it is — in English, written\n * for the model that will correct it.\n *\n * @param {EmailDraft} draft\n * @param {{kind?: string, compileWarnings?: {message: string}[]}} [options]\n * @returns {{blocking: string[], advice: string[]}}\n * `blocking`: the builder must not open it (rejected by the validator);\n * `advice`: it opens, but is not what was asked.\n */\nexport function reviewDraft(draft, { kind = 'marketing', compileWarnings = [] } = {}) {\n const blocking = validateMjml(draft.mjml).issues.map((issue) => issue.message);\n /** @type {string[]} */\n const advice = [];\n if (!/<mj-section[\\s>]/.test(draft.mjml)) advice.push('The email has no <mj-section>: build it from sections and columns.');\n const unsubscribe = /\\{\\{\\s*unsubscribe\\s*\\}\\}/.test(draft.mjml);\n if (kind !== 'security' && !unsubscribe) {\n advice.push('The footer has no link with href=\"{{ unsubscribe }}\".');\n }\n if (kind === 'security' && unsubscribe) advice.push('A security email must not have an unsubscribe link.');\n if (!draft.subject) advice.push('The subject is empty.');\n if (!draft.preheader) advice.push('The preheader is empty.');\n for (const warning of compileWarnings.slice(0, 8)) advice.push(`MJML: ${warning.message}`);\n return { blocking, advice };\n}\n","// @ts-check\n/**\n * THE PROMPT that writes an email from scratch.\n *\n * Two parts:\n * system fixed: what the builder can edit and how a good email is made.\n * The same for every request, so a provider can cache it;\n * user the request: the purpose and its brief (`purposes.js`), the\n * user's details, the language, the sender and the variables.\n *\n * The rules in the system prompt are the builder's own conventions, the ones\n * the blocks and the gallery follow (see `blocks/shared.js`, `starter.js`):\n * longhand paddings, explicit fonts and colors on every text, web-safe fonts,\n * empty image sources. An email that follows them opens in the editor block\n * by block, and the canvas shows what will be sent.\n */\n\nimport { FONT_OPTIONS, PALETTE } from '../../blocks/shared.js';\n\n/**\n * @typedef {import('./purposes.js').EmailPurpose} EmailPurpose\n * @typedef {{key: string, label?: string}} PromptVariable\n * @typedef {{name?: string, address?: string, logoUrl?: string}} PromptSender\n */\n\n/** JSON schema of the answer (structured output where the provider has it). */\nexport const EMAIL_DRAFT_SCHEMA = {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Short internal name of the template' },\n subject: { type: 'string', description: 'Subject line' },\n preheader: { type: 'string', description: 'Preview text shown after the subject in the inbox' },\n mjml: { type: 'string', description: 'The complete MJML document' },\n },\n required: ['name', 'subject', 'preheader', 'mjml'],\n additionalProperties: false,\n};\n\nconst FONTS = FONT_OPTIONS.map((option) => `\"${option.value}\"`).join(', ');\n\nexport const SYSTEM_PROMPT = `You are a senior email designer and copywriter. You write complete, ready-to-send marketing and transactional emails in MJML for a visual email builder. The user will open your email in the builder, edit it block by block and send it, so it must follow the builder's conventions exactly.\n\n# Answer\nReturn one JSON object, nothing else: {\"name\": string, \"subject\": string, \"preheader\": string, \"mjml\": string}.\n- name: a short internal template name (at most 50 characters), in the email's language.\n- subject: at most 60 characters, specific and honest, no ALL CAPS words, no spam triggers (\"FREE!!!\", \"$$$\"), at most one emoji and only when the occasion is festive.\n- preheader: 40 to 110 characters that complete the subject (never repeat it).\n- mjml: the full MJML document.\n\n# MJML the builder can edit\n- Skeleton: <mjml><mj-body background-color=\"${PALETTE.canvas}\" width=\"600px\"> ...sections... </mj-body></mjml>. No <mj-head>: the builder adds the title, preview text and language itself.\n- The body is a vertical stack of <mj-section>. Each section holds one to four <mj-column>. Content elements live only inside a column.\n- Allowed elements, and only these: mj-section, mj-column, mj-text, mj-image, mj-button, mj-divider, mj-spacer, mj-social, mj-social-element.\n- Never use mj-hero, mj-wrapper, mj-group, mj-table, mj-navbar, mj-raw, mj-style, mj-font, mj-include, mj-attributes, mj-class, the css-class attribute, <style> or <script>.\n- Inside mj-text and mj-button, only simple inline HTML: <b>, <strong>, <i>, <em>, <u>, <br />, <a href=\"...\" style=\"...\">, <span style=\"...\">, <p style=\"margin:0 0 12px 0;\">, <ul>, <ol>, <li>. No <h1>-<h6>, <div>, <table> or <img> in text: a heading is an mj-text written large and bold.\n- Inline style attributes only on <a>, <span>, <p>, <li>, and only for color, font-weight, font-style, text-decoration and margin.\n\n# Attributes\n- Padding ALWAYS as the four longhand attributes padding-top, padding-right, padding-bottom, padding-left. Never the padding=\"...\" shorthand. The one exception: inner-padding on mj-button (shorthand, for example inner-padding=\"12px 28px\").\n- Every mj-text and mj-button sets font-family, font-size, line-height and color itself (plus font-weight=\"bold\" when bold). Sizes in px: font-size=\"16px\", line-height=\"24px\".\n- Fonts: web-safe only, one family for the whole email (at most two: one for headings, one for text): ${FONTS}. No web fonts.\n- Side spacing: content elements of one-column sections take padding-left=\"32px\" padding-right=\"32px\". In two- to four-column sections use 12px to 16px on the sides of each element.\n- Sections: background-color=\"${PALETTE.surface}\" for content (or a brand tint), padding-top and padding-bottom between 8px and 48px. The email reads as one card on the ${PALETTE.canvas} body background.\n- Column widths in percent only when unequal (for example width=\"40%\" and width=\"60%\"), always adding up to 100%.\n- Images: <mj-image src=\"\" alt=\"...\" ...>. Leave src EMPTY: the user uploads their own pictures. Always write an alt that describes the picture to place, in the email's language. No background-url either. Use 0 to 3 images; product images in columns.\n- Buttons: border-radius=\"6px\", font-weight=\"bold\", inner-padding=\"12px 28px\", aligned like the text around them, href with an https URL. When the real URL is unknown, use https://example.com followed by a meaningful path (https://example.com/reset-password).\n- Dividers: border-color in a light gray, border-width=\"1px\".\n- Social icons (only when the details ask for them, in the footer): <mj-social mode=\"horizontal\" align=\"center\" icon-size=\"28px\" border-radius=\"14px\" inner-padding=\"6px\"> with <mj-social-element name=\"facebook-noshare\" href=\"https://...\"></mj-social-element>; names: facebook-noshare, instagram-noshare, linkedin-noshare, x-noshare, youtube-noshare, github-noshare.\n\n# Design\n- Structure: a header section (the sender's logo or name), a hero section, one to four body sections, a footer section. Three to eight sections in total.\n- One brand color for buttons and accents (use the one given, otherwise ${PALETTE.brand} or a color that suits the occasion), text ${PALETTE.ink} on white, headings ${PALETTE.navy} or the brand color.\n- Contrast: body text at least 4.5:1 against its background, large text and buttons at least 3:1. White text only on dark backgrounds.\n- Type scale: main heading 28 to 32px with line-height 36 to 40px, section headings 20 to 22px, body 15 to 16px with line-height 24 to 26px, small print 12 to 13px.\n- A hero is a section with a background color, a large heading, one sentence and usually the main button, centered.\n- One primary call to action (two at most), with a clear 2- to 4-word label that starts with a verb. Repeat it at most once at the end of a long email.\n- Mobile first: columns stack on phones, so each column must make sense on its own. Short paragraphs of one to three sentences.\n\n# Copy\n- Write the real email for the purpose and the details given: specific, useful, warm and concise. Never lorem ipsum, never meta comments.\n- Personalize with the variables listed in the request, written exactly as {{key}}. A greeting gives the first name a natural default after a pipe, in the email's language, used when the name is unknown: Hello {{contact.firstname|there}},. Use no other {{...}} variable.\n- Information that is not given and not in the variables (an order number, a date, a price, a code, an address) is written as a short placeholder in square brackets that the user will replace: [order number], [date], [20%]. Never invent facts that would be wrong.\n- Links read as what they do (\"Reset my password\", \"See the collection\"), never \"click here\".\n- The footer (last section): the sender's name and postal address (or [Company address]) in 12px #6d7484 text, centered, and the footer line the request asks for. The unsubscribe link is exactly <a href=\"{{ unsubscribe }}\" style=\"color:#6d7484;text-decoration:underline;\">...</a>.\n- Keep the MJML under 30 KB.`;\n\n/** Footer line asked for each kind of purpose. @type {Record<string, string>} */\nconst FOOTER_RULES = {\n marketing: 'This is a marketing email: the footer MUST contain the unsubscribe link (href=\"{{ unsubscribe }}\").',\n transactional:\n 'This is a transactional email: the footer contains a discreet \"Manage email preferences\" link with href=\"{{ unsubscribe }}\".',\n security:\n 'This is a security email: NO unsubscribe or preferences link. The footer says in one line why the recipient got this email.',\n};\n\n/**\n * English name of a language code (\"fr\" → \"French\"), the code itself when\n * the runtime does not know it.\n * @param {string} code\n */\nexport function languageName(code) {\n const value = String(code || 'en');\n try {\n return new Intl.DisplayNames(['en'], { type: 'language' }).of(value) || value;\n } catch {\n return value;\n }\n}\n\n/** One line per variable, the unsubscribe link left to the footer rule. @param {PromptVariable[]} variables */\nfunction variableLines(variables) {\n return variables\n .filter((variable) => variable.key !== 'unsubscribe')\n .map((variable) => `- {{${variable.key}}}${variable.label ? `: ${variable.label}` : ''}`)\n .join('\\n');\n}\n\n/**\n * The request message.\n *\n * @param {{\n * purpose?: EmailPurpose|null,\n * details?: string,\n * language?: string,\n * sender?: PromptSender,\n * brand?: {primary?: string, heading?: string, font?: string},\n * variables?: PromptVariable[],\n * }} input\n */\nexport function buildUserPrompt({ purpose = null, details = '', language = 'en', sender = {}, brand = {}, variables = [] }) {\n const lines = [];\n if (purpose) {\n lines.push(`Write a \"${purpose.label}\" email.`, '', `Brief: ${purpose.brief}`);\n } else {\n lines.push('Write the email described below.');\n }\n const extra = String(details || '').trim();\n if (extra) lines.push('', 'Details from the user (they take precedence over the brief):', extra);\n lines.push(\n '',\n `Language: write everything in ${languageName(language)}, unless the details ask for another language.`,\n '',\n 'Sender:',\n `- name: ${sender.name?.trim() || '[Company name]'}`,\n `- postal address: ${sender.address?.trim() || '[Company address]'}`,\n sender.logoUrl\n ? `- logo: put <mj-image src=\"${sender.logoUrl}\" alt=\"${sender.name || 'Logo'}\" width=\"140px\" align=\"left\"> in the header (this is the one image whose src you fill).`\n : '- logo: none, write the sender name in bold in the header.'\n );\n const look = [\n brand.primary && `- main color (buttons, banners, links): ${brand.primary}`,\n brand.heading && `- headings color: ${brand.heading}`,\n brand.font && `- font: font-family=\"${brand.font}\" on every text and button`,\n ].filter(Boolean);\n if (look.length) lines.push('', 'Brand (use exactly these):', ...look);\n const vars = variableLines(variables);\n if (vars) lines.push('', 'Variables you may use:', vars);\n lines.push('', FOOTER_RULES[purpose?.kind || 'marketing']);\n return lines.join('\\n');\n}\n\n/**\n * The message that asks the model to correct its email.\n * @param {string[]} problems\n */\nexport function buildRepairPrompt(problems) {\n return [\n 'The email cannot be opened in the builder as it is. Fix these problems and return the complete corrected JSON object:',\n ...problems.map((problem) => `- ${problem}`),\n ].join('\\n');\n}\n","// @ts-check\n/**\n * WHAT AN EMAIL IS FOR — the purposes offered by \"Generate\".\n *\n * Each purpose carries a short BRIEF for the model: the sections and the\n * information such an email needs (a password reset needs the link's expiry\n * and a \"not you?\" line; a sale needs the offer, its end date and a code).\n * The brief is the part of the prompt that changes with the purpose; the\n * design and MJML rules stay the same (see `prompt.js`).\n *\n * `kind` sets the footer:\n * marketing sent in bulk: an unsubscribe link is required (CAN-SPAM, GDPR);\n * transactional triggered by the recipient's action: a discreet link to the\n * email preferences;\n * security account security: no unsubscribe link at all, a line says\n * why the message was sent.\n *\n * Plain data: labels are English keys translated by the interface, briefs\n * stay in English (they are written for the model, not shown).\n */\n\n/**\n * @typedef {'marketing'|'transactional'|'security'} PurposeKind\n * @typedef {{id: string, label: string}} PurposeCategory\n * @typedef {{id: string, label: string, category: string, kind: PurposeKind, brief: string}} EmailPurpose\n */\n\n/** @type {PurposeCategory[]} */\nexport const PURPOSE_CATEGORIES = [\n { id: 'promotions', label: 'Sales and promotions' },\n { id: 'holidays', label: 'Holidays and occasions' },\n { id: 'events', label: 'Events and appointments' },\n { id: 'news', label: 'News and content' },\n { id: 'account', label: 'Account and security' },\n { id: 'orders', label: 'Orders and payments' },\n { id: 'relationship', label: 'Customer relationship' },\n { id: 'organization', label: 'Business and organization' },\n];\n\n/** @param {string} id @param {string} label @param {string} category @param {PurposeKind} kind @param {string} brief */\nconst purpose = (id, label, category, kind, brief) => ({ id, label, category, kind, brief });\n\n/** @type {EmailPurpose[]} */\nexport const EMAIL_PURPOSES = [\n // Sales and promotions\n purpose('sale', 'Store sale', 'promotions', 'marketing',\n 'An online store sale. A bold hero with the offer (discount and end date), a short reason to buy now, 2 to 4 featured products in two columns (image, name, old and new price, link), one \"Shop the sale\" button, and the conditions in small text.'),\n purpose('flash-sale', 'Flash sale', 'promotions', 'marketing',\n 'A sale that lasts hours, not days. Urgency first: the deadline in the hero, a large discount, one button, very little text.'),\n purpose('discount-code', 'Discount code', 'promotions', 'marketing',\n 'A personal discount code. The code in a large, easy-to-copy box (a colored section with the code in big letters), what it gives, its expiry date, and a button to use it.'),\n purpose('new-product', 'New product', 'promotions', 'marketing',\n 'A product launch. A hero image with the product name, three benefits in three short columns or a list, the price or availability, and a \"Discover\" button.'),\n purpose('back-in-stock', 'Back in stock', 'promotions', 'marketing',\n 'A product the recipient wanted is available again. The product (image, name, price), \"limited stock\" urgency without exaggeration, and a \"Buy now\" button.'),\n purpose('price-drop', 'Price drop', 'promotions', 'marketing',\n 'A product the recipient looked at is now cheaper. Old and new price side by side, the product image, and a button.'),\n purpose('abandoned-cart', 'Abandoned cart', 'promotions', 'marketing',\n 'Items left in the cart. A friendly reminder, the cart items (image, name, price), a \"Return to my cart\" button, and a reassurance line (free returns, secure payment, help contact).'),\n purpose('black-friday', 'Black Friday', 'promotions', 'marketing',\n 'A Black Friday or Cyber Monday campaign. A dark, high-contrast hero with the offer and dates, featured categories or products in columns, and a strong button.'),\n purpose('loyalty-reward', 'Loyalty reward', 'promotions', 'marketing',\n 'Thank a loyal customer with a reward: their status or points balance, the reward, how to use it, and a button.'),\n purpose('refer-a-friend', 'Refer a friend', 'promotions', 'marketing',\n 'Invite the customer to refer friends. What the friend gets, what the customer gets, three short steps, and a button to share their link.'),\n\n // Holidays and occasions\n purpose('christmas', 'Christmas', 'holidays', 'marketing',\n 'Christmas greetings from the company, warm and festive (deep red, green or gold accents), a short heartfelt message, an optional gift idea or offer, and the holiday opening hours if relevant.'),\n purpose('new-year', 'New Year', 'holidays', 'marketing',\n 'New Year wishes. A celebratory hero, thanks for the past year, one or two highlights or promises for the year ahead, and warm wishes.'),\n purpose('season-greetings', \"Season's greetings\", 'holidays', 'marketing',\n 'End-of-year greetings that suit every culture and belief (no religious reference): thanks, warm wishes, and the holiday closing dates.'),\n purpose('valentines-day', \"Valentine's Day\", 'holidays', 'marketing',\n \"Valentine's Day: a romantic tone (soft pink or red), gift ideas in columns, and a delivery deadline.\"),\n purpose('easter', 'Easter', 'holidays', 'marketing',\n 'Easter: a spring palette, greetings, and an optional seasonal offer.'),\n purpose('mothers-day', \"Mother's Day\", 'holidays', 'marketing',\n \"Mother's Day: gift ideas in columns, the order deadline for delivery in time, and a gentle tone.\"),\n purpose('fathers-day', \"Father's Day\", 'holidays', 'marketing',\n \"Father's Day: gift ideas in columns, the order deadline for delivery in time, and a warm tone.\"),\n purpose('halloween', 'Halloween', 'holidays', 'marketing',\n 'Halloween: a playful, spooky tone (orange and near-black, still readable), a themed offer or event.'),\n purpose('back-to-school', 'Back to school', 'holidays', 'marketing',\n 'Back to school: essentials or a checklist in columns, an offer, and a cheerful tone.'),\n purpose('summer', 'Summer holidays', 'holidays', 'marketing',\n 'Summer: a bright palette, summer picks or plans, and the summer opening hours if relevant.'),\n purpose('birthday', 'Birthday wishes', 'holidays', 'marketing',\n \"Happy birthday to the recipient by first name: a festive hero, a personal message, and a birthday gift or code with its validity.\"),\n purpose('anniversary', 'Customer anniversary', 'holidays', 'marketing',\n 'One (or more) years since the recipient became a customer: thanks, a short look back, and a small gift.'),\n purpose('womens-day', \"Women's Day\", 'holidays', 'marketing',\n \"International Women's Day (March 8): respectful greetings, an optional offer or a story about women of the team, no clichés.\"),\n purpose('thanksgiving', 'Thanksgiving', 'holidays', 'marketing',\n 'Thanksgiving: gratitude to customers, a warm autumn palette, and an optional offer.'),\n purpose('lunar-new-year', 'Lunar New Year', 'holidays', 'marketing',\n 'Lunar New Year: wishes for the new year (red and gold accents), the animal of the year if relevant, and an optional offer.'),\n purpose('ramadan-eid', 'Ramadan and Eid', 'holidays', 'marketing',\n 'Ramadan Kareem or Eid Mubarak greetings: respectful and warm, adapted opening hours, an optional offer.'),\n purpose('diwali', 'Diwali', 'holidays', 'marketing',\n 'Diwali, the festival of lights: warm wishes, a light-filled palette (gold, deep orange), an optional offer.'),\n\n // Events and appointments\n purpose('meeting-reminder', 'Meeting reminder', 'events', 'transactional',\n 'A reminder of an upcoming meeting: what, when (date, time, time zone), where (address or video link), who, what to prepare, and buttons to join and to reschedule.'),\n purpose('appointment-confirmation', 'Appointment confirmation', 'events', 'transactional',\n 'Confirm a booked appointment (doctor, salon, garage, consultant): service, date and time, address with directions, how to cancel or reschedule, and a contact.'),\n purpose('reservation-confirmation', 'Booking confirmation', 'events', 'transactional',\n 'Confirm a booking (restaurant table, hotel room, class): booking details in a clear summary box, arrival information, and how to change it.'),\n purpose('event-invitation', 'Event invitation', 'events', 'marketing',\n 'Invite to an event: a hero with the name, date and place, what to expect (program or speakers in columns), and an \"I will come\" RSVP button.'),\n purpose('webinar-invitation', 'Webinar invitation', 'events', 'marketing',\n 'Invite to a webinar: the topic and what attendees will learn (three points), the speaker, date and time with time zone, and a \"Register\" button.'),\n purpose('save-the-date', 'Save the date', 'events', 'marketing',\n 'Announce a date to keep free before the full invitation: the event name and date very large, one sentence, and an \"Add to calendar\" button.'),\n purpose('registration-confirmed', 'Registration confirmed', 'events', 'transactional',\n 'Confirm a registration to an event, a course or a webinar: what, when, where or the access link, an \"Add to calendar\" button, and how to cancel.'),\n purpose('event-change', 'Cancellation or new date', 'events', 'transactional',\n 'An event or appointment is cancelled or moved: the change in the first line, the new date if any, what happens to tickets or bookings, an apology, and a contact.'),\n purpose('event-follow-up', 'Thanks for attending', 'events', 'marketing',\n 'Thank attendees after an event: a recap, links to the replay or slides, a short survey, and the next event.'),\n\n // News and content\n purpose('newsletter', 'Newsletter', 'news', 'marketing',\n 'A newsletter: an intro, a lead story with an image, 2 or 3 shorter articles (title, two lines, \"Read more\" link), and an optional tip or quote.'),\n purpose('new-article', 'New article', 'news', 'marketing',\n 'Share one new blog article or video: its image, title, a two-sentence teaser, and a \"Read the article\" button.'),\n purpose('product-update', \"What's new\", 'news', 'marketing',\n \"A product update or release notes: the headline feature with an image, 2 or 3 smaller improvements, and a button to try it.\"),\n purpose('announcement', 'Announcement', 'news', 'marketing',\n 'An important announcement (new name, new address, partnership, milestone): what changes, why, what it means for the recipient, and a button.'),\n purpose('tips', 'Tips and how-to', 'news', 'marketing',\n 'Practical tips: 3 to 5 numbered tips, each with a short title and one or two sentences, and a button to learn more.'),\n\n // Account and security\n purpose('welcome', 'Welcome', 'account', 'transactional',\n 'Welcome a new user or subscriber: a warm hero, what they can expect, 3 first steps, and one button to get started.'),\n purpose('verify-email', 'Email verification', 'account', 'security',\n 'Ask to confirm the email address: one short sentence, a \"Confirm my email\" button, the link validity, and \"If you did not sign up, ignore this email\".'),\n purpose('password-reset', 'Password reset', 'account', 'security',\n 'A password reset: \"We received a request to reset your password\", a \"Reset my password\" button, the link expiry, and \"If you did not ask, ignore this email; your password stays the same\". Sober and reassuring, no marketing.'),\n purpose('login-code', 'Login code', 'account', 'security',\n 'A one-time sign-in code: the code as a placeholder in very large letters in a light box, its validity in minutes, and a warning never to share it. No button.'),\n purpose('security-alert', 'Security alert', 'account', 'security',\n 'A new sign-in or account change: what happened, when, device and location, \"If it was you, nothing to do\", and a \"Secure my account\" button.'),\n purpose('onboarding', 'Getting started', 'account', 'transactional',\n 'Help a new user get value: one key feature explained in 3 steps with short texts, a button, and where to find help.'),\n purpose('trial-ending', 'Trial ending', 'account', 'transactional',\n 'The free trial ends soon: the end date, what they lose, the plans or price, and an \"Upgrade\" button.'),\n\n // Orders and payments\n purpose('order-confirmation', 'Order confirmation', 'orders', 'transactional',\n 'Confirm an order: thanks, order number and date, the items in a summary (name, quantity, price), totals, shipping address, delivery estimate, and a \"View my order\" button.'),\n purpose('order-shipped', 'Order shipped', 'orders', 'transactional',\n 'The order is on its way: carrier, expected delivery date, a \"Track my parcel\" button, and the shipped items.'),\n purpose('delivery-delay', 'Delivery delay', 'orders', 'transactional',\n 'An order will arrive late: an apology, the new estimated date, the reason in one sentence, an optional gesture, and a \"Track my order\" button.'),\n purpose('receipt', 'Receipt or invoice', 'orders', 'transactional',\n 'A payment receipt or invoice: amount paid, date, payment method, a clear breakdown, and a \"Download the invoice\" button.'),\n purpose('payment-failed', 'Payment failed', 'orders', 'transactional',\n 'A payment did not go through: calm and helpful, what happens next and when, and an \"Update my payment method\" button.'),\n purpose('subscription-renewal', 'Subscription renewal', 'orders', 'transactional',\n 'An upcoming subscription renewal: plan, amount, renewal date, and buttons to manage the subscription.'),\n purpose('refund', 'Refund issued', 'orders', 'transactional',\n 'A refund was issued: amount, original order, when it reaches the account, and a contact for questions.'),\n\n // Customer relationship\n purpose('thank-you', 'Thank you', 'relationship', 'marketing',\n 'A sincere thank-you (for a purchase, a year together, a donation, a recommendation): personal tone, short, and an optional small gift.'),\n purpose('review-request', 'Review request', 'relationship', 'marketing',\n 'Ask for a review of a recent purchase or visit: the product or service, one sentence on why reviews matter, and a \"Leave a review\" button.'),\n purpose('survey', 'Survey', 'relationship', 'marketing',\n 'Ask for feedback: how long it takes (for example 2 minutes), why it helps, an optional incentive, and a \"Take the survey\" button.'),\n purpose('win-back', 'We miss you', 'relationship', 'marketing',\n 'Re-engage an inactive customer: what is new since their last visit, a comeback offer, and a button.'),\n purpose('follow-up', 'Follow-up', 'relationship', 'transactional',\n 'A personal follow-up after a meeting, a call or a quote: a reminder of what was discussed, the next step, and an easy way to reply or book a call. Short and personal, like a message from one person.'),\n purpose('apology', 'Apology', 'relationship', 'transactional',\n 'A sincere apology for a problem (outage, mistake, late delivery): what happened, what was done to fix it, a gesture if any, and a contact. No marketing.'),\n\n // Business and organization\n purpose('hiring', \"We're hiring\", 'organization', 'marketing',\n 'Job openings: why join the team, 2 to 4 open roles (title, place, one line), and a \"See the jobs\" button.'),\n purpose('policy-update', 'Policy update', 'organization', 'transactional',\n 'Updated terms or privacy policy: the date it takes effect, the key changes in a short list, and a link to the full text. Neutral and clear.'),\n purpose('price-change', 'Price change', 'organization', 'transactional',\n 'A price change notice: old and new price, the effective date, why, and how to change plan or cancel. Honest and respectful.'),\n purpose('maintenance', 'Scheduled maintenance', 'organization', 'transactional',\n 'Planned maintenance or an incident: when, how long, what is affected, and where to follow the status.'),\n purpose('closure', 'Closure or opening hours', 'organization', 'marketing',\n 'Holiday closure or new opening hours: the dates and hours very clearly, what customers can still do, and wishes.'),\n purpose('donation-appeal', 'Donation appeal', 'organization', 'marketing',\n 'A fundraising appeal for a nonprofit: one concrete story, what a gift makes possible (amount examples), and a \"Donate\" button.'),\n purpose('team-update', 'Internal update', 'organization', 'transactional',\n 'An internal message to employees: news, dates to remember, and a contact. Plain and friendly.'),\n];\n\n/** @param {string|null|undefined} id */\nexport function purposeById(id) {\n return EMAIL_PURPOSES.find((entry) => entry.id === id) || null;\n}\n\n/** Purposes grouped by category, in category order. */\nexport function purposesByCategory(purposes = EMAIL_PURPOSES) {\n return PURPOSE_CATEGORIES.map((category) => ({\n ...category,\n purposes: purposes.filter((entry) => entry.category === category.id),\n })).filter((group) => group.purposes.length > 0);\n}\n","// @ts-check\n/**\n * REWRITE — one text of the email, rewritten by the model: shorter, longer,\n * fixed, friendlier, more formal, translated, or changed as the user says.\n *\n * The text is either the HTML of a text block (`html`) or the label of a\n * button (`plain`). What comes back must keep what the email depends on:\n *\n * links the same hrefs, no new one (the words around them may change);\n * variables the same `{{keys}}`, none added (a fallback after `|` may be\n * translated);\n * markup what the server's validator accepts in a text block.\n *\n * When one of them is lost, the model gets one more turn with the list; then\n * the rewrite fails and the text stays as it was.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { validateMjml } from '../../validation/mjmlValidator.js';\nimport { EmailGenerationError } from './draft.js';\nimport { languageName } from './prompt.js';\n\n/**\n * The actions offered as buttons, in order. `translate` takes a language,\n * a custom change (`custom`) takes the user's words.\n */\nexport const REWRITE_ACTIONS = [\n { id: 'shorter', label: 'Shorter', change: 'Make it noticeably shorter, about half as long. Keep the meaning, the key facts and every link.' },\n {\n id: 'longer',\n label: 'Longer',\n change: 'Make it somewhat longer, about one and a half times, and more vivid. Develop what is there; invent no fact.',\n },\n {\n id: 'fix',\n label: 'Fix mistakes',\n change: 'Fix spelling, grammar and punctuation only. Change nothing else: same words, same tone, same length.',\n },\n { id: 'friendlier', label: 'Friendlier', change: 'Make the tone warmer and friendlier, as if talking to a customer you like. Same facts, about the same length.' },\n { id: 'formal', label: 'More formal', change: 'Make the tone more professional and formal. Same facts, about the same length.' },\n];\n\n/** Languages offered by \"Translate\", shown under their own name. */\nexport const REWRITE_LANGUAGES = ['en', 'fr', 'de', 'es', 'it', 'pt', 'nl', 'pl', 'uk', 'ru', 'tr', 'ja', 'zh'];\n\nexport const MAX_REWRITE_TEXT = 6000;\nexport const MAX_REWRITE_INSTRUCTION = 300;\n\nexport const REWRITE_SCHEMA = {\n type: 'object',\n properties: { text: { type: 'string', description: 'The rewritten text, nothing else.' } },\n required: ['text'],\n additionalProperties: false,\n};\n\nexport const REWRITE_SYSTEM_PROMPT = `You are an email copywriter. You rewrite ONE piece of text taken from an email, as asked, and answer with the rewritten text only, in the JSON field \"text\".\n\nThe text is one of:\n- HTML, the content of a text block: keep the same kind of markup. Allowed tags: <p>, <br>, <b>, <strong>, <i>, <em>, <u>, <s>, <span style=\"...\">, <a href=\"...\">, <ul>, <ol>, <li>, <sup>, <sub>. No other tag, no class, no new style. Keep bold, italic and links on the words they belong to.\n- plain text, the label of a button: answer plain text without HTML, a short call to action of a few words.\n\nAlways:\n- Keep every link: each <a href=\"...\"> of the text stays, with its href unchanged; the words of the link may change. Add no link.\n- Keep every merge tag in double braces exactly as written, e.g. {{contact.firstname}} or {{ unsubscribe }}. In {{key|fallback}} only the fallback after \"|\" may change (translated, for instance). Add no merge tag.\n- Keep [placeholders in square brackets]: the user replaces them later. Translate their words when translating.\n- Invent no facts: no new price, date, number, name, guarantee or promise.\n- Write in the language of the text, unless asked to translate.\n- No preamble, no comment, no quotation marks around the text.`;\n\n/** @param {unknown} value @param {number} max */\nconst bounded = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/** The words of an HTML fragment. @param {string} html */\nconst wordsOf = (html) =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/**\n * @typedef {{\n * action: string,\n * text: string,\n * format?: 'html'|'plain',\n * targetLanguage?: string,\n * instruction?: string,\n * }} RewriteInput\n * `action`: an id of REWRITE_ACTIONS, `translate` (with `targetLanguage`)\n * or `custom` (with `instruction`).\n */\n\n/**\n * The request as the rewriter uses it, or an error when there is nothing to do.\n * @param {RewriteInput} input\n */\nexport function cleanRewriteInput(input) {\n const source = input && typeof input === 'object' ? input : /** @type {RewriteInput} */ ({ action: '', text: '' });\n const format = /** @type {'html'|'plain'} */ (source.format === 'plain' ? 'plain' : 'html');\n const text = bounded(source.text, MAX_REWRITE_TEXT);\n if (!wordsOf(text)) throw new EmailGenerationError('NOTHING_TO_REWRITE');\n const preset = REWRITE_ACTIONS.find((action) => action.id === source.action);\n if (preset) return { format, text, change: preset.change };\n if (source.action === 'translate') {\n const language = String(source.targetLanguage || '');\n if (!/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(language)) throw new EmailGenerationError('NOTHING_TO_REWRITE', 'No target language.');\n return { format, text, change: `Translate it into ${languageName(language)}. Keep the tone and the length.` };\n }\n const instruction = bounded(source.instruction, MAX_REWRITE_INSTRUCTION);\n if (source.action === 'custom' && instruction) return { format, text, change: instruction };\n throw new EmailGenerationError('NOTHING_TO_REWRITE', 'Unknown action.');\n}\n\n/** @param {{format: 'html'|'plain', text: string, change: string}} request */\nexport function buildRewritePrompt({ format, text, change }) {\n return [\n `Change: ${change}`,\n `Format: ${format === 'plain' ? 'plain text (a button label)' : 'HTML (a text block)'}`,\n '',\n '<text>',\n text,\n '</text>',\n ].join('\\n');\n}\n\n/** The text in a model's answer: the JSON field, or the bare answer. @param {string} answer */\nexport function parseRewrite(answer) {\n const source = String(answer ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n if (unfenced.startsWith('{')) {\n try {\n const data = JSON.parse(unfenced);\n if (typeof data?.text === 'string') return data.text.trim();\n } catch {\n // not JSON after all: the text itself\n }\n }\n return unfenced.replace(/^<text>\\s*/, '').replace(/\\s*<\\/text>$/, '');\n}\n\n/** @param {string} html */\nconst hrefsOf = (html) => [...html.matchAll(/<a\\b[^>]*?\\shref\\s*=\\s*([\"'])(.*?)\\1/gi)].map((match) => match[2].trim());\n/** @param {string} html */\nconst variableKeysOf = (html) => [...html.matchAll(/\\{\\{\\s*([^}|]+?)\\s*(?:\\|[^}]*)?\\}\\}/g)].map((match) => match[1]);\n\n/**\n * What is wrong with a rewritten text, for the model (empty: usable).\n * @param {string} original @param {string} rewritten @param {'html'|'plain'} format\n */\nexport function reviewRewrite(original, rewritten, format) {\n /** @type {string[]} */\n const problems = [];\n if (!wordsOf(rewritten)) return ['The answer is empty.'];\n const before = new Set(variableKeysOf(original));\n const after = new Set(variableKeysOf(rewritten));\n for (const key of before) if (!after.has(key)) problems.push(`The merge tag {{${key}}} is missing: keep it.`);\n for (const key of after) if (!before.has(key)) problems.push(`The merge tag {{${key}}} was added: remove it.`);\n if (format === 'plain') return problems;\n const links = new Set(hrefsOf(original));\n const kept = new Set(hrefsOf(rewritten));\n for (const href of links) if (!kept.has(href)) problems.push(`The link to ${href} is missing: keep it with the same href.`);\n for (const href of kept) if (!links.has(href)) problems.push(`The link to ${href} was added: remove it.`);\n const wrapped = `<mjml><mj-body><mj-section><mj-column><mj-text>${rewritten}</mj-text></mj-column></mj-section></mj-body></mjml>`;\n for (const issue of validateMjml(wrapped).issues) problems.push(issue.message);\n return problems;\n}\n\n/**\n * @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options\n * @returns {(input: RewriteInput, options?: {signal?: AbortSignal}) => Promise<{text: string, attempts: number}>}\n */\nexport function createRewriter({ model, maxAttempts = 2 }) {\n return async function rewrite(input, { signal } = {}) {\n const request = cleanRewriteInput(input);\n /** @type {import('./generator.js').ModelMessage[]} */\n const messages = [{ role: 'user', content: buildRewritePrompt(request) }];\n let lastProblem = '';\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n let answer;\n try {\n answer = await model({ system: REWRITE_SYSTEM_PROMPT, messages, schema: REWRITE_SCHEMA, signal, task: 'rewrite' });\n } catch (error) {\n if (error instanceof EmailGenerationError && error.code === 'REFUSED') throw new EmailGenerationError('REWRITE_REFUSED');\n throw error;\n }\n let text = parseRewrite(answer);\n // A label: the words alone, whatever markup came with them.\n if (request.format === 'plain') text = wordsOf(text).slice(0, 120);\n const problems = reviewRewrite(request.text, text, request.format);\n if (!problems.length) return { text, attempts: attempt };\n lastProblem = problems[0];\n messages.push(\n { role: 'assistant', content: String(answer) },\n { role: 'user', content: `Fix these problems and answer the whole text again:\\n${problems.map((p) => `- ${p}`).join('\\n')}` }\n );\n }\n throw new EmailGenerationError('REWRITE_UNUSABLE', lastProblem);\n };\n}\n","// @ts-check\n/**\n * SUBJECT IDEAS — three other subject lines for an email, drawn from its words.\n *\n * The model gets what the inbox shows (the subject and the preview text) and\n * the words of the email, and answers three subjects that take three\n * different angles. What comes back is one line each, different from the\n * current subject and from each other, with no merge tag the catalog does not\n * know; when fewer than three are left, the model gets one more turn with the\n * list of problems.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { DEFAULT_VARIABLES } from '../variables/catalog.js';\nimport { EmailGenerationError } from './draft.js';\nimport { languageName } from './prompt.js';\n\nexport const SUBJECT_COUNT = 3;\nexport const MAX_SUBJECT_LENGTH = 150;\nexport const MAX_EMAIL_WORDS = 4000;\nconst MAX_VARIABLES = 60;\n\n/**\n * @typedef {{\n * subject?: string,\n * preheader?: string,\n * text?: string,\n * language?: string,\n * variables?: {key: string, label?: string}[],\n * avoid?: string[],\n * }} SubjectsInput\n * `text`: the words of the email (`emailWords`); `language`: the one of the\n * interface, used when the email has no words yet; `avoid`: ideas already\n * shown (\"More ideas\" asks for new ones).\n */\n\nexport const SUBJECTS_SCHEMA = {\n type: 'object',\n properties: {\n subjects: {\n type: 'array',\n description: 'Exactly three subject lines, plain text.',\n items: { type: 'string' },\n },\n },\n required: ['subjects'],\n additionalProperties: false,\n};\n\nexport const SUBJECTS_SYSTEM_PROMPT = `You write email subject lines. You get an email (its current subject, its preview text and its words) and answer three NEW subject lines for it, in the JSON field \"subjects\".\n\nEach subject:\n- tells what the email brings, specific to its content: under 50 characters when possible, 70 at most;\n- takes its own angle: one clear and direct, one that makes the reader curious, one personal or timely;\n- is written in the language of the email;\n- invents no facts: no price, discount, date, number, name or promise the email does not contain;\n- is plain text: no HTML, no quotation marks around it, no line break;\n- has no word in capitals, at most one \"!\", no spam wording (\"free!!!\", \"act now\", \"100% guaranteed\");\n- has no emoji, unless the current subject has one;\n- differs from the current subject and from the two others, and does not repeat the preview text.\n\nMerge tags: you may personalise ONE of the three with a merge tag from the list you are given, written exactly as listed, e.g. {{contact.firstname}}. No other merge tag.`;\n\n/** @param {unknown} value @param {number} max */\nconst bounded = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/** One line of plain text. @param {unknown} value */\nconst line = (value) =>\n String(value ?? '')\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^[\"'«“”‘’]+|[\"'»“”‘’]+$/g, '')\n .trim()\n .slice(0, MAX_SUBJECT_LENGTH);\n\nconst ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: ' ' };\n\n/**\n * The words of an email, as a reader sees them: the head, the styles and the\n * markup left out.\n * @param {string} mjml\n */\nexport function emailWords(mjml) {\n return String(mjml || '')\n .replace(/<mj-head[\\s\\S]*?<\\/mj-head>/gi, ' ')\n .replace(/<(style|script|mj-style|mj-raw)[\\s\\S]*?<\\/\\1>/gi, ' ')\n .replace(/<\\/(p|div|li|h[1-6]|mj-text|mj-button|mj-navbar-link|mj-social-element)>|<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]*>/g, ' ')\n .replace(/&(amp|lt|gt|quot|apos|nbsp);/g, (_, name) => ENTITIES[/** @type {keyof typeof ENTITIES} */ (name)])\n .replace(/&#(\\d+);/g, (_, code) => String.fromCodePoint(Number(code)))\n .replace(/[ \\t]+/g, ' ')\n .replace(/\\s*\\n\\s*/g, '\\n')\n .trim()\n .slice(0, MAX_EMAIL_WORDS);\n}\n\n/**\n * The request as the model sees it, or an error when there is nothing to\n * draw subjects from.\n * @param {SubjectsInput} input\n */\nexport function cleanSubjectsInput(input) {\n const source = input && typeof input === 'object' ? input : {};\n const subject = line(source.subject);\n const preheader = bounded(source.preheader, 300).replace(/\\s+/g, ' ');\n const text = bounded(source.text, MAX_EMAIL_WORDS);\n if (!subject && !text) throw new EmailGenerationError('NOTHING_TO_SUGGEST');\n const language = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(String(source.language || '')) ? String(source.language) : 'en';\n const variables = (Array.isArray(source.variables) ? source.variables : DEFAULT_VARIABLES)\n .filter((variable) => variable && /^[A-Za-z][\\w.]*$/.test(String(variable.key)) && variable.key !== 'unsubscribe')\n .filter((variable) => /** @type {any} */ (variable).kind !== 'link')\n .slice(0, MAX_VARIABLES)\n .map((variable) => ({ key: String(variable.key), label: bounded(variable.label, 80) }));\n const avoid = (Array.isArray(source.avoid) ? source.avoid : []).map(line).filter(Boolean).slice(0, 12);\n return { subject, preheader, text, language, variables, avoid };\n}\n\n/** @param {ReturnType<typeof cleanSubjectsInput>} request */\nexport function buildSubjectsPrompt({ subject, preheader, text, language, variables, avoid }) {\n return [\n `Current subject: ${subject || '(none yet)'}`,\n `Preview text: ${preheader || '(none)'}`,\n avoid.length ? `Already suggested, do not repeat: ${avoid.map((idea) => `\"${idea}\"`).join(', ')}` : null,\n `Merge tags you may use: ${variables.length ? variables.map((v) => `{{${v.key}}}${v.label ? ` (${v.label})` : ''}`).join(', ') : 'none'}`,\n text ? null : `The email has no words yet: write in ${languageName(language)}.`,\n '',\n '<email>',\n text,\n '</email>',\n ]\n .filter((row) => row !== null)\n .join('\\n');\n}\n\n/**\n * A capital letter first, as a sentence starts — unless the first word is\n * styled that way on purpose (iPhone, eBay). @param {string} subject\n */\nconst capitalized = (subject) => {\n const first = subject.split(/\\s/)[0];\n return /^\\p{Ll}/u.test(first) && !/\\p{Lu}/u.test(first) ? subject[0].toLocaleUpperCase() + subject.slice(1) : subject;\n};\n\n/** The subjects in a model's answer: the JSON field, else one per line. @param {string} answer */\nexport function parseSubjects(answer) {\n const source = String(answer ?? '').trim();\n const unfenced = source.replace(/^```[a-z]*\\s*\\n?/i, '').replace(/\\n?```\\s*$/, '').trim();\n const start = unfenced.indexOf('{');\n const end = unfenced.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n const data = JSON.parse(unfenced.slice(start, end + 1));\n if (Array.isArray(data?.subjects)) return data.subjects.map((subject) => capitalized(line(subject)));\n } catch {\n // not JSON: one subject per line\n }\n }\n return unfenced.split('\\n').map((row) => capitalized(line(row.replace(/^\\s*(?:[-*•]|\\d+[.)])\\s*/, ''))));\n}\n\n/** @param {string} text */\nconst keysOf = (text) => [...text.matchAll(/\\{\\{\\s*([^}|]+?)\\s*(?:\\|[^}]*)?\\}\\}/g)].map((match) => match[1]);\n\n/**\n * The usable subjects, and what is wrong with the others (for the model).\n * @param {string[]} subjects @param {ReturnType<typeof cleanSubjectsInput>} request\n */\nexport function reviewSubjects(subjects, request) {\n const known = new Set(request.variables.map((variable) => variable.key));\n const seen = new Set([request.subject, ...request.avoid].map((subject) => subject.toLocaleLowerCase()));\n /** @type {string[]} */\n const usable = [];\n /** @type {string[]} */\n const problems = [];\n for (const subject of subjects) {\n if (!subject) continue;\n const unknown = keysOf(subject).filter((key) => !known.has(key));\n if (unknown.length) {\n problems.push(`\"${subject}\" uses {{${unknown[0]}}}, which is not in the list of merge tags.`);\n continue;\n }\n const folded = subject.toLocaleLowerCase();\n if (seen.has(folded)) {\n problems.push(`\"${subject}\" repeats the current subject or another one.`);\n continue;\n }\n seen.add(folded);\n usable.push(subject);\n }\n if (usable.length < SUBJECT_COUNT && !problems.length) problems.push(`Give exactly ${SUBJECT_COUNT} subjects.`);\n return { usable: usable.slice(0, SUBJECT_COUNT), problems: usable.length >= SUBJECT_COUNT ? [] : problems };\n}\n\n/**\n * @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options\n * @returns {(input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[], attempts: number}>}\n */\nexport function createSubjectSuggester({ model, maxAttempts = 2 }) {\n return async function suggestSubjects(input, { signal } = {}) {\n const request = cleanSubjectsInput(input);\n /** @type {import('./generator.js').ModelMessage[]} */\n const messages = [{ role: 'user', content: buildSubjectsPrompt(request) }];\n /** @type {string[]} */\n let best = [];\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n let answer;\n try {\n answer = await model({ system: SUBJECTS_SYSTEM_PROMPT, messages, schema: SUBJECTS_SCHEMA, signal, task: 'subjects' });\n } catch (error) {\n if (error instanceof EmailGenerationError && error.code === 'REFUSED') throw new EmailGenerationError('SUBJECTS_REFUSED');\n throw error;\n }\n const { usable, problems } = reviewSubjects(parseSubjects(answer), request);\n if (usable.length > best.length) best = usable;\n if (!problems.length) return { subjects: usable, attempts: attempt };\n messages.push(\n { role: 'assistant', content: String(answer) },\n { role: 'user', content: `Fix these problems and answer the three subjects again:\\n${problems.map((p) => `- ${p}`).join('\\n')}` }\n );\n }\n // Fewer than three good ones is still worth showing.\n if (best.length) return { subjects: best, attempts: Math.max(1, maxAttempts) };\n throw new EmailGenerationError('SUBJECTS_UNUSABLE');\n };\n}\n","// @ts-check\n/**\n * EMAIL GENERATOR — from a purpose (and optional details) to an email the\n * builder opens block by block.\n *\n * const generator = createEmailGenerator({ model: claudeEmailModel({ client: new Anthropic() }) });\n * const { name, subject, preheader, mjml } = await generator.generate({ purpose: 'password-reset', language: 'fr' });\n *\n * The MODEL is any function `({system, messages, schema, signal}) => text`:\n * `claudeEmailModel` (claude.js) for Claude, or a few lines of your own for\n * another provider. Its answer is read, cleaned (`draft.js`) and checked; when\n * a problem remains, the model gets ONE more turn with the list of problems\n * (`maxAttempts`, 2 by default) — a slow model call is the cost of a retry.\n *\n * Never trusted as is: the MJML that comes out has passed `validateMjml`, the\n * same check the server runs before compiling and saving.\n *\n * The same model also REWRITES one text of an email (`rewrite`, see\n * rewrite.js): shorter, longer, fixed, another tone, translated; and SUGGESTS\n * three other subject lines (`suggestSubjects`, see subjects.js).\n */\n\nimport { DEFAULT_VARIABLES } from '../variables/catalog.js';\nimport { isSafeUrl } from '../../validation/urls.js';\nimport { EmailGenerationError, normalizeGeneratedMjml, parseDraft, reviewDraft } from './draft.js';\nimport { applyBrand, cleanBrand } from '../brand/brand.js';\nimport { EMAIL_DRAFT_SCHEMA, SYSTEM_PROMPT, buildRepairPrompt, buildUserPrompt } from './prompt.js';\nimport { purposeById } from './purposes.js';\nimport { createRewriter } from './rewrite.js';\nimport { createSubjectSuggester } from './subjects.js';\n\n/**\n * @typedef {{role: 'user'|'assistant', content: string}} ModelMessage\n * @typedef {(request: {system: string, messages: ModelMessage[], schema: object, signal?: AbortSignal, task?: 'generate'|'rewrite'|'subjects'}) => Promise<string>} EmailModel\n * `task`: a whole email, one text rewritten or subject ideas (lighter jobs:\n * a model may answer them with less effort).\n *\n * @typedef {{\n * purpose?: string|null,\n * details?: string,\n * language?: string,\n * sender?: {name?: string, address?: string, logoUrl?: string},\n * brand?: {primary?: string, heading?: string, font?: string},\n * variables?: {key: string, label?: string}[],\n * }} GenerateInput\n * `brand`: the colors and font the email is written in (features/brand).\n *\n * @typedef {import('./draft.js').EmailDraft & {warnings: string[], attempts: number}} GeneratedEmail\n */\n\nexport const MAX_DETAILS_LENGTH = 2000;\nconst MAX_VARIABLES = 200;\n\n/** @param {unknown} value @param {number} max */\nconst text = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '');\n\n/**\n * The input as the generator uses it: bounded strings, a known purpose or\n * none, an https logo or none, the brand's colors and font or none.\n * @param {GenerateInput} input\n */\nexport function cleanGenerateInput(input) {\n const source = input && typeof input === 'object' ? input : {};\n const purpose = purposeById(typeof source.purpose === 'string' ? source.purpose : null);\n const details = text(source.details, MAX_DETAILS_LENGTH);\n const language = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(String(source.language || '')) ? String(source.language) : 'en';\n const sender = source.sender && typeof source.sender === 'object' ? source.sender : {};\n const logoUrl = text(sender.logoUrl, 2000);\n const variables = (Array.isArray(source.variables) ? source.variables : DEFAULT_VARIABLES)\n .filter((variable) => variable && /^[A-Za-z][\\w.]*$/.test(String(variable.key)))\n .slice(0, MAX_VARIABLES)\n .map((variable) => ({ key: String(variable.key), label: text(variable.label, 80) }));\n const { primary, heading, font } = cleanBrand(source.brand);\n return {\n purpose,\n details,\n language,\n sender: {\n name: text(sender.name, 120),\n address: text(sender.address, 300),\n logoUrl: logoUrl && /^https:\\/\\//i.test(logoUrl) && isSafeUrl(logoUrl, 'image') ? logoUrl : '',\n },\n brand: { ...(primary ? { primary } : {}), ...(heading ? { heading } : {}), ...(font ? { font } : {}) },\n variables,\n };\n}\n\n/**\n * @param {{\n * model: EmailModel,\n * maxAttempts?: number,\n * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,\n * }} options\n * `compile`: compiles the MJML and returns MJML's own warnings, fed back to\n * the model (the server passes `compileMjml`).\n */\nexport function createEmailGenerator({ model, maxAttempts = 2, compile } = /** @type {any} */ ({})) {\n if (typeof model !== 'function') throw new Error('createEmailGenerator: `model` must be a function');\n\n /**\n * @param {GenerateInput} input\n * @param {{signal?: AbortSignal}} [options]\n * @returns {Promise<GeneratedEmail>}\n */\n async function generate(input, { signal } = {}) {\n const request = cleanGenerateInput(input);\n if (!request.purpose && !request.details) {\n throw new EmailGenerationError('NOTHING_TO_GENERATE');\n }\n const kind = request.purpose?.kind || 'marketing';\n const keepImages = request.sender.logoUrl ? [request.sender.logoUrl] : [];\n\n /** @type {ModelMessage[]} */\n const messages = [{ role: 'user', content: buildUserPrompt(request) }];\n /** @type {GeneratedEmail|null} */\n let usable = null;\n let lastProblem = '';\n\n for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {\n signal?.throwIfAborted();\n const answer = await model({ system: SYSTEM_PROMPT, messages, schema: EMAIL_DRAFT_SCHEMA, signal, task: 'generate' });\n /** @type {string[]} */\n let problems;\n try {\n const draft = parseDraft(answer);\n draft.mjml = normalizeGeneratedMjml(draft.mjml, { variables: request.variables, keepImages });\n // The model was told the brand; whatever it chose instead is repainted.\n draft.mjml = applyBrand(draft.mjml, request.brand);\n const compileWarnings = compile ? (await compile(draft.mjml)).warnings || [] : [];\n const { blocking, advice } = reviewDraft(draft, { kind, compileWarnings });\n if (!blocking.length) {\n usable = { ...draft, warnings: advice, attempts: attempt };\n if (!advice.length) return usable;\n }\n problems = [...blocking, ...advice];\n } catch (error) {\n if (!(error instanceof EmailGenerationError)) throw error;\n problems = [error.detail];\n }\n lastProblem = problems[0];\n messages.push({ role: 'assistant', content: String(answer) }, { role: 'user', content: buildRepairPrompt(problems) });\n }\n\n if (usable) return usable;\n throw new EmailGenerationError('INVALID_OUTPUT', lastProblem);\n }\n\n return {\n generate,\n rewrite: createRewriter({ model, maxAttempts }),\n suggestSubjects: createSubjectSuggester({ model, maxAttempts }),\n };\n}\n","// @ts-check\n/**\n * BRAND FROM A WEBSITE — what a company's home page says about its look:\n * its name, where its logo is, its main color, the color of its headings\n * and its font.\n *\n * Pure text work on what the server downloaded (src/server/brandImport.js\n * does the downloading, with its safety rules): no network, no DOM, so it\n * runs anywhere and is tested on fixtures.\n *\n * name og:site_name → JSON-LD Organization → application-name → the\n * part of <title> that matches the domain → the domain;\n * logo JSON-LD Organization logo → an <img> called \"logo\" (header\n * first) → apple-touch-icon; SVG and WebP are skipped later\n * (most email apps do not show them);\n * colors counted in the CSS, weighted by where they are used (buttons,\n * links, custom properties named primary/brand/accent, the\n * theme-color meta); grays, alert and social colors set aside;\n * font the body's, mapped to the nearest web-safe stack.\n */\n\nimport { FONT_OPTIONS, PALETTE } from '../../blocks/shared.js';\nimport { contrastRatio, parseColor, toHex, toHsl } from './brand.js';\n\n/**\n * @typedef {{\n * name: string,\n * logos: {url: string, from: 'schema'|'img'|'icon'}[],\n * stylesheets: string[],\n * css: string,\n * themeColor: string,\n * }} PageReading\n */\n\n/* ── HTML ───────────────────────────────────────────────────────────────── */\n\n/** @param {string} text */\nfunction decodeEntities(text) {\n return String(text ?? '')\n .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))\n .replace(/&#(\\d+);/g, (_, dec) => String.fromCodePoint(Number(dec)))\n .replace(/"/g, '\"')\n .replace(/'|'/g, \"'\")\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/ /g, ' ')\n .replace(/&/g, '&');\n}\n\n/**\n * The attributes of one start tag, names in lower case.\n * @param {string} tag `<img src=\"…\" class=logo>`\n * @returns {Record<string, string>}\n */\nexport function tagAttributes(tag) {\n /** @type {Record<string, string>} */\n const attributes = {};\n const inner = tag.replace(/^<[^\\s>/]+/, '').replace(/\\/?>$/, '');\n const pattern = /([^\\s\"'=<>/]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n let match;\n while ((match = pattern.exec(inner))) {\n const name = match[1].toLowerCase();\n if (!(name in attributes)) attributes[name] = decodeEntities(match[2] ?? match[3] ?? match[4] ?? '');\n }\n return attributes;\n}\n\n/** @param {string} html @param {string} name */\nconst startTags = (html, name) => html.match(new RegExp(`<${name}\\\\b[^>]*>`, 'gi')) || [];\n\n/** An http(s) address, resolved against the page; '' otherwise. @param {string|undefined} href @param {string} base */\nfunction resolve(href, base) {\n const value = String(href ?? '').trim();\n if (!value || /^(data|javascript|blob):/i.test(value)) return '';\n try {\n const url = new URL(value, base);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';\n } catch {\n return '';\n }\n}\n\n/** The largest image of a srcset (\"a.png 1x, a@2x.png 2x\"). @param {string} srcset */\nfunction largestOf(srcset) {\n let best = { url: '', size: 0 };\n for (const part of srcset.split(/,\\s+/)) {\n const [url, descriptor = '1x'] = part.trim().split(/\\s+/);\n const size = parseFloat(descriptor) * (descriptor.endsWith('w') ? 1 : 1000);\n if (url && size > best.size) best = { url, size };\n }\n return best.url;\n}\n\n/** Objects of a JSON-LD script, flattened (@graph, arrays). @param {unknown} value @returns {any[]} */\nfunction jsonLdNodes(value) {\n if (Array.isArray(value)) return value.flatMap(jsonLdNodes);\n if (!value || typeof value !== 'object') return [];\n const node = /** @type {any} */ (value);\n return [node, ...jsonLdNodes(node['@graph']), ...jsonLdNodes(node.publisher), ...jsonLdNodes(node.brand)];\n}\n\nconst ORGANIZATION = /organization|corporation|business|brand|store|restaurant|company|ngo|school|airline|bank|agency/i;\n\n/** The words of a host name that name the company: \"www.acme-corp.co.uk\" → \"acme corp\". @param {string} host */\nfunction hostWords(host) {\n const labels = host.toLowerCase().replace(/^www\\d?\\./, '').split('.');\n const main = labels.length > 2 && labels[labels.length - 2].length <= 3 ? labels[labels.length - 3] : labels[0];\n return main.replace(/[-_]+/g, ' ');\n}\n\n/** @param {string} text */\nconst squash = (text) => text.toLowerCase().replace(/[^a-z0-9À-]+/g, '');\n\n/** @param {unknown} value */\nconst cleanName = (value) =>\n decodeEntities(String(value ?? ''))\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 80);\n\n/**\n * What a home page says about its brand.\n * @param {string} html\n * @param {string} pageUrl the address it was read from (after redirects)\n * @returns {PageReading}\n */\nexport function readPage(html, pageUrl) {\n const source = String(html ?? '');\n const head = source.slice(0, 400_000);\n const baseTag = startTags(head, 'base').map(tagAttributes).find((a) => a.href);\n const base = resolve(baseTag?.href, pageUrl) || pageUrl;\n const host = (() => {\n try {\n return new URL(pageUrl).hostname;\n } catch {\n return '';\n }\n })();\n\n /** @type {Record<string, string>} */\n const meta = {};\n for (const attributes of startTags(head, 'meta').map(tagAttributes)) {\n const key = (attributes.property || attributes.name || '').toLowerCase();\n if (key && attributes.content && !(key in meta)) meta[key] = attributes.content;\n }\n\n // JSON-LD: the organization's own name and logo, when the site states them.\n let schemaName = '';\n let schemaLogo = '';\n for (const [, body] of source.matchAll(/<script\\b[^>]*type\\s*=\\s*[\"']?application\\/ld\\+json[^>]*>([\\s\\S]*?)<\\/script>/gi)) {\n let data;\n try {\n data = JSON.parse(body.trim());\n } catch {\n continue;\n }\n for (const node of jsonLdNodes(data)) {\n const types = [].concat(node['@type'] || []).join(' ');\n if (!ORGANIZATION.test(types)) continue;\n if (!schemaName && typeof node.name === 'string') schemaName = node.name;\n const logo = typeof node.logo === 'string' ? node.logo : node.logo?.url || node.logo?.contentUrl || '';\n if (!schemaLogo && typeof logo === 'string') schemaLogo = resolve(logo, base);\n }\n }\n\n /* Name */\n const title = cleanName(head.match(/<title\\b[^>]*>([\\s\\S]*?)<\\/title>/i)?.[1] || '');\n const words = hostWords(host);\n const fromTitle =\n title\n .split(/\\s+[|–—:·•»-]\\s+/)\n .map((part) => part.trim())\n .find((part) => part && squash(part).length <= 40 && (squash(part).includes(squash(words)) || squash(words).includes(squash(part)))) || '';\n const fromHost = words.replace(/\\b\\p{L}/gu, (letter) => letter.toUpperCase());\n const name = cleanName(meta['og:site_name'] || schemaName || meta['application-name'] || fromTitle || fromHost);\n\n /* Logo candidates, best first */\n /** @type {PageReading['logos']} */\n const logos = [];\n const add = (/** @type {string} */ url, /** @type {PageReading['logos'][number]['from']} */ from) => {\n if (url && !logos.some((logo) => logo.url === url)) logos.push({ url, from });\n };\n add(schemaLogo, 'schema');\n /** @type {{url: string, score: number}[]} */\n const images = [];\n const headerEnd = (() => {\n const at = source.search(/<\\/header>/i);\n return at > 0 ? at : Math.min(source.length, 20_000);\n })();\n for (const match of source.matchAll(/<img\\b[^>]*>/gi)) {\n const attributes = tagAttributes(match[0]);\n const own = [attributes.class, attributes.id, attributes.alt, attributes.src, attributes['data-src']].join(' ').toLowerCase();\n const before = source.slice(Math.max(0, (match.index ?? 0) - 400), match.index).toLowerCase();\n const parent = /<(a|div|span|figure|picture)\\b[^>]*(class|id)\\s*=\\s*[\"'][^\"']*logo[^\"']*[\"'][^>]*>[^<]*(<source\\b[^>]*>\\s*)*$/i.test(before);\n if (!own.includes('logo') && !parent) continue;\n // Partner, client and payment logos are someone else's.\n if (/partner|client|customer|sponsor|award|payment|badge|trust|press|app-?store|google-?play/.test(own)) continue;\n const url = resolve(attributes.srcset ? largestOf(attributes.srcset) : attributes.src || attributes['data-src'] || '', base);\n if (!url) continue;\n let score = own.includes('logo') ? 3 : 2;\n if ((match.index ?? 0) < headerEnd) score += 3;\n if (name && squash(attributes.alt || '').includes(squash(name))) score += 2;\n if (/footer/.test(own)) score -= 2;\n images.push({ url, score: score - images.length * 0.01 });\n }\n images.sort((a, b) => b.score - a.score).forEach((image) => add(image.url, 'img'));\n /** @type {{url: string, size: number}[]} */\n const icons = [];\n for (const attributes of startTags(head, 'link').map(tagAttributes)) {\n const rel = (attributes.rel || '').toLowerCase();\n if (!/apple-touch-icon|icon/.test(rel)) continue;\n const size = parseInt(attributes.sizes || (rel.includes('apple') ? '180' : '0'), 10) || 0;\n if (size >= 96) icons.push({ url: resolve(attributes.href, base), size });\n }\n icons.sort((a, b) => b.size - a.size).forEach((icon) => add(icon.url, 'icon'));\n\n /* Styles */\n const stylesheets = startTags(head, 'link')\n .map(tagAttributes)\n .filter((a) => /(^|\\s)stylesheet(\\s|$)/i.test(a.rel || '') && !/^print$/i.test((a.media || '').trim()))\n .map((a) => resolve(a.href, base))\n .filter(Boolean)\n // Fonts and third-party widgets say nothing about the brand's colors.\n .filter((href) => !/fonts\\.googleapis|use\\.typekit|fontawesome|cookie|consent|onetrust/i.test(href));\n const css = [\n ...[...source.matchAll(/<style\\b[^>]*>([\\s\\S]*?)<\\/style>/gi)].map((m) => m[1]),\n // Inline styles of buttons and links count too: `<a class=\"btn\" style=\"…\">`.\n ...[...source.matchAll(/<(a|button)\\b[^>]*>/gi)]\n .map((m) => tagAttributes(m[0]))\n .filter((a) => a.style)\n .map((a) => `${/btn|button|cta/i.test(a.class || '') ? '.btn' : 'a'}{${a.style}}`),\n ].join('\\n');\n\n return {\n name,\n logos,\n stylesheets: [...new Set(stylesheets)],\n css,\n themeColor: meta['theme-color'] || meta['msapplication-tilecolor'] || '',\n };\n}\n\n/* ── CSS ────────────────────────────────────────────────────────────────── */\n\nconst PRIMARY_NAME = /primary|brand|accent|main|theme|highlight|cta/i;\nconst NOT_A_BRAND_NAME = /text|foreground|fg|border|muted|gr[ae]y|neutral|surface|shadow|overlay|success|error|danger|warning|info|disabled|placeholder/i;\nconst NOISE = /error|danger|success|warning|alert|invalid|valid\\b|notice|toast|badge|disabled|facebook|twitter|linkedin|instagram|youtube|whatsapp|pinterest|tiktok|social|cookie|consent|onetrust|ot-|cmp|gdpr|wp-block|has-[\\w-]+-(background-)?color|syntax|hljs|code/i;\nconst COLOR_IN_VALUE = /#[0-9a-f]{3,8}\\b|rgba?\\([^)]*\\)|hsla?\\([^)]*\\)/gi;\nconst COLOR_PROPERTY = /^(color|background|background-color|border|border-color|border-(top|bottom|left|right)(-color)?|fill|stroke|outline(-color)?|text-decoration-color|accent-color|caret-color)$/;\n\n/** @param {string} css */\nconst stripComments = (css) => css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n/** Rules as [selector, declarations], @media flattened. @param {string} css */\nfunction rulesOf(css) {\n /** @type {[string, [string, string][]][]} */\n const rules = [];\n for (const [, selector, body] of stripComments(css).matchAll(/([^{}]+)\\{([^{}]*)\\}/g)) {\n const declarations = body\n .split(';')\n .map((part) => {\n const colon = part.indexOf(':');\n return /** @type {[string, string]} */ ([part.slice(0, colon).trim().toLowerCase(), part.slice(colon + 1).trim()]);\n })\n .filter(([property, value]) => property && value);\n if (declarations.length) rules.push([selector.trim(), declarations]);\n }\n return rules;\n}\n\n/**\n * A custom property's value as a color: `#2977bc`, `rgb(…)`, and the bare\n * channels design systems keep there (`41 119 188`, `210 64% 45%`).\n * @param {string} value\n */\nfunction customColor(value) {\n const direct = parseColor(value.replace(/\\s*!important$/, ''));\n if (direct) return direct;\n if (/^\\d{1,3}(\\.\\d+)?\\s+\\d{1,3}(\\.\\d+)?\\s+\\d{1,3}(\\.\\d+)?$/.test(value)) return parseColor(`rgb(${value})`);\n if (/^\\d{1,3}(\\.\\d+)?(deg)?\\s+\\d{1,3}(\\.\\d+)?%\\s+\\d{1,3}(\\.\\d+)?%$/.test(value)) return parseColor(`hsl(${value.replace('deg', '')})`);\n return null;\n}\n\n/**\n * `var(--x, fallback)` replaced by what --x holds, a few levels deep.\n * @param {string} value @param {Map<string, string>} custom\n */\nfunction resolveVars(value, custom) {\n let out = value;\n for (let depth = 0; depth < 4 && out.includes('var('); depth += 1) {\n out = out.replace(/var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^()]*(?:\\([^()]*\\))?[^()]*))?\\)/g, (_, name, fallback) => custom.get(name) ?? fallback ?? '');\n }\n return out;\n}\n\n/** @param {{r: number, g: number, b: number}} a @param {{r: number, g: number, b: number}} b */\nconst distance = (a, b) => Math.hypot(a.r - b.r, a.g - b.g, a.b - b.b);\n\n/** Colors of one value, as found (not resolved through var()). @param {string} value */\nconst colorsIn = (value) => (value.match(COLOR_IN_VALUE) || []).map((text) => parseColor(text)).filter((c) => c && c.a >= 0.6);\n\n/**\n * The web-safe stack nearest to a CSS font-family.\n * @param {string} family\n * @returns {string|undefined}\n */\nexport function nearestFont(family) {\n const stack = String(family ?? '')\n .toLowerCase()\n .replace(/[\"']/g, '');\n if (!stack.trim() || /^(inherit|initial|unset|revert)$/.test(stack.trim())) return undefined;\n const first = stack.split(',')[0].trim();\n const known = FONT_OPTIONS.find((option) => first.startsWith(option.label.toLowerCase()));\n if (known) return known.value;\n if (/^helvetica/.test(first)) return FONT_OPTIONS[0].value;\n if (/^times/.test(first)) return FONT_OPTIONS.find((o) => o.label === 'Times')?.value;\n if (/monospace|mono\\b|courier|consolas/.test(stack)) return FONT_OPTIONS.find((o) => o.label === 'Courier')?.value;\n // Web fonts: their family decides (serif, else sans-serif).\n if (/(^|,)\\s*serif\\s*(,|$)|garamond|playfair|merriweather|lora|baskerville|caslon|didot|bodoni|crimson|libre baskerville|source serif|noto serif|pt serif/.test(stack)) {\n return FONT_OPTIONS.find((o) => o.label === 'Georgia')?.value;\n }\n return FONT_OPTIONS[0].value;\n}\n\n/**\n * Main color, headings color and font of a site, from its CSS.\n * @param {{css: string, themeColor?: string}} input\n * @returns {{primary?: string, heading?: string, font?: string}}\n */\nexport function brandFromCss({ css, themeColor = '' }) {\n const rules = rulesOf(String(css ?? ''));\n\n /** @type {Map<string, string>} */\n const custom = new Map();\n for (const [selector, declarations] of rules) {\n // :root first: a theme's dark variant must not replace it.\n const root = /^(:root|html|body|\\*)(\\s*,|$)/i.test(selector);\n for (const [property, value] of declarations) {\n if (property.startsWith('--') && (root || !custom.has(property))) custom.set(property, value.replace(/\\s*!important$/, ''));\n }\n }\n\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const found = [];\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const darkText = [];\n /** @type {{font: string, weight: number}[]} */\n const fonts = [];\n const count = (/** @type {{r: number, g: number, b: number}} */ color, /** @type {number} */ weight) => found.push({ color, weight });\n\n for (const [selector, declarations] of rules) {\n if (selector.startsWith('@')) continue;\n const noise = NOISE.test(selector);\n const emphasis = /btn|button|cta|primary|brand|accent|hero|highlight/i.test(selector) ? 4 : /(^|[\\s,>+~])a([:.[\\s,]|$)|link/i.test(selector) ? 2 : 1;\n const isHeading = /(^|[\\s,>])h[1-3]\\b|title|heading|headline/i.test(selector);\n const isBody = /^(html|body|:root)(\\s*,|$)|(^|,)\\s*(html|body)\\s*(,|$)/i.test(selector);\n for (const [property, raw] of declarations) {\n if (property.startsWith('--')) {\n const color = customColor(resolveVars(raw, custom));\n if (color && color.a >= 0.6 && PRIMARY_NAME.test(property) && !NOT_A_BRAND_NAME.test(property)) count(color, 10);\n continue;\n }\n const value = resolveVars(raw, custom);\n if (property === 'font-family' || property === 'font') {\n const family = property === 'font' ? value.replace(/^.*?\\d[\\w%.]*(\\/[\\w%.]+)?\\s+/, '') : value;\n if (isBody || isHeading) fonts.push({ font: family, weight: isBody ? 2 : 1 });\n continue;\n }\n if (!COLOR_PROPERTY.test(property) || noise) continue;\n for (const color of colorsIn(value)) {\n if (!color) continue;\n count(color, emphasis * (property.startsWith('background') ? 1.5 : 1));\n if (property === 'color' && (isHeading || isBody)) darkText.push({ color, weight: isHeading ? 2 : 1 });\n }\n }\n }\n const theme = parseColor(themeColor);\n if (theme && theme.a >= 0.6) count(theme, 15);\n\n // Near colors are one color (hover shades, rounding).\n /** @type {{color: {r: number, g: number, b: number}, weight: number}[]} */\n const clusters = [];\n for (const entry of found) {\n const near = clusters.find((cluster) => distance(cluster.color, entry.color) < 24);\n if (near) near.weight += entry.weight;\n else clusters.push({ ...entry });\n }\n clusters.sort((a, b) => b.weight - a.weight);\n const primary = clusters.find(({ color }) => {\n const { s, l } = toHsl(color);\n return s >= 0.25 && l >= 0.15 && l <= 0.85;\n });\n\n /** @type {{primary?: string, heading?: string, font?: string}} */\n const result = {};\n if (primary) result.primary = toHex(primary.color);\n\n const dark = darkText\n .filter(({ color }) => toHsl(color).l < 0.3)\n .reduce((/** @type {Map<string, number>} */ map, { color, weight }) => map.set(toHex(color), (map.get(toHex(color)) || 0) + weight), new Map());\n const heading = [...dark.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];\n if (heading) result.heading = heading;\n else if (result.primary && contrastRatio(result.primary, PALETTE.white) >= 7) result.heading = result.primary;\n else result.heading = PALETTE.ink;\n\n const font = fonts.sort((a, b) => b.weight - a.weight).map(({ font }) => nearestFont(font)).find(Boolean);\n if (font) result.font = font;\n return result;\n}\n\n/* ── Images ─────────────────────────────────────────────────────────────── */\n\n/**\n * Width and height of a PNG, GIF or JPEG, from its header; null otherwise.\n * @param {Uint8Array} bytes\n * @returns {{width: number, height: number}|null}\n */\nexport function imageSize(bytes) {\n if (!bytes || bytes.length < 24) return null;\n const at = (/** @type {number} */ i) => bytes[i];\n // PNG: IHDR right after the signature.\n if (at(0) === 0x89 && at(1) === 0x50 && at(2) === 0x4e && at(3) === 0x47) {\n const read32 = (/** @type {number} */ i) => ((at(i) << 24) | (at(i + 1) << 16) | (at(i + 2) << 8) | at(i + 3)) >>> 0;\n return { width: read32(16), height: read32(20) };\n }\n // GIF: logical screen size.\n if (at(0) === 0x47 && at(1) === 0x49 && at(2) === 0x46) {\n return { width: at(6) | (at(7) << 8), height: at(8) | (at(9) << 8) };\n }\n // JPEG: the first start-of-frame marker.\n if (at(0) === 0xff && at(1) === 0xd8) {\n let i = 2;\n while (i + 9 < bytes.length) {\n if (at(i) !== 0xff) {\n i += 1;\n continue;\n }\n const marker = at(i + 1);\n if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0xff) {\n i += marker === 0xff ? 1 : 2;\n continue;\n }\n const length = (at(i + 2) << 8) | at(i + 3);\n if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {\n return { height: (at(i + 5) << 8) | at(i + 6), width: (at(i + 7) << 8) | at(i + 8) };\n }\n i += 2 + length;\n }\n }\n return null;\n}\n\n/**\n * The width a logo is shown at in an email: a wordmark wider, a square mark\n * smaller; never wider than the image itself (a blurry logo is worse than a\n * small one). Retina images (twice the size shown) are common: halved first.\n * @param {{width: number, height: number}} size\n */\nexport function logoWidthFor({ width, height }) {\n if (!width || !height) return 140;\n const ratio = width / height;\n const shown = width >= 240 ? width / 2 : width;\n const target = ratio >= 2.5 ? 180 : ratio >= 1.3 ? 140 : 72;\n return Math.max(24, Math.round(Math.min(target, shown)));\n}\n","// @ts-check\n/**\n * What the user is told about reading a brand from a website: why it failed\n * (the server's answer) and what it could not find (notes under the field).\n * English source strings, translated where they are shown.\n */\n\n/**\n * Why a website could not be read: [HTTP status, message], by\n * BrandImportError code (src/server/brandImport.js).\n * @type {Record<string, [number, string]>}\n */\nexport const BRAND_IMPORT_ERRORS = {\n BAD_URL: [400, 'Enter the address of a website, like acme.com'],\n PRIVATE_ADDRESS: [400, 'This address is not a public website'],\n UNREACHABLE: [422, 'The website could not be reached. Check the address.'],\n TIMEOUT: [422, 'The website took too long to answer'],\n HTTP_ERROR: [422, 'The website does not let the builder read it. Fill in your brand below.'],\n NOT_HTML: [422, 'This address is not a web page'],\n TOO_MANY_REDIRECTS: [422, 'The website redirects too many times'],\n TOO_LARGE: [422, 'The website sent too much data'],\n};\n\n/** What could not be read, by note code. @type {Record<string, string>} */\nexport const BRAND_NOTES = {\n LOGO_NOT_FOUND: 'No logo was found on the website. Upload it below.',\n LOGO_UNSUPPORTED: 'The logo on the website is an SVG or WebP image, which most email apps do not show. Upload a PNG or JPEG version.',\n LOGO_ICON: 'The website icon is used as the logo. Upload your full logo if you have one.',\n COLORS_NOT_FOUND: 'No main color was found on the website. Pick one below.',\n};\n"],"mappings":"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","names":["text","purpose","text","bounded","text","MAX_VARIABLES","purpose","decodeEntities","text","font"]}
|