@spectare-personalisation/react 0.2.2 → 0.5.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/dist/index.cjs CHANGED
@@ -1,18 +1,65 @@
1
- 'use strict';
2
-
3
- var react = require('react');
4
- var marked = require('marked');
5
- var DOMPurify = require('isomorphic-dompurify');
6
- var jsxRuntime = require('react/jsx-runtime');
7
-
8
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
1
+ 'use client';
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
9
30
 
10
- var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ AnnouncementBar: () => AnnouncementBar,
35
+ AtomCard: () => AtomCard,
36
+ CodeBlock: () => CodeBlock,
37
+ ComparisonTable: () => ComparisonTable,
38
+ ComponentRenderer: () => ComponentRenderer,
39
+ CtaStrip: () => CtaStrip,
40
+ DEFAULT_ACCENT: () => DEFAULT_ACCENT,
41
+ FeatureList: () => FeatureList,
42
+ HeroStatement: () => HeroStatement,
43
+ ImageCtaHero: () => ImageCtaHero,
44
+ PersonalisedSection: () => PersonalisedSection,
45
+ SP_ROOT: () => SP_ROOT,
46
+ StatGrid: () => StatGrid,
47
+ TestimonialCard: () => TestimonialCard,
48
+ buildTokenCss: () => buildTokenCss,
49
+ filterAssemblyByZone: () => filterAssemblyByZone
50
+ });
51
+ module.exports = __toCommonJS(src_exports);
11
52
 
12
53
  // src/PersonalisedSection.tsx
54
+ var import_react = require("react");
55
+
56
+ // src/ComponentRenderer.tsx
57
+ var import_marked = require("marked");
58
+ var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
13
59
 
14
60
  // src/types.ts
15
61
  var COMPONENT_ZONES = {
62
+ AnnouncementBar: "announce",
16
63
  HeroStatement: "hero",
17
64
  ImageCtaHero: "hero",
18
65
  AtomCard: "body",
@@ -30,156 +77,284 @@ function filterAssemblyByZone(assembly, zone) {
30
77
  return effectiveZone === zone;
31
78
  });
32
79
  }
80
+ var COMPONENT_CATEGORIES = {
81
+ AnnouncementBar: "conversion",
82
+ HeroStatement: "hero",
83
+ AtomCard: "content",
84
+ StatGrid: "content",
85
+ CodeBlock: "technical",
86
+ ComparisonTable: "evaluation",
87
+ FeatureList: "content",
88
+ TestimonialCard: "social-proof",
89
+ ImageCtaHero: "hero",
90
+ CtaStrip: "conversion"
91
+ };
92
+
93
+ // src/tokens.ts
94
+ var DEFAULT_ACCENT = "#60a5fa";
95
+ var SP_ROOT = "sp-root";
96
+ var STYLESHEET_SCOPE = `:where(:root),.${SP_ROOT}`;
97
+ function palette(accent) {
98
+ return {
99
+ "--sp-accent": accent,
100
+ "--sp-accent-bg": `color-mix(in srgb,${accent} 10%,transparent)`,
101
+ "--sp-accent-border": `color-mix(in srgb,${accent} 20%,transparent)`,
102
+ "--sp-bg": "#18181b",
103
+ "--sp-surface": "#09090b",
104
+ "--sp-border": "#27272a",
105
+ "--sp-text": "#f4f4f5",
106
+ "--sp-text-sub": "#d4d4d8",
107
+ "--sp-text-strong": "#e4e4e7",
108
+ "--sp-text-muted": "#82828b",
109
+ "--sp-text-dim": "#6b6b74",
110
+ "--sp-text-body": "#a1a1aa",
111
+ "--sp-positive": "#6ee7b7",
112
+ "--sp-cta-bg": "#fff",
113
+ "--sp-cta-text": "#09090b",
114
+ "--sp-divider": "rgba(255,255,255,0.06)",
115
+ "--sp-code-bg": "rgba(255,255,255,0.07)",
116
+ "--sp-code-border": "rgba(255,255,255,0.08)",
117
+ "--sp-pre-bg": "rgba(0,0,0,0.4)"
118
+ };
119
+ }
120
+ function rules(unit) {
121
+ const gap = unit === "px" ? "20px" : "1.25rem";
122
+ return [
123
+ ".sp-root .sp-prose p{margin-bottom:.625rem}",
124
+ ".sp-root .sp-prose p:last-child{margin-bottom:0}",
125
+ ".sp-root .sp-prose ul{list-style:disc;padding-left:1.25rem;margin-bottom:.625rem}",
126
+ ".sp-root .sp-prose ol{list-style:decimal;padding-left:1.25rem;margin-bottom:.625rem}",
127
+ ".sp-root .sp-prose li{margin-bottom:.25rem}",
128
+ ".sp-root .sp-prose strong{color:var(--sp-text-strong);font-weight:600}",
129
+ ".sp-root .sp-prose h2,.sp-root .sp-prose h3{color:var(--sp-text);font-weight:600;margin:.875rem 0 .375rem;font-size:1rem}",
130
+ ".sp-root .sp-prose code{background:var(--sp-code-bg);border:1px solid var(--sp-code-border);padding:.1rem .35rem;border-radius:.25rem;font-size:.82em;color:var(--sp-text)}",
131
+ ".sp-root .sp-prose blockquote{border-left:3px solid var(--sp-border);padding-left:1rem;color:var(--sp-text-muted);margin:.75rem 0}",
132
+ `.sp-root .sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:${gap}}`,
133
+ "@media(max-width:640px){.sp-root .sp-pair{grid-template-columns:1fr}}"
134
+ ].join("");
135
+ }
136
+ function buildTokenCss({ accent = DEFAULT_ACCENT, unit = "px", scope = `.${SP_ROOT}` } = {}) {
137
+ const decls = Object.entries(palette(accent)).map(([k, v]) => `${k}:${v}`).join(";");
138
+ return `${scope}{${decls}}${rules(unit)}`;
139
+ }
140
+
141
+ // src/ComponentRenderer.tsx
142
+ var import_jsx_runtime = require("react/jsx-runtime");
33
143
  function renderMarkdown(content) {
34
- const raw = marked.marked.parse(content != null ? content : "", { async: false });
35
- return DOMPurify__default.default.sanitize(raw, { USE_PROFILES: { html: true } });
144
+ const raw = import_marked.marked.parse(content != null ? content : "", { async: false });
145
+ return import_isomorphic_dompurify.default.sanitize(raw, { USE_PROFILES: { html: true } });
36
146
  }
147
+ var scaler = (unit) => (n) => unit === "rem" ? `${n / 16}rem` : `${n}px`;
148
+ var PX = scaler("px");
37
149
  var HALF_COMPONENTS = /* @__PURE__ */ new Set(["TestimonialCard"]);
38
150
  var flexItems = (a) => a === "center" ? "center" : a === "right" ? "flex-end" : "flex-start";
39
- function HeroStatement({ headline, subheading, align = "left" }) {
40
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: "24px", paddingBottom: "24px" }, children: [
41
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "32px", fontWeight: 700, letterSpacing: "-0.025em", lineHeight: 1.2, color: "var(--sp-text)", margin: "0 0 12px" }, children: headline }),
42
- subheading && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "18px", color: "var(--sp-text-sub)", lineHeight: 1.6, maxWidth: "672px", margin: 0 }, children: subheading })
151
+ function AnnouncementBar({ text, link, unit = "px" }) {
152
+ const u = scaler(unit);
153
+ if (!text) return null;
154
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
155
+ display: "flex",
156
+ alignItems: "center",
157
+ justifyContent: "center",
158
+ gap: u(10),
159
+ flexWrap: "wrap",
160
+ textAlign: "center",
161
+ background: "var(--sp-accent-bg)",
162
+ border: "1px solid var(--sp-accent-border)",
163
+ borderRadius: u(8),
164
+ padding: `${u(8)} ${u(16)}`,
165
+ fontSize: u(14),
166
+ lineHeight: 1.4,
167
+ color: "var(--sp-text-sub)"
168
+ }, children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: text }),
170
+ (link == null ? void 0 : link.href) && (link == null ? void 0 : link.label) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: link.href, style: { color: "var(--sp-accent)", fontWeight: 600, textDecoration: "none", whiteSpace: "nowrap" }, children: [
171
+ link.label,
172
+ " \u2192"
173
+ ] })
174
+ ] });
175
+ }
176
+ function HeroStatement({ headline, subheading, align = "left", unit = "px" }) {
177
+ const u = scaler(unit);
178
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: u(24), paddingBottom: u(24) }, children: [
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { fontSize: u(32), fontWeight: 700, letterSpacing: "-0.025em", lineHeight: 1.2, color: "var(--sp-text)", margin: `0 0 ${u(12)}` }, children: headline }),
180
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(18), color: "var(--sp-text-sub)", lineHeight: 1.6, maxWidth: u(672), margin: 0 }, children: subheading })
43
181
  ] });
44
182
  }
45
183
  var ATOM_LAYOUT_DEFAULT = ["title", "content", "stats", "cta"];
46
- function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl }) {
184
+ var ATOM_CLAMP_LINES = 4;
185
+ var clampStyle = {
186
+ display: "-webkit-box",
187
+ WebkitLineClamp: ATOM_CLAMP_LINES,
188
+ WebkitBoxOrient: "vertical",
189
+ overflow: "hidden"
190
+ };
191
+ function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl, full, unit = "px" }) {
192
+ const u = scaler(unit);
47
193
  const order = layout && layout.length ? layout : ATOM_LAYOUT_DEFAULT;
48
194
  const element = (name) => {
49
- if (name === "title") return title ? /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "16px", fontWeight: 600, color: "var(--sp-text)", margin: "0 0 12px" }, children: title }, "title") : null;
50
- if (name === "content") return content ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sp-prose", style: { fontSize: "15px", color: "var(--sp-text-sub)", lineHeight: 1.6 }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
51
- if (name === "stats") return stats.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "20px", margin: "0 0 12px", paddingBottom: "12px", borderBottom: "1px solid var(--sp-border)" }, children: stats.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [
52
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "22px", fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
53
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
195
+ if (name === "title") return title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: u(16), fontWeight: 600, color: "var(--sp-text)", margin: `0 0 ${u(12)}` }, children: title }, "title") : null;
196
+ if (name === "content") return content ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sp-prose", style: { fontSize: u(15), color: "var(--sp-text-sub)", lineHeight: 1.6, ...full ? {} : clampStyle }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
197
+ if (name === "stats") return stats.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: u(20), margin: `0 0 ${u(12)}`, paddingBottom: u(12), borderBottom: "1px solid var(--sp-border)" }, children: stats.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: u(2) }, children: [
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(22), fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(11), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
54
200
  ] }, i)) }, "stats") : null;
55
- if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ jsxRuntime.jsxs("a", { href: ctaUrl, style: { display: "inline-flex", alignItems: "center", gap: "4px", marginTop: "12px", fontSize: "13px", fontWeight: 600, color: "var(--sp-accent)", textDecoration: "none" }, children: [
201
+ if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: ctaUrl, style: { display: "inline-flex", alignItems: "center", gap: u(4), marginTop: u(12), fontSize: u(13), fontWeight: 600, color: "var(--sp-accent)", textDecoration: "none" }, children: [
56
202
  ctaText,
57
203
  " \u2192"
58
204
  ] }, "cta") : null;
59
205
  return null;
60
206
  };
61
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "24px", height: "100%" }, children: order.map(element) });
207
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(24), height: "100%" }, children: order.map(element) });
62
208
  }
63
- function StatGrid({ stats = [], caption }) {
209
+ function StatGrid({ stats = [], caption, unit = "px" }) {
210
+ const u = scaler(unit);
64
211
  const cols = stats.length === 2 ? 2 : stats.length === 3 ? 3 : 4;
65
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
66
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: "12px" }, children: stats.map(({ value, label }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "10px", padding: "16px 20px", display: "flex", flexDirection: "column", justifyContent: "space-between", minHeight: "88px" }, children: [
67
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", margin: "0 0 12px", lineHeight: 1.3 }, children: label }),
68
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "20px", fontWeight: 500, color: "var(--sp-text)", fontVariantNumeric: "tabular-nums", lineHeight: 1, margin: 0 }, children: value })
212
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: u(12) }, children: stats.map(({ value, label }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(10), padding: `${u(16)} ${u(20)}`, display: "flex", flexDirection: "column", justifyContent: "space-between", minHeight: u(88) }, children: [
214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(11), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", margin: `0 0 ${u(12)}`, lineHeight: 1.3 }, children: label }),
215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(20), fontWeight: 500, color: "var(--sp-text)", fontVariantNumeric: "tabular-nums", lineHeight: 1, margin: 0 }, children: value })
69
216
  ] }, label)) }),
70
- caption && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", margin: "12px 0 0" }, children: caption })
217
+ caption && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(12)} 0 0` }, children: caption })
71
218
  ] });
72
219
  }
73
- function CodeBlock({ code, lang = "typescript", caption }) {
74
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRadius: "12px", overflow: "hidden", border: "1px solid var(--sp-border)" }, children: [
75
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: "var(--sp-bg)", padding: "10px 16px", display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid var(--sp-border)" }, children: [
76
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "12px", color: "var(--sp-text-muted)", fontFamily: "monospace" }, children: lang }),
77
- caption && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "12px", color: "var(--sp-text-muted)" }, children: caption })
220
+ function CodeBlock({ code, lang = "typescript", caption, unit = "px" }) {
221
+ const u = scaler(unit);
222
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderRadius: u(12), overflow: "hidden", border: "1px solid var(--sp-border)" }, children: [
223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { background: "var(--sp-bg)", padding: `${u(10)} ${u(16)}`, display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid var(--sp-border)" }, children: [
224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(12), color: "var(--sp-text-muted)", fontFamily: "monospace" }, children: lang }),
225
+ caption && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(12), color: "var(--sp-text-muted)" }, children: caption })
78
226
  ] }),
79
- /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { background: "var(--sp-surface)", padding: "20px", fontSize: "13px", color: "var(--sp-text-sub)", overflowX: "auto", lineHeight: 1.6, fontFamily: "monospace", whiteSpace: "pre", margin: 0 }, children: code })
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { style: { background: "var(--sp-surface)", padding: u(20), fontSize: u(13), color: "var(--sp-text-sub)", overflowX: "auto", lineHeight: 1.6, fontFamily: "monospace", whiteSpace: "pre", margin: 0 }, children: code })
80
228
  ] });
81
229
  }
82
- function ComparisonTable({ brand = "Us", competitor, rows = [] }) {
83
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", fontSize: "14px", borderCollapse: "collapse" }, children: [
84
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { style: { borderBottom: "1px solid var(--sp-border)", background: "var(--sp-bg)" }, children: [
85
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "12px 16px", color: "var(--sp-text-muted)", fontWeight: 500, fontSize: "12px", width: "33%" } }),
86
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "12px 16px", fontWeight: 600, color: "var(--sp-accent)", fontSize: "12px" }, children: brand }),
87
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: { textAlign: "left", padding: "12px 16px", color: "var(--sp-text-muted)", fontWeight: 500, fontSize: "12px" }, children: competitor })
230
+ function ComparisonTable({ brand = "Us", competitor, rows = [], unit = "px" }) {
231
+ const u = scaler(unit);
232
+ const cell = { textAlign: "left", padding: `${u(12)} ${u(16)}` };
233
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { border: "1px solid var(--sp-border)", borderRadius: u(12), overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: { width: "100%", fontSize: u(14), borderCollapse: "collapse" }, children: [
234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { style: { borderBottom: "1px solid var(--sp-border)", background: "var(--sp-bg)" }, children: [
235
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12), width: "33%" } }),
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, fontWeight: 600, color: "var(--sp-accent)", fontSize: u(12) }, children: brand }),
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12) }, children: competitor })
88
238
  ] }) }),
89
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map(({ label, spectare, them }) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { style: { borderBottom: "1px solid var(--sp-border)" }, children: [
90
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
91
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
92
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-text-muted)" }, children: them })
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map(({ label, spectare, them }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { style: { borderBottom: "1px solid var(--sp-border)" }, children: [
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "row", style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)" }, children: them })
93
243
  ] }, label)) })
94
244
  ] }) }) });
95
245
  }
96
- function FeatureList({ items = [] }) {
97
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "24px" }, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "16px" }, children: items.map(({ text, sub }) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { display: "flex", gap: "12px" }, children: [
98
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--sp-accent)", flexShrink: 0, fontSize: "15px", marginTop: "2px" }, children: "\u2713" }),
99
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
100
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "15px", fontWeight: 500, color: "var(--sp-text)", lineHeight: 1.4, margin: 0 }, children: text }),
101
- sub && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", lineHeight: 1.5, margin: "4px 0 0" }, children: sub })
246
+ function FeatureList({ items = [], unit = "px" }) {
247
+ const u = scaler(unit);
248
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(24) }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: u(16) }, children: items.map(({ text, sub }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: u(12) }, children: [
249
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--sp-accent)", flexShrink: 0, fontSize: u(15), marginTop: u(2) }, children: "\u2713" }),
250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(15), fontWeight: 500, color: "var(--sp-text)", lineHeight: 1.4, margin: 0 }, children: text }),
252
+ sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", lineHeight: 1.5, margin: `${u(4)} 0 0` }, children: sub })
102
253
  ] })
103
254
  ] }, text)) }) });
104
255
  }
105
- function TestimonialCard({ quote, name, role, company }) {
106
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "24px", display: "flex", flexDirection: "column", gap: "20px", height: "100%" }, children: [
107
- /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "15px", color: "var(--sp-text-sub)", lineHeight: 1.6, flex: 1, margin: 0 }, children: [
108
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: "18px", lineHeight: 1, marginRight: "4px" }, children: '"' }),
256
+ function TestimonialCard({ quote, name, role, company, unit = "px" }) {
257
+ const u = scaler(unit);
258
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(24), display: "flex", flexDirection: "column", gap: u(20), height: "100%" }, children: [
259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: u(15), color: "var(--sp-text-sub)", lineHeight: 1.6, flex: 1, margin: 0 }, children: [
260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--sp-text-dim)", fontSize: u(18), lineHeight: 1, marginRight: u(4) }, children: '"' }),
109
261
  quote,
110
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: "18px", lineHeight: 1, marginLeft: "4px" }, children: '"' })
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--sp-text-dim)", fontSize: u(18), lineHeight: 1, marginLeft: u(4) }, children: '"' })
111
263
  ] }),
112
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderTop: "1px solid var(--sp-border)", paddingTop: "16px" }, children: [
113
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "14px", fontWeight: 600, color: "var(--sp-text)", margin: 0 }, children: name }),
114
- /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", margin: "2px 0 0" }, children: [
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderTop: "1px solid var(--sp-border)", paddingTop: u(16) }, children: [
265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(14), fontWeight: 600, color: "var(--sp-text)", margin: 0 }, children: name }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(2)} 0 0` }, children: [
115
267
  role,
116
268
  company ? `, ${company}` : ""
117
269
  ] })
118
270
  ] })
119
271
  ] });
120
272
  }
121
- function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref, align = "left" }) {
273
+ function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref, align = "left", unit = "px" }) {
274
+ const u = scaler(unit);
122
275
  if (!src) return null;
123
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRadius: "12px", overflow: "hidden", position: "relative", width: "100%", paddingBottom: "56.25%" }, children: [
124
- /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" } }),
125
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.45)" } }),
126
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.2) 60%, transparent 100%)" } }),
127
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "28px", textAlign: align }, children: [
128
- headline && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "24px", fontWeight: 700, color: "#fff", lineHeight: 1.2, margin: "0 0 8px", letterSpacing: "-0.02em" }, children: headline }),
129
- subheading && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "15px", color: "rgba(255,255,255,0.75)", lineHeight: 1.5, margin: "0 0 20px" }, children: subheading }),
130
- ctaHref && ctaLabel && /* @__PURE__ */ jsxRuntime.jsx("a", { href: ctaHref, style: { display: "inline-block", background: "#fff", color: "#09090b", padding: "8px 20px", borderRadius: "8px", fontWeight: 600, fontSize: "14px", textDecoration: "none" }, children: ctaLabel })
276
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderRadius: u(12), overflow: "hidden", position: "relative", width: "100%", paddingBottom: "56.25%" }, children: [
277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src, alt, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" } }),
278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.45)" } }),
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.2) 60%, transparent 100%)" } }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: u(28), textAlign: align }, children: [
281
+ headline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { fontSize: u(24), fontWeight: 700, color: "#fff", lineHeight: 1.2, margin: `0 0 ${u(8)}`, letterSpacing: "-0.02em" }, children: headline }),
282
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(15), color: "rgba(255,255,255,0.75)", lineHeight: 1.5, margin: `0 0 ${u(20)}` }, children: subheading }),
283
+ ctaHref && ctaLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: ctaHref, style: { display: "inline-block", background: "#fff", color: "#09090b", padding: `${u(8)} ${u(20)}`, borderRadius: u(8), fontWeight: 600, fontSize: u(14), textDecoration: "none" }, children: ctaLabel })
131
284
  ] })
132
285
  ] });
133
286
  }
134
- function CtaStrip({ headline, primary, secondary, align = "center" }) {
135
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "32px", textAlign: align, background: "var(--sp-bg)" }, children: [
136
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "24px", fontWeight: 700, color: "var(--sp-text)", margin: "0 0 24px" }, children: headline }),
137
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: flexItems(align), gap: "16px", flexWrap: "wrap" }, children: [
138
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: primary.href, style: { background: "#fff", color: "#09090b", padding: "10px 24px", borderRadius: "12px", fontWeight: 600, fontSize: "15px", textDecoration: "none" }, children: primary.label }),
139
- secondary && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: secondary.href, style: { fontSize: "15px", color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
287
+ function CtaStrip({ headline, primary, secondary, align = "center", unit = "px" }) {
288
+ const u = scaler(unit);
289
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(32), textAlign: align, background: "var(--sp-bg)" }, children: [
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: u(24), fontWeight: 700, color: "var(--sp-text)", margin: `0 0 ${u(24)}` }, children: headline }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: flexItems(align), gap: u(16), flexWrap: "wrap" }, children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: primary.href, style: { background: "var(--sp-cta-bg)", color: "var(--sp-cta-text)", padding: `${u(10)} ${u(24)}`, borderRadius: u(12), fontWeight: 600, fontSize: u(15), textDecoration: "none" }, children: primary.label }),
293
+ secondary && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: secondary.href, style: { fontSize: u(15), color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
140
294
  secondary.label,
141
295
  " \u2192"
142
296
  ] })
143
297
  ] })
144
298
  ] });
145
299
  }
146
- function renderSingle(item, brand, align) {
300
+ function renderSingle(item, brand, align, unit = "px") {
147
301
  var _a, _b;
148
302
  const s = (_a = item.slots) != null ? _a : {};
149
303
  switch (item.component) {
304
+ case "AnnouncementBar":
305
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnnouncementBar, { text: s.text, link: s.link, unit });
150
306
  case "HeroStatement":
151
- return /* @__PURE__ */ jsxRuntime.jsx(HeroStatement, { headline: s.headline, subheading: s.subheading, align });
307
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeroStatement, { headline: s.headline, subheading: s.subheading, align, unit });
308
+ // A full-width card has nothing beside it, so there is nothing for a clamp to protect.
152
309
  case "AtomCard":
153
- return /* @__PURE__ */ jsxRuntime.jsx(AtomCard, { title: s.title, content: s.content, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl });
310
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AtomCard, { title: s.title, content: s.content, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: s.full || resolvedSize(item) === "full", unit });
154
311
  case "StatGrid":
155
- return /* @__PURE__ */ jsxRuntime.jsx(StatGrid, { stats: s.stats, caption: s.caption });
312
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatGrid, { stats: s.stats, caption: s.caption, unit });
156
313
  case "CodeBlock":
157
- return /* @__PURE__ */ jsxRuntime.jsx(CodeBlock, { code: s.code, lang: s.lang, caption: s.caption });
314
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeBlock, { code: s.code, lang: s.lang, caption: s.caption, unit });
158
315
  case "ComparisonTable":
159
- return /* @__PURE__ */ jsxRuntime.jsx(ComparisonTable, { brand: (_b = s.brand) != null ? _b : brand, competitor: s.competitor, rows: s.rows });
316
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComparisonTable, { brand: (_b = s.brand) != null ? _b : brand, competitor: s.competitor, rows: s.rows, unit });
160
317
  case "FeatureList":
161
- return /* @__PURE__ */ jsxRuntime.jsx(FeatureList, { items: s.items });
318
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FeatureList, { items: s.items, unit });
162
319
  case "TestimonialCard":
163
- return /* @__PURE__ */ jsxRuntime.jsx(TestimonialCard, { quote: s.quote, name: s.name, role: s.role, company: s.company });
320
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TestimonialCard, { quote: s.quote, name: s.name, role: s.role, company: s.company, unit });
164
321
  case "ImageCtaHero":
165
- return /* @__PURE__ */ jsxRuntime.jsx(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref, align });
322
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref, align, unit });
166
323
  case "CtaStrip":
167
- return /* @__PURE__ */ jsxRuntime.jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align });
168
- default:
169
- return null;
324
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align, unit });
325
+ // An unrecognised component name used to render nothing at all. A component needs a
326
+ // renderer here plus spectare.js plus the WordPress plugin, so missing one made its content
327
+ // silently absent on that surface only: no error, no warning, just a shorter page that
328
+ // looks deliberate. That is the failure mode to remove before the registry grows.
329
+ //
330
+ // Falls back to AtomCard when the slots carry something readable, since almost every
331
+ // component is built from an atom and title/content is the shape they share. When there
332
+ // is genuinely nothing to show it still renders nothing, because an empty card is worse
333
+ // than a gap, but the warning fires either way.
334
+ default: {
335
+ console.warn(`[spectare] ComponentRenderer: no renderer for "${item.component}", falling back to AtomCard`);
336
+ if (!s.title && !s.content) return null;
337
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AtomCard, { title: s.title, content: s.content, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: true, unit });
338
+ }
170
339
  }
171
340
  }
172
341
  function resolvedSize(item) {
173
342
  if (item.size) return item.size;
174
343
  return HALF_COMPONENTS.has(item.component) ? "half" : "full";
175
344
  }
345
+ function pairable(a, b) {
346
+ const catOf = (x) => COMPONENT_CATEGORIES[x.component];
347
+ const ca = catOf(a);
348
+ const cb = catOf(b);
349
+ return ca !== void 0 && ca === cb;
350
+ }
176
351
  function buildLayout(assembly) {
177
352
  const groups = [];
178
353
  let i = 0;
179
354
  while (i < assembly.length) {
180
355
  const cur = assembly[i];
181
356
  const next = assembly[i + 1];
182
- if (resolvedSize(cur) === "half" && next && resolvedSize(next) === "half") {
357
+ if (resolvedSize(cur) === "half" && next && resolvedSize(next) === "half" && pairable(cur, next)) {
183
358
  groups.push({ type: "pair", items: [cur, next] });
184
359
  i += 2;
185
360
  } else if (resolvedSize(cur) === "half") {
@@ -193,44 +368,60 @@ function buildLayout(assembly) {
193
368
  return groups;
194
369
  }
195
370
  function XrayBadge({ name, size }) {
196
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", top: "8px", left: "8px", zIndex: 10, display: "flex", alignItems: "center", gap: "6px", pointerEvents: "none", userSelect: "none" }, children: [
197
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontFamily: "monospace", background: "rgba(59,130,246,0.9)", color: "#fff", padding: "2px 6px", borderRadius: "4px", lineHeight: 1 }, children: name }),
198
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
371
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "absolute", top: PX(8), left: PX(8), zIndex: 10, display: "flex", alignItems: "center", gap: PX(6), pointerEvents: "none", userSelect: "none" }, children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: PX(10), fontFamily: "monospace", background: "rgba(59,130,246,0.9)", color: "#fff", padding: `${PX(2)} ${PX(6)}`, borderRadius: PX(4), lineHeight: 1 }, children: name }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: PX(10), fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
199
374
  ] });
200
375
  }
201
- var PAIR_STYLE = ".sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:640px){.sp-pair{grid-template-columns:1fr}}";
202
- function ComponentRenderer({ assembly, brand, xray = false, zone, align }) {
376
+ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent, unit = "px", renderOverride, renderItem, rootProps }) {
377
+ const u = scaler(unit);
203
378
  const filtered = zone ? filterAssemblyByZone(assembly, zone) : assembly;
204
379
  const layout = buildLayout(filtered);
205
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "20px", fontSize: "16px" }, children: [
206
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: PAIR_STYLE } }),
380
+ const { className: extraClass, style: extraStyle, ...restRoot } = rootProps != null ? rootProps : {};
381
+ const renderOne = (item) => {
382
+ const override = renderOverride == null ? void 0 : renderOverride(item);
383
+ const node = override === void 0 ? renderSingle(item, brand, align, unit) : override;
384
+ return renderItem ? renderItem(item, node) : node;
385
+ };
386
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: extraClass ? `${SP_ROOT} ${extraClass}` : SP_ROOT, ...restRoot, style: { ...extraStyle, display: "flex", flexDirection: "column", gap: u(20), ...unit === "px" ? { fontSize: "16px" } : {} }, children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: buildTokenCss({ accent, unit }) } }),
207
388
  layout.map((group, i) => {
208
- if (group.type === "full") {
209
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
210
- xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.item.component, size: "full" }),
211
- renderSingle(group.item, brand, align)
212
- ] }, i);
213
- }
214
389
  if (group.type === "pair") {
215
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sp-pair", children: [
216
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
217
- xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.items[0].component, size: "half" }),
218
- renderSingle(group.items[0], brand, align)
390
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sp-pair", children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: xray ? { position: "relative" } : void 0, children: [
392
+ xray && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XrayBadge, { name: group.items[0].component, size: "half" }),
393
+ renderOne(group.items[0])
219
394
  ] }),
220
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
221
- xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.items[1].component, size: "half" }),
222
- renderSingle(group.items[1], brand, align)
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: xray ? { position: "relative" } : void 0, children: [
396
+ xray && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XrayBadge, { name: group.items[1].component, size: "half" }),
397
+ renderOne(group.items[1])
223
398
  ] })
224
399
  ] }, i);
225
400
  }
226
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
227
- xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.item.component, size: "full" }),
228
- renderSingle(group.item, brand, align)
401
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: xray ? { position: "relative" } : void 0, children: [
402
+ xray && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XrayBadge, { name: group.item.component, size: group.type === "half-solo" ? "half" : "full" }),
403
+ renderOne(group.item)
229
404
  ] }, i);
230
405
  })
231
406
  ] });
232
407
  }
233
- var CSS_TOKENS = `:root{--sp-accent:#60a5fa;--sp-accent-bg:rgba(96,165,250,0.1);--sp-accent-border:rgba(96,165,250,0.2);--sp-bg:#18181b;--sp-surface:#09090b;--sp-border:#27272a;--sp-text:#f4f4f5;--sp-text-sub:#d4d4d8;--sp-text-strong:#e4e4e7;--sp-text-muted:#71717a;--sp-text-dim:#52525b;--sp-text-body:#a1a1aa;--sp-positive:#6ee7b7;--sp-divider:rgba(255,255,255,0.06);--sp-code-bg:rgba(255,255,255,0.07);--sp-code-border:rgba(255,255,255,0.08);--sp-pre-bg:rgba(0,0,0,0.4)}.sp-prose p{margin-bottom:.625rem}.sp-prose p:last-child{margin-bottom:0}.sp-prose ul{list-style:disc;padding-left:1.25rem;margin-bottom:.625rem}.sp-prose ol{list-style:decimal;padding-left:1.25rem;margin-bottom:.625rem}.sp-prose li{margin-bottom:.25rem}.sp-prose strong{color:var(--sp-text-strong);font-weight:600}.sp-prose h2,.sp-prose h3{color:var(--sp-text);font-weight:600;margin:.875rem 0 .375rem;font-size:1rem}.sp-prose code{background:var(--sp-code-bg);border:1px solid var(--sp-code-border);padding:.1rem .35rem;border-radius:.25rem;font-size:.82em;color:var(--sp-text)}.sp-prose blockquote{border-left:3px solid var(--sp-border);padding-left:1rem;color:var(--sp-text-muted);margin:.75rem 0}`;
408
+
409
+ // src/PersonalisedSection.tsx
410
+ var import_jsx_runtime2 = require("react/jsx-runtime");
411
+ var VARIANT_KEY = (orgSlug) => `_spectare_variant_${orgSlug}`;
412
+ function readVariantId(orgSlug) {
413
+ try {
414
+ return sessionStorage.getItem(VARIANT_KEY(orgSlug));
415
+ } catch (e) {
416
+ return null;
417
+ }
418
+ }
419
+ function writeVariantId(orgSlug, variantId) {
420
+ try {
421
+ sessionStorage.setItem(VARIANT_KEY(orgSlug), variantId);
422
+ } catch (e) {
423
+ }
424
+ }
234
425
  function buildSummary(pageHint, orgSlug) {
235
426
  const parts = [pageHint];
236
427
  const referrer = document.referrer;
@@ -266,11 +457,11 @@ function readVisitPath(orgSlug) {
266
457
  }
267
458
  }
268
459
  function Shimmer() {
269
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
270
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "20px", background: "rgba(39,39,42,0.6)", borderRadius: "8px", width: "75%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
271
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "100%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
272
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "83%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
273
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "67%", animation: "sp-pulse 1.5s ease-in-out infinite" } })
460
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { height: "20px", background: "rgba(39,39,42,0.6)", borderRadius: "8px", width: "75%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "100%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "83%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "67%", animation: "sp-pulse 1.5s ease-in-out infinite" } })
274
465
  ] });
275
466
  }
276
467
  function PersonalisedSection({
@@ -282,32 +473,39 @@ function PersonalisedSection({
282
473
  style,
283
474
  initialAssembly,
284
475
  initialIntent,
476
+ initialVariantId,
477
+ directSummary,
285
478
  zone,
286
- align
479
+ align,
480
+ accent
287
481
  }) {
288
- const [assembly, setAssembly] = react.useState(initialAssembly != null ? initialAssembly : []);
289
- const [loading, setLoading] = react.useState(!initialAssembly);
290
- const [orgName, setOrgName] = react.useState(void 0);
291
- const tokenRef = react.useRef(null);
292
- const conversionFiredRef = react.useRef(false);
293
- react.useEffect(() => {
482
+ const [assembly, setAssembly] = (0, import_react.useState)(initialAssembly != null ? initialAssembly : []);
483
+ const [loading, setLoading] = (0, import_react.useState)(!initialAssembly);
484
+ const [orgName, setOrgName] = (0, import_react.useState)(void 0);
485
+ const [debugMode, setDebugMode] = (0, import_react.useState)(false);
486
+ const [debugIntent, setDebugIntent] = (0, import_react.useState)(null);
487
+ const tokenRef = (0, import_react.useRef)(null);
488
+ const conversionFiredRef = (0, import_react.useRef)(false);
489
+ (0, import_react.useEffect)(() => {
490
+ setDebugMode(new URLSearchParams(window.location.search).has("spectare_debug"));
491
+ if (initialIntent) setDebugIntent(initialIntent);
492
+ }, [initialIntent]);
493
+ (0, import_react.useEffect)(() => {
494
+ if (initialVariantId) writeVariantId(orgSlug, initialVariantId);
495
+ }, [orgSlug, initialVariantId]);
496
+ (0, import_react.useEffect)(() => {
294
497
  let cancelled = false;
295
498
  async function load() {
296
- var _a, _b, _c, _d, _e, _f, _g, _h;
499
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
297
500
  try {
298
- const p = new URLSearchParams(window.location.search);
299
- const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
300
- var _a2;
301
- return (_a2 = p.get(k)) != null ? _a2 : "";
302
- }).filter(Boolean).join("_");
303
- const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
304
- let token = sessionStorage.getItem(tokenKey);
305
- if (!token) {
306
- const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
501
+ let token = null;
502
+ if (directSummary) {
503
+ setLoading(true);
504
+ setAssembly([]);
307
505
  const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
308
506
  method: "POST",
309
507
  headers: { "Content-Type": "application/json" },
310
- body: JSON.stringify({ summary })
508
+ body: JSON.stringify({ summary: directSummary, direct: true })
311
509
  });
312
510
  if (!res2.ok || cancelled) {
313
511
  setLoading(false);
@@ -315,8 +513,30 @@ function PersonalisedSection({
315
513
  }
316
514
  const data = await res2.json();
317
515
  token = (_a = data.token) != null ? _a : null;
318
- if (token) sessionStorage.setItem(tokenKey, token);
319
- if (data.orgName) setOrgName(data.orgName);
516
+ } else {
517
+ const p = new URLSearchParams(window.location.search);
518
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
519
+ var _a2;
520
+ return (_a2 = p.get(k)) != null ? _a2 : "";
521
+ }).filter(Boolean).join("_");
522
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
523
+ token = sessionStorage.getItem(tokenKey);
524
+ if (!token) {
525
+ const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
526
+ const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
527
+ method: "POST",
528
+ headers: { "Content-Type": "application/json" },
529
+ body: JSON.stringify({ summary })
530
+ });
531
+ if (!res2.ok || cancelled) {
532
+ setLoading(false);
533
+ return;
534
+ }
535
+ const data = await res2.json();
536
+ token = (_b = data.token) != null ? _b : null;
537
+ if (token) sessionStorage.setItem(tokenKey, token);
538
+ if (data.orgName) setOrgName(data.orgName);
539
+ }
320
540
  }
321
541
  if (!token || cancelled) {
322
542
  setLoading(false);
@@ -326,9 +546,19 @@ function PersonalisedSection({
326
546
  let res = await fetch(`${baseUrl}/api/assemble/components`, {
327
547
  method: "POST",
328
548
  headers: { "Content-Type": "application/json" },
329
- body: JSON.stringify({ token, orgSlug })
549
+ // Direct: flag it so the server cost-caps the build, and omit the seen-variant
550
+ // exclusion so it can read the shared cache instead of forcing a fresh build.
551
+ body: JSON.stringify(
552
+ directSummary ? { token, orgSlug, direct: true } : { token, orgSlug, excludeVariantId: readVariantId(orgSlug) }
553
+ )
330
554
  });
331
- if (res.status === 401 && !cancelled) {
555
+ if (res.status === 401 && !cancelled && !directSummary) {
556
+ const p = new URLSearchParams(window.location.search);
557
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
558
+ var _a2;
559
+ return (_a2 = p.get(k)) != null ? _a2 : "";
560
+ }).filter(Boolean).join("_");
561
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
332
562
  sessionStorage.removeItem(tokenKey);
333
563
  const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
334
564
  const qRes = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
@@ -341,7 +571,7 @@ function PersonalisedSection({
341
571
  return;
342
572
  }
343
573
  const qData = await qRes.json();
344
- token = (_b = qData.token) != null ? _b : null;
574
+ token = (_c = qData.token) != null ? _c : null;
345
575
  if (token) {
346
576
  sessionStorage.setItem(tokenKey, token);
347
577
  tokenRef.current = token;
@@ -353,14 +583,14 @@ function PersonalisedSection({
353
583
  res = await fetch(`${baseUrl}/api/assemble/components`, {
354
584
  method: "POST",
355
585
  headers: { "Content-Type": "application/json" },
356
- body: JSON.stringify({ token, orgSlug })
586
+ body: JSON.stringify({ token, orgSlug, excludeVariantId: readVariantId(orgSlug) })
357
587
  });
358
588
  }
359
589
  if (!res.ok || cancelled) {
360
590
  setLoading(false);
361
591
  return;
362
592
  }
363
- const contentType = (_c = res.headers.get("content-type")) != null ? _c : "";
593
+ const contentType = (_d = res.headers.get("content-type")) != null ? _d : "";
364
594
  if (contentType.includes("text/event-stream")) {
365
595
  const reader = res.body.getReader();
366
596
  const decoder = new TextDecoder();
@@ -372,13 +602,26 @@ function PersonalisedSection({
372
602
  if (done || cancelled) break;
373
603
  buf += decoder.decode(value, { stream: true });
374
604
  const lines = buf.split("\n");
375
- buf = (_d = lines.pop()) != null ? _d : "";
605
+ buf = (_e = lines.pop()) != null ? _e : "";
376
606
  for (const line of lines) {
377
607
  if (!line.startsWith("data: ")) continue;
378
608
  const payload = line.slice(6).trim();
379
609
  if (payload === "[DONE]") break;
380
610
  try {
381
- const comp = JSON.parse(payload);
611
+ const parsed = JSON.parse(payload);
612
+ if (parsed._variant && typeof parsed._variant === "string") {
613
+ writeVariantId(orgSlug, parsed._variant);
614
+ continue;
615
+ }
616
+ if (parsed._error) {
617
+ if (!loadingCleared) {
618
+ setLoading(false);
619
+ loadingCleared = true;
620
+ }
621
+ continue;
622
+ }
623
+ if (!parsed.component) continue;
624
+ const comp = parsed;
382
625
  if (!loadingCleared) {
383
626
  setLoading(false);
384
627
  loadingCleared = true;
@@ -394,10 +637,14 @@ function PersonalisedSection({
394
637
  }
395
638
  } else {
396
639
  const data = await res.json();
640
+ if (data.variantId) writeVariantId(orgSlug, data.variantId);
397
641
  if (cancelled) return;
398
- const clientAudience = (_f = (_e = data.intent) == null ? void 0 : _e.userContext) == null ? void 0 : _f.audience;
399
- const clientStage = (_h = (_g = data.intent) == null ? void 0 : _g.userContext) == null ? void 0 : _h.stage;
642
+ const clientAudience = (_g = (_f = data.intent) == null ? void 0 : _f.userContext) == null ? void 0 : _g.audience;
643
+ const clientStage = (_i = (_h = data.intent) == null ? void 0 : _h.userContext) == null ? void 0 : _i.stage;
400
644
  const ssrMatches = initialIntent && clientAudience === initialIntent.audience && clientStage === initialIntent.stage;
645
+ if (clientAudience && clientStage) {
646
+ setDebugIntent({ audience: clientAudience, stage: clientStage });
647
+ }
401
648
  if (!ssrMatches && Array.isArray(data.assembly) && data.assembly.length > 0) {
402
649
  setAssembly(data.assembly);
403
650
  }
@@ -411,8 +658,8 @@ function PersonalisedSection({
411
658
  return () => {
412
659
  cancelled = true;
413
660
  };
414
- }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent]);
415
- react.useEffect(() => {
661
+ }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent, directSummary]);
662
+ (0, import_react.useEffect)(() => {
416
663
  if (!assembly.length || conversionFiredRef.current) return;
417
664
  const token = tokenRef.current;
418
665
  if (!token) return;
@@ -456,6 +703,7 @@ function PersonalisedSection({
456
703
  token,
457
704
  orgSlug,
458
705
  url,
706
+ variantId: readVariantId(orgSlug),
459
707
  ...atomsShown.length ? { atomsShown } : {},
460
708
  ...visitPath.length > 1 ? { visitPath } : {},
461
709
  ...data.goalName ? { goalName: data.goalName } : {},
@@ -467,15 +715,40 @@ function PersonalisedSection({
467
715
  });
468
716
  }, [assembly, orgSlug, baseUrl]);
469
717
  if (!loading && assembly.length === 0) return null;
470
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
471
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: CSS_TOKENS } }),
472
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: "@keyframes sp-pulse{0%,100%{opacity:1}50%{opacity:0.4}}" } }),
473
- /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: loading && assembly.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Shimmer, {}) : /* @__PURE__ */ jsxRuntime.jsx(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align }) })
718
+ const debugStyle = debugMode ? { outline: "2px solid rgba(96,165,250,0.5)", borderRadius: "0.5rem", position: "relative", padding: "0.5rem", ...style } : style;
719
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
720
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { dangerouslySetInnerHTML: { __html: "@keyframes sp-pulse{0%,100%{opacity:1}50%{opacity:0.4}}" } }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: debugStyle, children: [
722
+ debugMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "absolute", top: "-1px", right: "-1px", zIndex: 20, display: "flex", alignItems: "center", gap: "0.375rem", background: "rgba(96,165,250,0.9)", borderRadius: "0 0.5rem 0 0.375rem", padding: "0.25rem 0.625rem", fontSize: "0.625rem", fontFamily: "monospace", color: "#fff", pointerEvents: "none", userSelect: "none" }, children: [
723
+ "Personalised by Spectare",
724
+ debugIntent && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { opacity: 0.75 }, children: [
725
+ " ",
726
+ debugIntent.audience,
727
+ "/",
728
+ debugIntent.stage
729
+ ] })
730
+ ] }),
731
+ loading && assembly.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Shimmer, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align, accent })
732
+ ] })
474
733
  ] });
475
734
  }
476
-
477
- exports.ComponentRenderer = ComponentRenderer;
478
- exports.PersonalisedSection = PersonalisedSection;
479
- exports.filterAssemblyByZone = filterAssemblyByZone;
480
- //# sourceMappingURL=index.cjs.map
735
+ // Annotate the CommonJS export names for ESM import in node:
736
+ 0 && (module.exports = {
737
+ AnnouncementBar,
738
+ AtomCard,
739
+ CodeBlock,
740
+ ComparisonTable,
741
+ ComponentRenderer,
742
+ CtaStrip,
743
+ DEFAULT_ACCENT,
744
+ FeatureList,
745
+ HeroStatement,
746
+ ImageCtaHero,
747
+ PersonalisedSection,
748
+ SP_ROOT,
749
+ StatGrid,
750
+ TestimonialCard,
751
+ buildTokenCss,
752
+ filterAssemblyByZone
753
+ });
481
754
  //# sourceMappingURL=index.cjs.map