@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.js CHANGED
@@ -1,12 +1,15 @@
1
- import { useState, useRef, useEffect } from 'react';
2
- import { marked } from 'marked';
3
- import DOMPurify from 'isomorphic-dompurify';
4
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ 'use client';
5
2
 
6
3
  // src/PersonalisedSection.tsx
4
+ import { useEffect, useRef, useState } from "react";
5
+
6
+ // src/ComponentRenderer.tsx
7
+ import { marked } from "marked";
8
+ import DOMPurify from "isomorphic-dompurify";
7
9
 
8
10
  // src/types.ts
9
11
  var COMPONENT_ZONES = {
12
+ AnnouncementBar: "announce",
10
13
  HeroStatement: "hero",
11
14
  ImageCtaHero: "hero",
12
15
  AtomCard: "body",
@@ -24,156 +27,284 @@ function filterAssemblyByZone(assembly, zone) {
24
27
  return effectiveZone === zone;
25
28
  });
26
29
  }
30
+ var COMPONENT_CATEGORIES = {
31
+ AnnouncementBar: "conversion",
32
+ HeroStatement: "hero",
33
+ AtomCard: "content",
34
+ StatGrid: "content",
35
+ CodeBlock: "technical",
36
+ ComparisonTable: "evaluation",
37
+ FeatureList: "content",
38
+ TestimonialCard: "social-proof",
39
+ ImageCtaHero: "hero",
40
+ CtaStrip: "conversion"
41
+ };
42
+
43
+ // src/tokens.ts
44
+ var DEFAULT_ACCENT = "#60a5fa";
45
+ var SP_ROOT = "sp-root";
46
+ var STYLESHEET_SCOPE = `:where(:root),.${SP_ROOT}`;
47
+ function palette(accent) {
48
+ return {
49
+ "--sp-accent": accent,
50
+ "--sp-accent-bg": `color-mix(in srgb,${accent} 10%,transparent)`,
51
+ "--sp-accent-border": `color-mix(in srgb,${accent} 20%,transparent)`,
52
+ "--sp-bg": "#18181b",
53
+ "--sp-surface": "#09090b",
54
+ "--sp-border": "#27272a",
55
+ "--sp-text": "#f4f4f5",
56
+ "--sp-text-sub": "#d4d4d8",
57
+ "--sp-text-strong": "#e4e4e7",
58
+ "--sp-text-muted": "#82828b",
59
+ "--sp-text-dim": "#6b6b74",
60
+ "--sp-text-body": "#a1a1aa",
61
+ "--sp-positive": "#6ee7b7",
62
+ "--sp-cta-bg": "#fff",
63
+ "--sp-cta-text": "#09090b",
64
+ "--sp-divider": "rgba(255,255,255,0.06)",
65
+ "--sp-code-bg": "rgba(255,255,255,0.07)",
66
+ "--sp-code-border": "rgba(255,255,255,0.08)",
67
+ "--sp-pre-bg": "rgba(0,0,0,0.4)"
68
+ };
69
+ }
70
+ function rules(unit) {
71
+ const gap = unit === "px" ? "20px" : "1.25rem";
72
+ return [
73
+ ".sp-root .sp-prose p{margin-bottom:.625rem}",
74
+ ".sp-root .sp-prose p:last-child{margin-bottom:0}",
75
+ ".sp-root .sp-prose ul{list-style:disc;padding-left:1.25rem;margin-bottom:.625rem}",
76
+ ".sp-root .sp-prose ol{list-style:decimal;padding-left:1.25rem;margin-bottom:.625rem}",
77
+ ".sp-root .sp-prose li{margin-bottom:.25rem}",
78
+ ".sp-root .sp-prose strong{color:var(--sp-text-strong);font-weight:600}",
79
+ ".sp-root .sp-prose h2,.sp-root .sp-prose h3{color:var(--sp-text);font-weight:600;margin:.875rem 0 .375rem;font-size:1rem}",
80
+ ".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)}",
81
+ ".sp-root .sp-prose blockquote{border-left:3px solid var(--sp-border);padding-left:1rem;color:var(--sp-text-muted);margin:.75rem 0}",
82
+ `.sp-root .sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:${gap}}`,
83
+ "@media(max-width:640px){.sp-root .sp-pair{grid-template-columns:1fr}}"
84
+ ].join("");
85
+ }
86
+ function buildTokenCss({ accent = DEFAULT_ACCENT, unit = "px", scope = `.${SP_ROOT}` } = {}) {
87
+ const decls = Object.entries(palette(accent)).map(([k, v]) => `${k}:${v}`).join(";");
88
+ return `${scope}{${decls}}${rules(unit)}`;
89
+ }
90
+
91
+ // src/ComponentRenderer.tsx
92
+ import { jsx, jsxs } from "react/jsx-runtime";
27
93
  function renderMarkdown(content) {
28
94
  const raw = marked.parse(content != null ? content : "", { async: false });
29
95
  return DOMPurify.sanitize(raw, { USE_PROFILES: { html: true } });
30
96
  }
97
+ var scaler = (unit) => (n) => unit === "rem" ? `${n / 16}rem` : `${n}px`;
98
+ var PX = scaler("px");
31
99
  var HALF_COMPONENTS = /* @__PURE__ */ new Set(["TestimonialCard"]);
32
100
  var flexItems = (a) => a === "center" ? "center" : a === "right" ? "flex-end" : "flex-start";
33
- function HeroStatement({ headline, subheading, align = "left" }) {
34
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: "24px", paddingBottom: "24px" }, children: [
35
- /* @__PURE__ */ jsx("h2", { style: { fontSize: "32px", fontWeight: 700, letterSpacing: "-0.025em", lineHeight: 1.2, color: "var(--sp-text)", margin: "0 0 12px" }, children: headline }),
36
- subheading && /* @__PURE__ */ jsx("p", { style: { fontSize: "18px", color: "var(--sp-text-sub)", lineHeight: 1.6, maxWidth: "672px", margin: 0 }, children: subheading })
101
+ function AnnouncementBar({ text, link, unit = "px" }) {
102
+ const u = scaler(unit);
103
+ if (!text) return null;
104
+ return /* @__PURE__ */ jsxs("div", { style: {
105
+ display: "flex",
106
+ alignItems: "center",
107
+ justifyContent: "center",
108
+ gap: u(10),
109
+ flexWrap: "wrap",
110
+ textAlign: "center",
111
+ background: "var(--sp-accent-bg)",
112
+ border: "1px solid var(--sp-accent-border)",
113
+ borderRadius: u(8),
114
+ padding: `${u(8)} ${u(16)}`,
115
+ fontSize: u(14),
116
+ lineHeight: 1.4,
117
+ color: "var(--sp-text-sub)"
118
+ }, children: [
119
+ /* @__PURE__ */ jsx("span", { children: text }),
120
+ (link == null ? void 0 : link.href) && (link == null ? void 0 : link.label) && /* @__PURE__ */ jsxs("a", { href: link.href, style: { color: "var(--sp-accent)", fontWeight: 600, textDecoration: "none", whiteSpace: "nowrap" }, children: [
121
+ link.label,
122
+ " \u2192"
123
+ ] })
124
+ ] });
125
+ }
126
+ function HeroStatement({ headline, subheading, align = "left", unit = "px" }) {
127
+ const u = scaler(unit);
128
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: u(24), paddingBottom: u(24) }, children: [
129
+ /* @__PURE__ */ 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 }),
130
+ subheading && /* @__PURE__ */ jsx("p", { style: { fontSize: u(18), color: "var(--sp-text-sub)", lineHeight: 1.6, maxWidth: u(672), margin: 0 }, children: subheading })
37
131
  ] });
38
132
  }
39
133
  var ATOM_LAYOUT_DEFAULT = ["title", "content", "stats", "cta"];
40
- function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl }) {
134
+ var ATOM_CLAMP_LINES = 4;
135
+ var clampStyle = {
136
+ display: "-webkit-box",
137
+ WebkitLineClamp: ATOM_CLAMP_LINES,
138
+ WebkitBoxOrient: "vertical",
139
+ overflow: "hidden"
140
+ };
141
+ function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl, full, unit = "px" }) {
142
+ const u = scaler(unit);
41
143
  const order = layout && layout.length ? layout : ATOM_LAYOUT_DEFAULT;
42
144
  const element = (name) => {
43
- if (name === "title") return title ? /* @__PURE__ */ jsx("h3", { style: { fontSize: "16px", fontWeight: 600, color: "var(--sp-text)", margin: "0 0 12px" }, children: title }, "title") : null;
44
- if (name === "content") return content ? /* @__PURE__ */ jsx("div", { className: "sp-prose", style: { fontSize: "15px", color: "var(--sp-text-sub)", lineHeight: 1.6 }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
45
- if (name === "stats") return stats.length ? /* @__PURE__ */ 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__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "2px" }, children: [
46
- /* @__PURE__ */ jsx("span", { style: { fontSize: "22px", fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
47
- /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
145
+ if (name === "title") return title ? /* @__PURE__ */ jsx("h3", { style: { fontSize: u(16), fontWeight: 600, color: "var(--sp-text)", margin: `0 0 ${u(12)}` }, children: title }, "title") : null;
146
+ if (name === "content") return content ? /* @__PURE__ */ 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;
147
+ if (name === "stats") return stats.length ? /* @__PURE__ */ 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__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: u(2) }, children: [
148
+ /* @__PURE__ */ jsx("span", { style: { fontSize: u(22), fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
149
+ /* @__PURE__ */ jsx("span", { style: { fontSize: u(11), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
48
150
  ] }, i)) }, "stats") : null;
49
- if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ 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: [
151
+ if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ 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: [
50
152
  ctaText,
51
153
  " \u2192"
52
154
  ] }, "cta") : null;
53
155
  return null;
54
156
  };
55
- return /* @__PURE__ */ jsx("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "24px", height: "100%" }, children: order.map(element) });
157
+ return /* @__PURE__ */ 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) });
56
158
  }
57
- function StatGrid({ stats = [], caption }) {
159
+ function StatGrid({ stats = [], caption, unit = "px" }) {
160
+ const u = scaler(unit);
58
161
  const cols = stats.length === 2 ? 2 : stats.length === 3 ? 3 : 4;
59
162
  return /* @__PURE__ */ jsxs("div", { children: [
60
- /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: "12px" }, children: stats.map(({ value, label }) => /* @__PURE__ */ 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: [
61
- /* @__PURE__ */ jsx("p", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", margin: "0 0 12px", lineHeight: 1.3 }, children: label }),
62
- /* @__PURE__ */ jsx("p", { style: { fontSize: "20px", fontWeight: 500, color: "var(--sp-text)", fontVariantNumeric: "tabular-nums", lineHeight: 1, margin: 0 }, children: value })
163
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: u(12) }, children: stats.map(({ value, label }) => /* @__PURE__ */ 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: [
164
+ /* @__PURE__ */ 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 }),
165
+ /* @__PURE__ */ jsx("p", { style: { fontSize: u(20), fontWeight: 500, color: "var(--sp-text)", fontVariantNumeric: "tabular-nums", lineHeight: 1, margin: 0 }, children: value })
63
166
  ] }, label)) }),
64
- caption && /* @__PURE__ */ jsx("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", margin: "12px 0 0" }, children: caption })
167
+ caption && /* @__PURE__ */ jsx("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(12)} 0 0` }, children: caption })
65
168
  ] });
66
169
  }
67
- function CodeBlock({ code, lang = "typescript", caption }) {
68
- return /* @__PURE__ */ jsxs("div", { style: { borderRadius: "12px", overflow: "hidden", border: "1px solid var(--sp-border)" }, children: [
69
- /* @__PURE__ */ jsxs("div", { style: { background: "var(--sp-bg)", padding: "10px 16px", display: "flex", alignItems: "center", justifyContent: "space-between", borderBottom: "1px solid var(--sp-border)" }, children: [
70
- /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--sp-text-muted)", fontFamily: "monospace" }, children: lang }),
71
- caption && /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--sp-text-muted)" }, children: caption })
170
+ function CodeBlock({ code, lang = "typescript", caption, unit = "px" }) {
171
+ const u = scaler(unit);
172
+ return /* @__PURE__ */ jsxs("div", { style: { borderRadius: u(12), overflow: "hidden", border: "1px solid var(--sp-border)" }, children: [
173
+ /* @__PURE__ */ 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: [
174
+ /* @__PURE__ */ jsx("span", { style: { fontSize: u(12), color: "var(--sp-text-muted)", fontFamily: "monospace" }, children: lang }),
175
+ caption && /* @__PURE__ */ jsx("span", { style: { fontSize: u(12), color: "var(--sp-text-muted)" }, children: caption })
72
176
  ] }),
73
- /* @__PURE__ */ 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 })
177
+ /* @__PURE__ */ 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 })
74
178
  ] });
75
179
  }
76
- function ComparisonTable({ brand = "Us", competitor, rows = [] }) {
77
- return /* @__PURE__ */ jsx("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", fontSize: "14px", borderCollapse: "collapse" }, children: [
180
+ function ComparisonTable({ brand = "Us", competitor, rows = [], unit = "px" }) {
181
+ const u = scaler(unit);
182
+ const cell = { textAlign: "left", padding: `${u(12)} ${u(16)}` };
183
+ return /* @__PURE__ */ jsx("div", { style: { border: "1px solid var(--sp-border)", borderRadius: u(12), overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", fontSize: u(14), borderCollapse: "collapse" }, children: [
78
184
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { style: { borderBottom: "1px solid var(--sp-border)", background: "var(--sp-bg)" }, children: [
79
- /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "12px 16px", color: "var(--sp-text-muted)", fontWeight: 500, fontSize: "12px", width: "33%" } }),
80
- /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "12px 16px", fontWeight: 600, color: "var(--sp-accent)", fontSize: "12px" }, children: brand }),
81
- /* @__PURE__ */ jsx("th", { style: { textAlign: "left", padding: "12px 16px", color: "var(--sp-text-muted)", fontWeight: 500, fontSize: "12px" }, children: competitor })
185
+ /* @__PURE__ */ jsx("th", { style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12), width: "33%" } }),
186
+ /* @__PURE__ */ jsx("th", { scope: "col", style: { ...cell, fontWeight: 600, color: "var(--sp-accent)", fontSize: u(12) }, children: brand }),
187
+ /* @__PURE__ */ jsx("th", { scope: "col", style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12) }, children: competitor })
82
188
  ] }) }),
83
189
  /* @__PURE__ */ jsx("tbody", { children: rows.map(({ label, spectare, them }) => /* @__PURE__ */ jsxs("tr", { style: { borderBottom: "1px solid var(--sp-border)" }, children: [
84
- /* @__PURE__ */ jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
85
- /* @__PURE__ */ jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
86
- /* @__PURE__ */ jsx("td", { style: { padding: "12px 16px", fontSize: "13px", color: "var(--sp-text-muted)" }, children: them })
190
+ /* @__PURE__ */ jsx("th", { scope: "row", style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
191
+ /* @__PURE__ */ jsx("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
192
+ /* @__PURE__ */ jsx("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)" }, children: them })
87
193
  ] }, label)) })
88
194
  ] }) }) });
89
195
  }
90
- function FeatureList({ items = [] }) {
91
- return /* @__PURE__ */ jsx("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "24px" }, children: /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "16px" }, children: items.map(({ text, sub }) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: "12px" }, children: [
92
- /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-accent)", flexShrink: 0, fontSize: "15px", marginTop: "2px" }, children: "\u2713" }),
196
+ function FeatureList({ items = [], unit = "px" }) {
197
+ const u = scaler(unit);
198
+ return /* @__PURE__ */ jsx("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(24) }, children: /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: u(16) }, children: items.map(({ text, sub }) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: u(12) }, children: [
199
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-accent)", flexShrink: 0, fontSize: u(15), marginTop: u(2) }, children: "\u2713" }),
93
200
  /* @__PURE__ */ jsxs("div", { children: [
94
- /* @__PURE__ */ jsx("p", { style: { fontSize: "15px", fontWeight: 500, color: "var(--sp-text)", lineHeight: 1.4, margin: 0 }, children: text }),
95
- sub && /* @__PURE__ */ jsx("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", lineHeight: 1.5, margin: "4px 0 0" }, children: sub })
201
+ /* @__PURE__ */ jsx("p", { style: { fontSize: u(15), fontWeight: 500, color: "var(--sp-text)", lineHeight: 1.4, margin: 0 }, children: text }),
202
+ sub && /* @__PURE__ */ jsx("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", lineHeight: 1.5, margin: `${u(4)} 0 0` }, children: sub })
96
203
  ] })
97
204
  ] }, text)) }) });
98
205
  }
99
- function TestimonialCard({ quote, name, role, company }) {
100
- return /* @__PURE__ */ 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: [
101
- /* @__PURE__ */ jsxs("p", { style: { fontSize: "15px", color: "var(--sp-text-sub)", lineHeight: 1.6, flex: 1, margin: 0 }, children: [
102
- /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: "18px", lineHeight: 1, marginRight: "4px" }, children: '"' }),
206
+ function TestimonialCard({ quote, name, role, company, unit = "px" }) {
207
+ const u = scaler(unit);
208
+ return /* @__PURE__ */ 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: [
209
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: u(15), color: "var(--sp-text-sub)", lineHeight: 1.6, flex: 1, margin: 0 }, children: [
210
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: u(18), lineHeight: 1, marginRight: u(4) }, children: '"' }),
103
211
  quote,
104
- /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: "18px", lineHeight: 1, marginLeft: "4px" }, children: '"' })
212
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--sp-text-dim)", fontSize: u(18), lineHeight: 1, marginLeft: u(4) }, children: '"' })
105
213
  ] }),
106
- /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid var(--sp-border)", paddingTop: "16px" }, children: [
107
- /* @__PURE__ */ jsx("p", { style: { fontSize: "14px", fontWeight: 600, color: "var(--sp-text)", margin: 0 }, children: name }),
108
- /* @__PURE__ */ jsxs("p", { style: { fontSize: "13px", color: "var(--sp-text-muted)", margin: "2px 0 0" }, children: [
214
+ /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid var(--sp-border)", paddingTop: u(16) }, children: [
215
+ /* @__PURE__ */ jsx("p", { style: { fontSize: u(14), fontWeight: 600, color: "var(--sp-text)", margin: 0 }, children: name }),
216
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(2)} 0 0` }, children: [
109
217
  role,
110
218
  company ? `, ${company}` : ""
111
219
  ] })
112
220
  ] })
113
221
  ] });
114
222
  }
115
- function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref, align = "left" }) {
223
+ function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref, align = "left", unit = "px" }) {
224
+ const u = scaler(unit);
116
225
  if (!src) return null;
117
- return /* @__PURE__ */ jsxs("div", { style: { borderRadius: "12px", overflow: "hidden", position: "relative", width: "100%", paddingBottom: "56.25%" }, children: [
226
+ return /* @__PURE__ */ jsxs("div", { style: { borderRadius: u(12), overflow: "hidden", position: "relative", width: "100%", paddingBottom: "56.25%" }, children: [
118
227
  /* @__PURE__ */ jsx("img", { src, alt, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" } }),
119
228
  /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.45)" } }),
120
229
  /* @__PURE__ */ 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%)" } }),
121
- /* @__PURE__ */ jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "28px", textAlign: align }, children: [
122
- headline && /* @__PURE__ */ jsx("h2", { style: { fontSize: "24px", fontWeight: 700, color: "#fff", lineHeight: 1.2, margin: "0 0 8px", letterSpacing: "-0.02em" }, children: headline }),
123
- subheading && /* @__PURE__ */ jsx("p", { style: { fontSize: "15px", color: "rgba(255,255,255,0.75)", lineHeight: 1.5, margin: "0 0 20px" }, children: subheading }),
124
- ctaHref && ctaLabel && /* @__PURE__ */ 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 })
230
+ /* @__PURE__ */ jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: u(28), textAlign: align }, children: [
231
+ headline && /* @__PURE__ */ jsx("h2", { style: { fontSize: u(24), fontWeight: 700, color: "#fff", lineHeight: 1.2, margin: `0 0 ${u(8)}`, letterSpacing: "-0.02em" }, children: headline }),
232
+ subheading && /* @__PURE__ */ jsx("p", { style: { fontSize: u(15), color: "rgba(255,255,255,0.75)", lineHeight: 1.5, margin: `0 0 ${u(20)}` }, children: subheading }),
233
+ ctaHref && ctaLabel && /* @__PURE__ */ 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 })
125
234
  ] })
126
235
  ] });
127
236
  }
128
- function CtaStrip({ headline, primary, secondary, align = "center" }) {
129
- return /* @__PURE__ */ jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "32px", textAlign: align, background: "var(--sp-bg)" }, children: [
130
- /* @__PURE__ */ jsx("h3", { style: { fontSize: "24px", fontWeight: 700, color: "var(--sp-text)", margin: "0 0 24px" }, children: headline }),
131
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: flexItems(align), gap: "16px", flexWrap: "wrap" }, children: [
132
- /* @__PURE__ */ jsx("a", { href: primary.href, style: { background: "#fff", color: "#09090b", padding: "10px 24px", borderRadius: "12px", fontWeight: 600, fontSize: "15px", textDecoration: "none" }, children: primary.label }),
133
- secondary && /* @__PURE__ */ jsxs("a", { href: secondary.href, style: { fontSize: "15px", color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
237
+ function CtaStrip({ headline, primary, secondary, align = "center", unit = "px" }) {
238
+ const u = scaler(unit);
239
+ return /* @__PURE__ */ jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(32), textAlign: align, background: "var(--sp-bg)" }, children: [
240
+ /* @__PURE__ */ jsx("h3", { style: { fontSize: u(24), fontWeight: 700, color: "var(--sp-text)", margin: `0 0 ${u(24)}` }, children: headline }),
241
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: flexItems(align), gap: u(16), flexWrap: "wrap" }, children: [
242
+ /* @__PURE__ */ 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 }),
243
+ secondary && /* @__PURE__ */ jsxs("a", { href: secondary.href, style: { fontSize: u(15), color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
134
244
  secondary.label,
135
245
  " \u2192"
136
246
  ] })
137
247
  ] })
138
248
  ] });
139
249
  }
140
- function renderSingle(item, brand, align) {
250
+ function renderSingle(item, brand, align, unit = "px") {
141
251
  var _a, _b;
142
252
  const s = (_a = item.slots) != null ? _a : {};
143
253
  switch (item.component) {
254
+ case "AnnouncementBar":
255
+ return /* @__PURE__ */ jsx(AnnouncementBar, { text: s.text, link: s.link, unit });
144
256
  case "HeroStatement":
145
- return /* @__PURE__ */ jsx(HeroStatement, { headline: s.headline, subheading: s.subheading, align });
257
+ return /* @__PURE__ */ jsx(HeroStatement, { headline: s.headline, subheading: s.subheading, align, unit });
258
+ // A full-width card has nothing beside it, so there is nothing for a clamp to protect.
146
259
  case "AtomCard":
147
- return /* @__PURE__ */ jsx(AtomCard, { title: s.title, content: s.content, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl });
260
+ return /* @__PURE__ */ 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 });
148
261
  case "StatGrid":
149
- return /* @__PURE__ */ jsx(StatGrid, { stats: s.stats, caption: s.caption });
262
+ return /* @__PURE__ */ jsx(StatGrid, { stats: s.stats, caption: s.caption, unit });
150
263
  case "CodeBlock":
151
- return /* @__PURE__ */ jsx(CodeBlock, { code: s.code, lang: s.lang, caption: s.caption });
264
+ return /* @__PURE__ */ jsx(CodeBlock, { code: s.code, lang: s.lang, caption: s.caption, unit });
152
265
  case "ComparisonTable":
153
- return /* @__PURE__ */ jsx(ComparisonTable, { brand: (_b = s.brand) != null ? _b : brand, competitor: s.competitor, rows: s.rows });
266
+ return /* @__PURE__ */ jsx(ComparisonTable, { brand: (_b = s.brand) != null ? _b : brand, competitor: s.competitor, rows: s.rows, unit });
154
267
  case "FeatureList":
155
- return /* @__PURE__ */ jsx(FeatureList, { items: s.items });
268
+ return /* @__PURE__ */ jsx(FeatureList, { items: s.items, unit });
156
269
  case "TestimonialCard":
157
- return /* @__PURE__ */ jsx(TestimonialCard, { quote: s.quote, name: s.name, role: s.role, company: s.company });
270
+ return /* @__PURE__ */ jsx(TestimonialCard, { quote: s.quote, name: s.name, role: s.role, company: s.company, unit });
158
271
  case "ImageCtaHero":
159
- return /* @__PURE__ */ jsx(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref, align });
272
+ return /* @__PURE__ */ jsx(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref, align, unit });
160
273
  case "CtaStrip":
161
- return /* @__PURE__ */ jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align });
162
- default:
163
- return null;
274
+ return /* @__PURE__ */ jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align, unit });
275
+ // An unrecognised component name used to render nothing at all. A component needs a
276
+ // renderer here plus spectare.js plus the WordPress plugin, so missing one made its content
277
+ // silently absent on that surface only: no error, no warning, just a shorter page that
278
+ // looks deliberate. That is the failure mode to remove before the registry grows.
279
+ //
280
+ // Falls back to AtomCard when the slots carry something readable, since almost every
281
+ // component is built from an atom and title/content is the shape they share. When there
282
+ // is genuinely nothing to show it still renders nothing, because an empty card is worse
283
+ // than a gap, but the warning fires either way.
284
+ default: {
285
+ console.warn(`[spectare] ComponentRenderer: no renderer for "${item.component}", falling back to AtomCard`);
286
+ if (!s.title && !s.content) return null;
287
+ return /* @__PURE__ */ jsx(AtomCard, { title: s.title, content: s.content, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: true, unit });
288
+ }
164
289
  }
165
290
  }
166
291
  function resolvedSize(item) {
167
292
  if (item.size) return item.size;
168
293
  return HALF_COMPONENTS.has(item.component) ? "half" : "full";
169
294
  }
295
+ function pairable(a, b) {
296
+ const catOf = (x) => COMPONENT_CATEGORIES[x.component];
297
+ const ca = catOf(a);
298
+ const cb = catOf(b);
299
+ return ca !== void 0 && ca === cb;
300
+ }
170
301
  function buildLayout(assembly) {
171
302
  const groups = [];
172
303
  let i = 0;
173
304
  while (i < assembly.length) {
174
305
  const cur = assembly[i];
175
306
  const next = assembly[i + 1];
176
- if (resolvedSize(cur) === "half" && next && resolvedSize(next) === "half") {
307
+ if (resolvedSize(cur) === "half" && next && resolvedSize(next) === "half" && pairable(cur, next)) {
177
308
  groups.push({ type: "pair", items: [cur, next] });
178
309
  i += 2;
179
310
  } else if (resolvedSize(cur) === "half") {
@@ -187,44 +318,60 @@ function buildLayout(assembly) {
187
318
  return groups;
188
319
  }
189
320
  function XrayBadge({ name, size }) {
190
- return /* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: "8px", left: "8px", zIndex: 10, display: "flex", alignItems: "center", gap: "6px", pointerEvents: "none", userSelect: "none" }, children: [
191
- /* @__PURE__ */ 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 }),
192
- /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
321
+ return /* @__PURE__ */ 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: [
322
+ /* @__PURE__ */ 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 }),
323
+ /* @__PURE__ */ jsx("span", { style: { fontSize: PX(10), fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
193
324
  ] });
194
325
  }
195
- var PAIR_STYLE = ".sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:640px){.sp-pair{grid-template-columns:1fr}}";
196
- function ComponentRenderer({ assembly, brand, xray = false, zone, align }) {
326
+ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent, unit = "px", renderOverride, renderItem, rootProps }) {
327
+ const u = scaler(unit);
197
328
  const filtered = zone ? filterAssemblyByZone(assembly, zone) : assembly;
198
329
  const layout = buildLayout(filtered);
199
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "20px", fontSize: "16px" }, children: [
200
- /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: PAIR_STYLE } }),
330
+ const { className: extraClass, style: extraStyle, ...restRoot } = rootProps != null ? rootProps : {};
331
+ const renderOne = (item) => {
332
+ const override = renderOverride == null ? void 0 : renderOverride(item);
333
+ const node = override === void 0 ? renderSingle(item, brand, align, unit) : override;
334
+ return renderItem ? renderItem(item, node) : node;
335
+ };
336
+ return /* @__PURE__ */ jsxs("div", { className: extraClass ? `${SP_ROOT} ${extraClass}` : SP_ROOT, ...restRoot, style: { ...extraStyle, display: "flex", flexDirection: "column", gap: u(20), ...unit === "px" ? { fontSize: "16px" } : {} }, children: [
337
+ /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: buildTokenCss({ accent, unit }) } }),
201
338
  layout.map((group, i) => {
202
- if (group.type === "full") {
203
- return /* @__PURE__ */ jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
204
- xray && /* @__PURE__ */ jsx(XrayBadge, { name: group.item.component, size: "full" }),
205
- renderSingle(group.item, brand, align)
206
- ] }, i);
207
- }
208
339
  if (group.type === "pair") {
209
340
  return /* @__PURE__ */ jsxs("div", { className: "sp-pair", children: [
210
341
  /* @__PURE__ */ jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
211
342
  xray && /* @__PURE__ */ jsx(XrayBadge, { name: group.items[0].component, size: "half" }),
212
- renderSingle(group.items[0], brand, align)
343
+ renderOne(group.items[0])
213
344
  ] }),
214
345
  /* @__PURE__ */ jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
215
346
  xray && /* @__PURE__ */ jsx(XrayBadge, { name: group.items[1].component, size: "half" }),
216
- renderSingle(group.items[1], brand, align)
347
+ renderOne(group.items[1])
217
348
  ] })
218
349
  ] }, i);
219
350
  }
220
351
  return /* @__PURE__ */ jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
221
- xray && /* @__PURE__ */ jsx(XrayBadge, { name: group.item.component, size: "full" }),
222
- renderSingle(group.item, brand, align)
352
+ xray && /* @__PURE__ */ jsx(XrayBadge, { name: group.item.component, size: group.type === "half-solo" ? "half" : "full" }),
353
+ renderOne(group.item)
223
354
  ] }, i);
224
355
  })
225
356
  ] });
226
357
  }
227
- 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}`;
358
+
359
+ // src/PersonalisedSection.tsx
360
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
361
+ var VARIANT_KEY = (orgSlug) => `_spectare_variant_${orgSlug}`;
362
+ function readVariantId(orgSlug) {
363
+ try {
364
+ return sessionStorage.getItem(VARIANT_KEY(orgSlug));
365
+ } catch (e) {
366
+ return null;
367
+ }
368
+ }
369
+ function writeVariantId(orgSlug, variantId) {
370
+ try {
371
+ sessionStorage.setItem(VARIANT_KEY(orgSlug), variantId);
372
+ } catch (e) {
373
+ }
374
+ }
228
375
  function buildSummary(pageHint, orgSlug) {
229
376
  const parts = [pageHint];
230
377
  const referrer = document.referrer;
@@ -260,11 +407,11 @@ function readVisitPath(orgSlug) {
260
407
  }
261
408
  }
262
409
  function Shimmer() {
263
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
264
- /* @__PURE__ */ 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" } }),
265
- /* @__PURE__ */ 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" } }),
266
- /* @__PURE__ */ 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" } }),
267
- /* @__PURE__ */ 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" } })
410
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
411
+ /* @__PURE__ */ jsx2("div", { style: { height: "20px", background: "rgba(39,39,42,0.6)", borderRadius: "8px", width: "75%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
412
+ /* @__PURE__ */ jsx2("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "100%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
413
+ /* @__PURE__ */ jsx2("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "83%", animation: "sp-pulse 1.5s ease-in-out infinite" } }),
414
+ /* @__PURE__ */ jsx2("div", { style: { height: "14px", background: "rgba(39,39,42,0.4)", borderRadius: "8px", width: "67%", animation: "sp-pulse 1.5s ease-in-out infinite" } })
268
415
  ] });
269
416
  }
270
417
  function PersonalisedSection({
@@ -276,32 +423,39 @@ function PersonalisedSection({
276
423
  style,
277
424
  initialAssembly,
278
425
  initialIntent,
426
+ initialVariantId,
427
+ directSummary,
279
428
  zone,
280
- align
429
+ align,
430
+ accent
281
431
  }) {
282
432
  const [assembly, setAssembly] = useState(initialAssembly != null ? initialAssembly : []);
283
433
  const [loading, setLoading] = useState(!initialAssembly);
284
434
  const [orgName, setOrgName] = useState(void 0);
435
+ const [debugMode, setDebugMode] = useState(false);
436
+ const [debugIntent, setDebugIntent] = useState(null);
285
437
  const tokenRef = useRef(null);
286
438
  const conversionFiredRef = useRef(false);
439
+ useEffect(() => {
440
+ setDebugMode(new URLSearchParams(window.location.search).has("spectare_debug"));
441
+ if (initialIntent) setDebugIntent(initialIntent);
442
+ }, [initialIntent]);
443
+ useEffect(() => {
444
+ if (initialVariantId) writeVariantId(orgSlug, initialVariantId);
445
+ }, [orgSlug, initialVariantId]);
287
446
  useEffect(() => {
288
447
  let cancelled = false;
289
448
  async function load() {
290
- var _a, _b, _c, _d, _e, _f, _g, _h;
449
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
291
450
  try {
292
- const p = new URLSearchParams(window.location.search);
293
- const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
294
- var _a2;
295
- return (_a2 = p.get(k)) != null ? _a2 : "";
296
- }).filter(Boolean).join("_");
297
- const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
298
- let token = sessionStorage.getItem(tokenKey);
299
- if (!token) {
300
- const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
451
+ let token = null;
452
+ if (directSummary) {
453
+ setLoading(true);
454
+ setAssembly([]);
301
455
  const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
302
456
  method: "POST",
303
457
  headers: { "Content-Type": "application/json" },
304
- body: JSON.stringify({ summary })
458
+ body: JSON.stringify({ summary: directSummary, direct: true })
305
459
  });
306
460
  if (!res2.ok || cancelled) {
307
461
  setLoading(false);
@@ -309,8 +463,30 @@ function PersonalisedSection({
309
463
  }
310
464
  const data = await res2.json();
311
465
  token = (_a = data.token) != null ? _a : null;
312
- if (token) sessionStorage.setItem(tokenKey, token);
313
- if (data.orgName) setOrgName(data.orgName);
466
+ } else {
467
+ const p = new URLSearchParams(window.location.search);
468
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
469
+ var _a2;
470
+ return (_a2 = p.get(k)) != null ? _a2 : "";
471
+ }).filter(Boolean).join("_");
472
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
473
+ token = sessionStorage.getItem(tokenKey);
474
+ if (!token) {
475
+ const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
476
+ const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
477
+ method: "POST",
478
+ headers: { "Content-Type": "application/json" },
479
+ body: JSON.stringify({ summary })
480
+ });
481
+ if (!res2.ok || cancelled) {
482
+ setLoading(false);
483
+ return;
484
+ }
485
+ const data = await res2.json();
486
+ token = (_b = data.token) != null ? _b : null;
487
+ if (token) sessionStorage.setItem(tokenKey, token);
488
+ if (data.orgName) setOrgName(data.orgName);
489
+ }
314
490
  }
315
491
  if (!token || cancelled) {
316
492
  setLoading(false);
@@ -320,9 +496,19 @@ function PersonalisedSection({
320
496
  let res = await fetch(`${baseUrl}/api/assemble/components`, {
321
497
  method: "POST",
322
498
  headers: { "Content-Type": "application/json" },
323
- body: JSON.stringify({ token, orgSlug })
499
+ // Direct: flag it so the server cost-caps the build, and omit the seen-variant
500
+ // exclusion so it can read the shared cache instead of forcing a fresh build.
501
+ body: JSON.stringify(
502
+ directSummary ? { token, orgSlug, direct: true } : { token, orgSlug, excludeVariantId: readVariantId(orgSlug) }
503
+ )
324
504
  });
325
- if (res.status === 401 && !cancelled) {
505
+ if (res.status === 401 && !cancelled && !directSummary) {
506
+ const p = new URLSearchParams(window.location.search);
507
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
508
+ var _a2;
509
+ return (_a2 = p.get(k)) != null ? _a2 : "";
510
+ }).filter(Boolean).join("_");
511
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
326
512
  sessionStorage.removeItem(tokenKey);
327
513
  const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
328
514
  const qRes = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
@@ -335,7 +521,7 @@ function PersonalisedSection({
335
521
  return;
336
522
  }
337
523
  const qData = await qRes.json();
338
- token = (_b = qData.token) != null ? _b : null;
524
+ token = (_c = qData.token) != null ? _c : null;
339
525
  if (token) {
340
526
  sessionStorage.setItem(tokenKey, token);
341
527
  tokenRef.current = token;
@@ -347,14 +533,14 @@ function PersonalisedSection({
347
533
  res = await fetch(`${baseUrl}/api/assemble/components`, {
348
534
  method: "POST",
349
535
  headers: { "Content-Type": "application/json" },
350
- body: JSON.stringify({ token, orgSlug })
536
+ body: JSON.stringify({ token, orgSlug, excludeVariantId: readVariantId(orgSlug) })
351
537
  });
352
538
  }
353
539
  if (!res.ok || cancelled) {
354
540
  setLoading(false);
355
541
  return;
356
542
  }
357
- const contentType = (_c = res.headers.get("content-type")) != null ? _c : "";
543
+ const contentType = (_d = res.headers.get("content-type")) != null ? _d : "";
358
544
  if (contentType.includes("text/event-stream")) {
359
545
  const reader = res.body.getReader();
360
546
  const decoder = new TextDecoder();
@@ -366,13 +552,26 @@ function PersonalisedSection({
366
552
  if (done || cancelled) break;
367
553
  buf += decoder.decode(value, { stream: true });
368
554
  const lines = buf.split("\n");
369
- buf = (_d = lines.pop()) != null ? _d : "";
555
+ buf = (_e = lines.pop()) != null ? _e : "";
370
556
  for (const line of lines) {
371
557
  if (!line.startsWith("data: ")) continue;
372
558
  const payload = line.slice(6).trim();
373
559
  if (payload === "[DONE]") break;
374
560
  try {
375
- const comp = JSON.parse(payload);
561
+ const parsed = JSON.parse(payload);
562
+ if (parsed._variant && typeof parsed._variant === "string") {
563
+ writeVariantId(orgSlug, parsed._variant);
564
+ continue;
565
+ }
566
+ if (parsed._error) {
567
+ if (!loadingCleared) {
568
+ setLoading(false);
569
+ loadingCleared = true;
570
+ }
571
+ continue;
572
+ }
573
+ if (!parsed.component) continue;
574
+ const comp = parsed;
376
575
  if (!loadingCleared) {
377
576
  setLoading(false);
378
577
  loadingCleared = true;
@@ -388,10 +587,14 @@ function PersonalisedSection({
388
587
  }
389
588
  } else {
390
589
  const data = await res.json();
590
+ if (data.variantId) writeVariantId(orgSlug, data.variantId);
391
591
  if (cancelled) return;
392
- const clientAudience = (_f = (_e = data.intent) == null ? void 0 : _e.userContext) == null ? void 0 : _f.audience;
393
- const clientStage = (_h = (_g = data.intent) == null ? void 0 : _g.userContext) == null ? void 0 : _h.stage;
592
+ const clientAudience = (_g = (_f = data.intent) == null ? void 0 : _f.userContext) == null ? void 0 : _g.audience;
593
+ const clientStage = (_i = (_h = data.intent) == null ? void 0 : _h.userContext) == null ? void 0 : _i.stage;
394
594
  const ssrMatches = initialIntent && clientAudience === initialIntent.audience && clientStage === initialIntent.stage;
595
+ if (clientAudience && clientStage) {
596
+ setDebugIntent({ audience: clientAudience, stage: clientStage });
597
+ }
395
598
  if (!ssrMatches && Array.isArray(data.assembly) && data.assembly.length > 0) {
396
599
  setAssembly(data.assembly);
397
600
  }
@@ -405,7 +608,7 @@ function PersonalisedSection({
405
608
  return () => {
406
609
  cancelled = true;
407
610
  };
408
- }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent]);
611
+ }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent, directSummary]);
409
612
  useEffect(() => {
410
613
  if (!assembly.length || conversionFiredRef.current) return;
411
614
  const token = tokenRef.current;
@@ -450,6 +653,7 @@ function PersonalisedSection({
450
653
  token,
451
654
  orgSlug,
452
655
  url,
656
+ variantId: readVariantId(orgSlug),
453
657
  ...atomsShown.length ? { atomsShown } : {},
454
658
  ...visitPath.length > 1 ? { visitPath } : {},
455
659
  ...data.goalName ? { goalName: data.goalName } : {},
@@ -461,13 +665,39 @@ function PersonalisedSection({
461
665
  });
462
666
  }, [assembly, orgSlug, baseUrl]);
463
667
  if (!loading && assembly.length === 0) return null;
464
- return /* @__PURE__ */ jsxs(Fragment, { children: [
465
- /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: CSS_TOKENS } }),
466
- /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: "@keyframes sp-pulse{0%,100%{opacity:1}50%{opacity:0.4}}" } }),
467
- /* @__PURE__ */ jsx("div", { className, style, children: loading && assembly.length === 0 ? /* @__PURE__ */ jsx(Shimmer, {}) : /* @__PURE__ */ jsx(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align }) })
668
+ const debugStyle = debugMode ? { outline: "2px solid rgba(96,165,250,0.5)", borderRadius: "0.5rem", position: "relative", padding: "0.5rem", ...style } : style;
669
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
670
+ /* @__PURE__ */ jsx2("style", { dangerouslySetInnerHTML: { __html: "@keyframes sp-pulse{0%,100%{opacity:1}50%{opacity:0.4}}" } }),
671
+ /* @__PURE__ */ jsxs2("div", { className, style: debugStyle, children: [
672
+ debugMode && /* @__PURE__ */ jsxs2("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: [
673
+ "Personalised by Spectare",
674
+ debugIntent && /* @__PURE__ */ jsxs2("span", { style: { opacity: 0.75 }, children: [
675
+ " ",
676
+ debugIntent.audience,
677
+ "/",
678
+ debugIntent.stage
679
+ ] })
680
+ ] }),
681
+ loading && assembly.length === 0 ? /* @__PURE__ */ jsx2(Shimmer, {}) : /* @__PURE__ */ jsx2(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align, accent })
682
+ ] })
468
683
  ] });
469
684
  }
470
-
471
- export { ComponentRenderer, PersonalisedSection, filterAssemblyByZone };
472
- //# sourceMappingURL=index.js.map
685
+ export {
686
+ AnnouncementBar,
687
+ AtomCard,
688
+ CodeBlock,
689
+ ComparisonTable,
690
+ ComponentRenderer,
691
+ CtaStrip,
692
+ DEFAULT_ACCENT,
693
+ FeatureList,
694
+ HeroStatement,
695
+ ImageCtaHero,
696
+ PersonalisedSection,
697
+ SP_ROOT,
698
+ StatGrid,
699
+ TestimonialCard,
700
+ buildTokenCss,
701
+ filterAssemblyByZone
702
+ };
473
703
  //# sourceMappingURL=index.js.map