@at-flux/astroflare 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +98 -14
  3. package/dist/chunk-pbuEa-1d.js +13 -0
  4. package/dist/core.cjs +162 -138
  5. package/dist/core.d.cts +59 -2
  6. package/dist/core.d.ts +59 -2
  7. package/dist/core.js +147 -16
  8. package/dist/forms/index.cjs +7 -130
  9. package/dist/forms/index.d.cts +2 -52
  10. package/dist/forms/index.d.ts +2 -52
  11. package/dist/forms/index.js +99 -14
  12. package/dist/{chunk-DRYHJSYC.js → forms-8uIdWmMD.cjs} +81 -53
  13. package/dist/index-Bk-K9aDQ.d.ts +44 -0
  14. package/dist/index-CcH_yXr-.d.cts +44 -0
  15. package/dist/index.cjs +20 -141
  16. package/dist/index.d.cts +3 -2
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.js +3 -17
  19. package/package.json +14 -8
  20. package/src/components/CollectionQuery.astro +107 -0
  21. package/src/components/ContactModalCta.astro +2 -0
  22. package/src/components/FilterPills.astro +164 -0
  23. package/src/components/IconButton.astro +5 -0
  24. package/src/components/InstagramProfileLink.astro +3 -0
  25. package/src/components/ListSummary.astro +63 -0
  26. package/src/components/MediaProtect.astro +41 -0
  27. package/src/components/Modal.astro +3 -0
  28. package/src/components/ModalTrigger.astro +2 -0
  29. package/src/components/Pager.astro +92 -0
  30. package/src/components/Section.astro +3 -0
  31. package/src/components/TagSummary.astro +71 -0
  32. package/src/components/ThemeToggle.astro +1 -0
  33. package/src/components/Tooltip.astro +70 -0
  34. package/src/components/collection-query-props.ts +45 -0
  35. package/src/runtime/collection-query.ts +156 -0
  36. package/src/runtime/media-protect.ts +70 -0
  37. package/src/styles/accessibility.css +1 -1
  38. package/src/styles/no-save.css +2 -2
  39. package/src/styles/prose.css +23 -11
  40. package/src/styles/scrollbar.css +10 -3
  41. package/dist/chunk-ALMPH3A2.js +0 -0
package/dist/core.js CHANGED
@@ -1,17 +1,148 @@
1
- import "./chunk-ALMPH3A2.js";
2
- import {
3
- composeEmailAddress,
4
- forms_exports,
5
- generateFormResultHtml,
6
- generateFormSectionHtml,
7
- renderEmailTemplate,
8
- sendEmail
9
- } from "./chunk-DRYHJSYC.js";
10
- export {
11
- composeEmailAddress,
12
- forms_exports as forms,
13
- generateFormResultHtml,
14
- generateFormSectionHtml,
15
- renderEmailTemplate,
16
- sendEmail
1
+ import { composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail, t as forms_exports } from "./forms/index.js";
2
+ //#region src/tag-colors.ts
3
+ const DEFAULT_OVERRIDES = {
4
+ shoots: "#dc3545",
5
+ tech: "#8a95a5",
6
+ ai: "#6b7b8d",
7
+ sites: "#7dd3c0",
8
+ flow: "#b367e0"
17
9
  };
10
+ const hashStringToHue = (input) => {
11
+ let hash = 0;
12
+ for (let i = 0; i < input.length; i += 1) hash = hash * 31 + input.charCodeAt(i) >>> 0;
13
+ return hash % 360;
14
+ };
15
+ const hexToRgb = (hex) => {
16
+ const value = hex.replace("#", "").trim();
17
+ if (!/^[0-9a-fA-F]{6}$/.test(value)) return null;
18
+ return [
19
+ Number.parseInt(value.slice(0, 2), 16),
20
+ Number.parseInt(value.slice(2, 4), 16),
21
+ Number.parseInt(value.slice(4, 6), 16)
22
+ ];
23
+ };
24
+ const rgbToHsl = (r, g, b) => {
25
+ const red = r / 255;
26
+ const green = g / 255;
27
+ const blue = b / 255;
28
+ const max = Math.max(red, green, blue);
29
+ const min = Math.min(red, green, blue);
30
+ const delta = max - min;
31
+ const lightness = (max + min) / 2;
32
+ const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
33
+ let hue = 0;
34
+ if (delta !== 0) if (max === red) hue = (green - blue) / delta % 6;
35
+ else if (max === green) hue = (blue - red) / delta + 2;
36
+ else hue = (red - green) / delta + 4;
37
+ return [
38
+ Math.round((hue * 60 + 360) % 360),
39
+ Math.round(saturation * 100),
40
+ Math.round(lightness * 100)
41
+ ];
42
+ };
43
+ const resolveHue = (tag, options) => {
44
+ const key = tag.trim().toLowerCase();
45
+ const color = options.overrides?.[key] ?? DEFAULT_OVERRIDES[key];
46
+ if (!color) return hashStringToHue(key);
47
+ const rgb = hexToRgb(color);
48
+ if (!rgb) return hashStringToHue(key);
49
+ return rgbToHsl(...rgb)[0];
50
+ };
51
+ /**
52
+ * Build a deterministic HSL-based palette for a tag label.
53
+ * Uses known defaults and optional overrides before falling back to hashed hue.
54
+ */
55
+ const getTagPalette = (tag, options = {}) => {
56
+ const hue = resolveHue(tag, options);
57
+ return {
58
+ bg: `hsla(${hue} 48% 58% / 0.12)`,
59
+ border: `hsla(${hue} 52% 58% / 0.34)`,
60
+ text: `hsl(${hue} 50% 66%)`
61
+ };
62
+ };
63
+ //#endregion
64
+ //#region src/date-format.ts
65
+ const DEFAULT_OPTIONS = {
66
+ year: "numeric",
67
+ month: "long",
68
+ day: "numeric"
69
+ };
70
+ /**
71
+ * Format dates consistently for cards and article metadata.
72
+ */
73
+ const formatDisplayDate = (date, config = {}) => {
74
+ const { locale = "en-GB", options = DEFAULT_OPTIONS } = config;
75
+ return new Intl.DateTimeFormat(locale, options).format(new Date(date));
76
+ };
77
+ //#endregion
78
+ //#region src/collection-query.ts
79
+ const toInt = (value, fallback) => {
80
+ const parsed = Number.parseInt(value ?? "", 10);
81
+ return Number.isFinite(parsed) ? parsed : fallback;
82
+ };
83
+ const parseCollectionQuery = (searchParams, options = {}) => {
84
+ const { defaultPage = 1, defaultSize = 12, maxSize = 100 } = options;
85
+ const page = Math.max(1, toInt(searchParams.get("page"), defaultPage));
86
+ const size = Math.min(maxSize, Math.max(1, toInt(searchParams.get("size"), defaultSize)));
87
+ const fallbackOffset = (page - 1) * size;
88
+ const offset = Math.max(0, toInt(searchParams.get("offset"), fallbackOffset));
89
+ const rawFilters = searchParams.get("filters");
90
+ let filters = {};
91
+ if (rawFilters) try {
92
+ const parsed = JSON.parse(rawFilters);
93
+ filters = Object.fromEntries(Object.entries(parsed).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, String(value)]));
94
+ } catch {
95
+ filters = {};
96
+ }
97
+ return {
98
+ page,
99
+ size,
100
+ offset,
101
+ filters
102
+ };
103
+ };
104
+ const paginateCollection = (list, query) => {
105
+ const totalItems = list.length;
106
+ const totalPages = Math.max(1, Math.ceil(totalItems / query.size));
107
+ const start = (Math.min(totalPages, Math.max(1, Math.floor(query.offset / query.size) + 1)) - 1) * query.size;
108
+ const end = start + query.size;
109
+ return {
110
+ items: list.slice(start, end),
111
+ totalItems,
112
+ totalPages,
113
+ start,
114
+ end: Math.min(end, totalItems)
115
+ };
116
+ };
117
+ const buildCollectionHref = (pathname, query, overrides = {}) => {
118
+ const next = {
119
+ ...query,
120
+ ...overrides
121
+ };
122
+ const normalizedOffset = (next.page - 1) * next.size;
123
+ const params = new URLSearchParams({
124
+ page: String(next.page),
125
+ size: String(next.size),
126
+ offset: String(next.offset ?? normalizedOffset)
127
+ });
128
+ if (Object.keys(next.filters ?? {}).length > 0) params.set("filters", JSON.stringify(next.filters));
129
+ return `${pathname}?${params.toString()}`;
130
+ };
131
+ const matchesCollectionFilters = (values, filters) => Object.entries(filters).every(([key, value]) => {
132
+ return (values[key] ?? []).includes(value);
133
+ });
134
+ const buildPageSequence = (totalPages, currentPage, maxButtons = 7) => {
135
+ if (totalPages <= maxButtons) return Array.from({ length: totalPages }, (_, index) => index + 1);
136
+ const innerSlots = Math.max(1, maxButtons - 2);
137
+ const left = Math.max(2, currentPage - Math.floor(innerSlots / 2));
138
+ const right = Math.min(totalPages - 1, left + innerSlots - 1);
139
+ const adjustedLeft = Math.max(2, right - innerSlots + 1);
140
+ const sequence = [1];
141
+ if (adjustedLeft > 2) sequence.push("…");
142
+ for (let page = adjustedLeft; page <= right; page += 1) sequence.push(page);
143
+ if (right < totalPages - 1) sequence.push("…");
144
+ sequence.push(totalPages);
145
+ return sequence;
146
+ };
147
+ //#endregion
148
+ export { buildCollectionHref, buildPageSequence, composeEmailAddress, formatDisplayDate, forms_exports as forms, generateFormResultHtml, generateFormSectionHtml, getTagPalette, matchesCollectionFilters, paginateCollection, parseCollectionQuery, renderEmailTemplate, sendEmail };
@@ -1,130 +1,7 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/forms/index.ts
21
- var forms_exports = {};
22
- __export(forms_exports, {
23
- composeEmailAddress: () => composeEmailAddress,
24
- generateFormResultHtml: () => generateFormResultHtml,
25
- generateFormSectionHtml: () => generateFormSectionHtml,
26
- renderEmailTemplate: () => renderEmailTemplate,
27
- sendEmail: () => sendEmail
28
- });
29
- module.exports = __toCommonJS(forms_exports);
30
- var import_resend = require("resend");
31
- async function sendEmail(config, payload) {
32
- if (config.mock) {
33
- console.info("[astroflare:forms] Mock email payload", payload);
34
- return { id: "mock-email-id" };
35
- }
36
- const resend = new import_resend.Resend(config.apiKey);
37
- const { data, error } = await resend.emails.send({
38
- from: payload.from,
39
- to: payload.to,
40
- subject: payload.subject,
41
- html: payload.html
42
- });
43
- if (error) {
44
- console.error("[astroflare:forms] Resend API error", error);
45
- throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
46
- }
47
- return data;
48
- }
49
- function composeEmailAddress(local, domain, tag) {
50
- const taggedLocal = tag ? `${local}+${tag}` : local;
51
- return `${taggedLocal}@${domain}`;
52
- }
53
- function generateFormSectionHtml(section) {
54
- const itemsHtml = section.items.map((item) => {
55
- const value = item.value ?? "";
56
- const isHtml = /<[^>]+>/.test(value);
57
- return `
58
- <div style="margin-bottom:16px;">
59
- <p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
60
- ${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
61
- </div>
62
- `;
63
- }).join("");
64
- return `
65
- <section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
66
- <h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
67
- <div style="color:#111827;">
68
- ${itemsHtml}
69
- </div>
70
- </section>
71
- `;
72
- }
73
- function generateFormResultHtml(sections) {
74
- return sections.map(generateFormSectionHtml).join("\n");
75
- }
76
- async function renderEmailTemplate(args) {
77
- const { title, contentHtml, brandName, footerText } = args;
78
- const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
79
- "en-GB"
80
- )}.`;
81
- return `<!DOCTYPE html>
82
- <html lang="en">
83
- <head>
84
- <meta charset="utf-8">
85
- <meta name="viewport" content="width=device-width, initial-scale=1">
86
- <title>${title}</title>
87
- <meta http-equiv="x-ua-compatible" content="ie=edge">
88
- </head>
89
- <body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
90
- <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
91
- <tr>
92
- <td align="center" style="padding:24px;">
93
- <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
94
- <tr>
95
- <td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
96
- <h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
97
- ${title}
98
- </h1>
99
- <p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
100
- Contact form submission
101
- </p>
102
- </td>
103
- </tr>
104
- <tr>
105
- <td style="padding:24px;">
106
- ${contentHtml}
107
- </td>
108
- </tr>
109
- <tr>
110
- <td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
111
- <p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
112
- ${footer}
113
- </p>
114
- </td>
115
- </tr>
116
- </table>
117
- </td>
118
- </tr>
119
- </table>
120
- </body>
121
- </html>`;
122
- }
123
- // Annotate the CommonJS export names for ESM import in node:
124
- 0 && (module.exports = {
125
- composeEmailAddress,
126
- generateFormResultHtml,
127
- generateFormSectionHtml,
128
- renderEmailTemplate,
129
- sendEmail
130
- });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_forms = require("../forms-8uIdWmMD.cjs");
3
+ exports.composeEmailAddress = require_forms.composeEmailAddress;
4
+ exports.generateFormResultHtml = require_forms.generateFormResultHtml;
5
+ exports.generateFormSectionHtml = require_forms.generateFormSectionHtml;
6
+ exports.renderEmailTemplate = require_forms.renderEmailTemplate;
7
+ exports.sendEmail = require_forms.sendEmail;
@@ -1,52 +1,2 @@
1
- import { CreateEmailResponseSuccess } from 'resend';
2
-
3
- interface ResendConfig {
4
- /**
5
- * Resend API key.
6
- */
7
- apiKey: string;
8
- /**
9
- * When true, do not send real email – log to console instead.
10
- * Useful for local/dev environments.
11
- */
12
- mock?: boolean;
13
- }
14
- interface EmailPayload {
15
- from: string;
16
- to: string;
17
- subject: string;
18
- html: string;
19
- }
20
- declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
21
- id: string;
22
- }>;
23
- declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
24
- interface FormSection {
25
- title: string;
26
- items: {
27
- key: string;
28
- value: string;
29
- }[];
30
- }
31
- declare function generateFormSectionHtml(section: FormSection): string;
32
- declare function generateFormResultHtml(sections: FormSection[]): string;
33
- declare function renderEmailTemplate(args: {
34
- title: string;
35
- contentHtml: string;
36
- brandName?: string;
37
- footerText?: string;
38
- }): Promise<string>;
39
-
40
- type index_EmailPayload = EmailPayload;
41
- type index_FormSection = FormSection;
42
- type index_ResendConfig = ResendConfig;
43
- declare const index_composeEmailAddress: typeof composeEmailAddress;
44
- declare const index_generateFormResultHtml: typeof generateFormResultHtml;
45
- declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
46
- declare const index_renderEmailTemplate: typeof renderEmailTemplate;
47
- declare const index_sendEmail: typeof sendEmail;
48
- declare namespace index {
49
- export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
50
- }
51
-
52
- export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
1
+ import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, t as EmailPayload } from "../index-CcH_yXr-.cjs";
2
+ export { EmailPayload, FormSection, ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail };
@@ -1,52 +1,2 @@
1
- import { CreateEmailResponseSuccess } from 'resend';
2
-
3
- interface ResendConfig {
4
- /**
5
- * Resend API key.
6
- */
7
- apiKey: string;
8
- /**
9
- * When true, do not send real email – log to console instead.
10
- * Useful for local/dev environments.
11
- */
12
- mock?: boolean;
13
- }
14
- interface EmailPayload {
15
- from: string;
16
- to: string;
17
- subject: string;
18
- html: string;
19
- }
20
- declare function sendEmail(config: ResendConfig, payload: EmailPayload): Promise<CreateEmailResponseSuccess | {
21
- id: string;
22
- }>;
23
- declare function composeEmailAddress(local: string, domain: string, tag?: string): string;
24
- interface FormSection {
25
- title: string;
26
- items: {
27
- key: string;
28
- value: string;
29
- }[];
30
- }
31
- declare function generateFormSectionHtml(section: FormSection): string;
32
- declare function generateFormResultHtml(sections: FormSection[]): string;
33
- declare function renderEmailTemplate(args: {
34
- title: string;
35
- contentHtml: string;
36
- brandName?: string;
37
- footerText?: string;
38
- }): Promise<string>;
39
-
40
- type index_EmailPayload = EmailPayload;
41
- type index_FormSection = FormSection;
42
- type index_ResendConfig = ResendConfig;
43
- declare const index_composeEmailAddress: typeof composeEmailAddress;
44
- declare const index_generateFormResultHtml: typeof generateFormResultHtml;
45
- declare const index_generateFormSectionHtml: typeof generateFormSectionHtml;
46
- declare const index_renderEmailTemplate: typeof renderEmailTemplate;
47
- declare const index_sendEmail: typeof sendEmail;
48
- declare namespace index {
49
- export { type index_EmailPayload as EmailPayload, type index_FormSection as FormSection, type index_ResendConfig as ResendConfig, index_composeEmailAddress as composeEmailAddress, index_generateFormResultHtml as generateFormResultHtml, index_generateFormSectionHtml as generateFormSectionHtml, index_renderEmailTemplate as renderEmailTemplate, index_sendEmail as sendEmail };
50
- }
51
-
52
- export { type EmailPayload, type FormSection, type ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, index as i, renderEmailTemplate, sendEmail };
1
+ import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, t as EmailPayload } from "../index-Bk-K9aDQ.js";
2
+ export { EmailPayload, FormSection, ResendConfig, composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail };
@@ -1,14 +1,99 @@
1
- import {
2
- composeEmailAddress,
3
- generateFormResultHtml,
4
- generateFormSectionHtml,
5
- renderEmailTemplate,
6
- sendEmail
7
- } from "../chunk-DRYHJSYC.js";
8
- export {
9
- composeEmailAddress,
10
- generateFormResultHtml,
11
- generateFormSectionHtml,
12
- renderEmailTemplate,
13
- sendEmail
14
- };
1
+ import { t as __exportAll } from "../chunk-pbuEa-1d.js";
2
+ import { Resend } from "resend";
3
+ //#region src/forms/index.ts
4
+ var forms_exports = /* @__PURE__ */ __exportAll({
5
+ composeEmailAddress: () => composeEmailAddress,
6
+ generateFormResultHtml: () => generateFormResultHtml,
7
+ generateFormSectionHtml: () => generateFormSectionHtml,
8
+ renderEmailTemplate: () => renderEmailTemplate,
9
+ sendEmail: () => sendEmail
10
+ });
11
+ async function sendEmail(config, payload) {
12
+ if (config.mock) {
13
+ console.info("[astroflare:forms] Mock email payload", payload);
14
+ return { id: "mock-email-id" };
15
+ }
16
+ const { data, error } = await new Resend(config.apiKey).emails.send({
17
+ from: payload.from,
18
+ to: payload.to,
19
+ subject: payload.subject,
20
+ html: payload.html
21
+ });
22
+ if (error) {
23
+ console.error("[astroflare:forms] Resend API error", error);
24
+ throw new Error(`Failed to send email: ${error.message ?? "Unknown error"}`);
25
+ }
26
+ return data;
27
+ }
28
+ function composeEmailAddress(local, domain, tag) {
29
+ return `${tag ? `${local}+${tag}` : local}@${domain}`;
30
+ }
31
+ function generateFormSectionHtml(section) {
32
+ const itemsHtml = section.items.map((item) => {
33
+ const value = item.value ?? "";
34
+ const isHtml = /<[^>]+>/.test(value);
35
+ return `
36
+ <div style="margin-bottom:16px;">
37
+ <p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
38
+ ${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
39
+ </div>
40
+ `;
41
+ }).join("");
42
+ return `
43
+ <section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
44
+ <h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
45
+ <div style="color:#111827;">
46
+ ${itemsHtml}
47
+ </div>
48
+ </section>
49
+ `;
50
+ }
51
+ function generateFormResultHtml(sections) {
52
+ return sections.map(generateFormSectionHtml).join("\n");
53
+ }
54
+ async function renderEmailTemplate(args) {
55
+ const { title, contentHtml, brandName, footerText } = args;
56
+ return `<!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="utf-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1">
61
+ <title>${title}</title>
62
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
63
+ </head>
64
+ <body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
65
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
66
+ <tr>
67
+ <td align="center" style="padding:24px;">
68
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
69
+ <tr>
70
+ <td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
71
+ <h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
72
+ ${title}
73
+ </h1>
74
+ <p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
75
+ Contact form submission
76
+ </p>
77
+ </td>
78
+ </tr>
79
+ <tr>
80
+ <td style="padding:24px;">
81
+ ${contentHtml}
82
+ </td>
83
+ </tr>
84
+ <tr>
85
+ <td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
86
+ <p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
87
+ ${footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString("en-GB")}.`}
88
+ </p>
89
+ </td>
90
+ </tr>
91
+ </table>
92
+ </td>
93
+ </tr>
94
+ </table>
95
+ </body>
96
+ </html>`;
97
+ }
98
+ //#endregion
99
+ export { composeEmailAddress, generateFormResultHtml, generateFormSectionHtml, renderEmailTemplate, sendEmail, forms_exports as t };