@spectare-personalisation/react 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -13,6 +13,7 @@ var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
13
13
 
14
14
  // src/types.ts
15
15
  var COMPONENT_ZONES = {
16
+ AnnouncementBar: "announce",
16
17
  HeroStatement: "hero",
17
18
  ImageCtaHero: "hero",
18
19
  AtomCard: "body",
@@ -30,23 +31,102 @@ function filterAssemblyByZone(assembly, zone) {
30
31
  return effectiveZone === zone;
31
32
  });
32
33
  }
34
+
35
+ // src/tokens.ts
36
+ var DEFAULT_ACCENT = "#60a5fa";
37
+ var SP_ROOT = "sp-root";
38
+ function palette(accent) {
39
+ return {
40
+ "--sp-accent": accent,
41
+ "--sp-accent-bg": `color-mix(in srgb,${accent} 10%,transparent)`,
42
+ "--sp-accent-border": `color-mix(in srgb,${accent} 20%,transparent)`,
43
+ "--sp-bg": "#18181b",
44
+ "--sp-surface": "#09090b",
45
+ "--sp-border": "#27272a",
46
+ "--sp-text": "#f4f4f5",
47
+ "--sp-text-sub": "#d4d4d8",
48
+ "--sp-text-strong": "#e4e4e7",
49
+ "--sp-text-muted": "#82828b",
50
+ "--sp-text-dim": "#6b6b74",
51
+ "--sp-text-body": "#a1a1aa",
52
+ "--sp-positive": "#6ee7b7",
53
+ "--sp-cta-bg": "#fff",
54
+ "--sp-cta-text": "#09090b",
55
+ "--sp-divider": "rgba(255,255,255,0.06)",
56
+ "--sp-code-bg": "rgba(255,255,255,0.07)",
57
+ "--sp-code-border": "rgba(255,255,255,0.08)",
58
+ "--sp-pre-bg": "rgba(0,0,0,0.4)"
59
+ };
60
+ }
61
+ function rules(unit) {
62
+ const gap = unit === "px" ? "20px" : "1.25rem";
63
+ return [
64
+ ".sp-root .sp-prose p{margin-bottom:.625rem}",
65
+ ".sp-root .sp-prose p:last-child{margin-bottom:0}",
66
+ ".sp-root .sp-prose ul{list-style:disc;padding-left:1.25rem;margin-bottom:.625rem}",
67
+ ".sp-root .sp-prose ol{list-style:decimal;padding-left:1.25rem;margin-bottom:.625rem}",
68
+ ".sp-root .sp-prose li{margin-bottom:.25rem}",
69
+ ".sp-root .sp-prose strong{color:var(--sp-text-strong);font-weight:600}",
70
+ ".sp-root .sp-prose h2,.sp-root .sp-prose h3{color:var(--sp-text);font-weight:600;margin:.875rem 0 .375rem;font-size:1rem}",
71
+ ".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)}",
72
+ ".sp-root .sp-prose blockquote{border-left:3px solid var(--sp-border);padding-left:1rem;color:var(--sp-text-muted);margin:.75rem 0}",
73
+ `.sp-root .sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:${gap}}`,
74
+ "@media(max-width:640px){.sp-root .sp-pair{grid-template-columns:1fr}}"
75
+ ].join("");
76
+ }
77
+ function buildTokenCss({ accent = DEFAULT_ACCENT, unit = "px", scope = `.${SP_ROOT}` } = {}) {
78
+ const decls = Object.entries(palette(accent)).map(([k, v]) => `${k}:${v}`).join(";");
79
+ return `${scope}{${decls}}${rules(unit)}`;
80
+ }
33
81
  function renderMarkdown(content) {
34
82
  const raw = marked.marked.parse(content != null ? content : "", { async: false });
35
83
  return DOMPurify__default.default.sanitize(raw, { USE_PROFILES: { html: true } });
36
84
  }
37
85
  var HALF_COMPONENTS = /* @__PURE__ */ new Set(["TestimonialCard"]);
38
- function HeroStatement({ headline, subheading }) {
39
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { paddingTop: "24px", paddingBottom: "24px" }, children: [
86
+ var flexItems = (a) => a === "center" ? "center" : a === "right" ? "flex-end" : "flex-start";
87
+ function AnnouncementBar({ text, link }) {
88
+ if (!text) return null;
89
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
90
+ display: "flex",
91
+ alignItems: "center",
92
+ justifyContent: "center",
93
+ gap: "10px",
94
+ flexWrap: "wrap",
95
+ textAlign: "center",
96
+ background: "var(--sp-accent-bg)",
97
+ border: "1px solid var(--sp-accent-border)",
98
+ borderRadius: "8px",
99
+ padding: "8px 16px",
100
+ fontSize: "14px",
101
+ lineHeight: 1.4,
102
+ color: "var(--sp-text-sub)"
103
+ }, children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: text }),
105
+ (link == null ? void 0 : link.href) && (link == null ? void 0 : link.label) && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: link.href, style: { color: "var(--sp-accent)", fontWeight: 600, textDecoration: "none", whiteSpace: "nowrap" }, children: [
106
+ link.label,
107
+ " \u2192"
108
+ ] })
109
+ ] });
110
+ }
111
+ function HeroStatement({ headline, subheading, align = "left" }) {
112
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: "24px", paddingBottom: "24px" }, children: [
40
113
  /* @__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 }),
41
114
  subheading && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "18px", color: "var(--sp-text-sub)", lineHeight: 1.6, maxWidth: "672px", margin: 0 }, children: subheading })
42
115
  ] });
43
116
  }
44
117
  var ATOM_LAYOUT_DEFAULT = ["title", "content", "stats", "cta"];
45
- function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl }) {
118
+ var ATOM_CLAMP_LINES = 4;
119
+ var clampStyle = {
120
+ display: "-webkit-box",
121
+ WebkitLineClamp: ATOM_CLAMP_LINES,
122
+ WebkitBoxOrient: "vertical",
123
+ overflow: "hidden"
124
+ };
125
+ function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl, full }) {
46
126
  const order = layout && layout.length ? layout : ATOM_LAYOUT_DEFAULT;
47
127
  const element = (name) => {
48
128
  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;
49
- 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;
129
+ if (name === "content") return content ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sp-prose", style: { fontSize: "15px", color: "var(--sp-text-sub)", lineHeight: 1.6, ...full ? {} : clampStyle }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
50
130
  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: [
51
131
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "22px", fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
52
132
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
@@ -117,24 +197,24 @@ function TestimonialCard({ quote, name, role, company }) {
117
197
  ] })
118
198
  ] });
119
199
  }
120
- function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref }) {
200
+ function ImageCtaHero({ src, alt = "", headline, subheading, ctaLabel, ctaHref, align = "left" }) {
121
201
  if (!src) return null;
122
202
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderRadius: "12px", overflow: "hidden", position: "relative", width: "100%", paddingBottom: "56.25%" }, children: [
123
203
  /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" } }),
124
204
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.45)" } }),
125
205
  /* @__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%)" } }),
126
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "28px" }, children: [
206
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "28px", textAlign: align }, children: [
127
207
  headline && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "24px", fontWeight: 700, color: "#fff", lineHeight: 1.2, margin: "0 0 8px", letterSpacing: "-0.02em" }, children: headline }),
128
208
  subheading && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "15px", color: "rgba(255,255,255,0.75)", lineHeight: 1.5, margin: "0 0 20px" }, children: subheading }),
129
209
  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 })
130
210
  ] })
131
211
  ] });
132
212
  }
133
- function CtaStrip({ headline, primary, secondary }) {
134
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "32px", textAlign: "center", background: "var(--sp-bg)" }, children: [
213
+ function CtaStrip({ headline, primary, secondary, align = "center" }) {
214
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "32px", textAlign: align, background: "var(--sp-bg)" }, children: [
135
215
  /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "24px", fontWeight: 700, color: "var(--sp-text)", margin: "0 0 24px" }, children: headline }),
136
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "16px", flexWrap: "wrap" }, children: [
137
- /* @__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 }),
216
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: flexItems(align), gap: "16px", flexWrap: "wrap" }, children: [
217
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: primary.href, style: { background: "var(--sp-cta-bg)", color: "var(--sp-cta-text)", padding: "10px 24px", borderRadius: "12px", fontWeight: 600, fontSize: "15px", textDecoration: "none" }, children: primary.label }),
138
218
  secondary && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: secondary.href, style: { fontSize: "15px", color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
139
219
  secondary.label,
140
220
  " \u2192"
@@ -142,14 +222,17 @@ function CtaStrip({ headline, primary, secondary }) {
142
222
  ] })
143
223
  ] });
144
224
  }
145
- function renderSingle(item, brand) {
225
+ function renderSingle(item, brand, align) {
146
226
  var _a, _b;
147
227
  const s = (_a = item.slots) != null ? _a : {};
148
228
  switch (item.component) {
229
+ case "AnnouncementBar":
230
+ return /* @__PURE__ */ jsxRuntime.jsx(AnnouncementBar, { text: s.text, link: s.link });
149
231
  case "HeroStatement":
150
- return /* @__PURE__ */ jsxRuntime.jsx(HeroStatement, { headline: s.headline, subheading: s.subheading });
232
+ return /* @__PURE__ */ jsxRuntime.jsx(HeroStatement, { headline: s.headline, subheading: s.subheading, align });
233
+ // A full-width card has nothing beside it, so there is nothing for a clamp to protect.
151
234
  case "AtomCard":
152
- return /* @__PURE__ */ jsxRuntime.jsx(AtomCard, { title: s.title, content: s.content, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl });
235
+ return /* @__PURE__ */ jsxRuntime.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" });
153
236
  case "StatGrid":
154
237
  return /* @__PURE__ */ jsxRuntime.jsx(StatGrid, { stats: s.stats, caption: s.caption });
155
238
  case "CodeBlock":
@@ -161,11 +244,23 @@ function renderSingle(item, brand) {
161
244
  case "TestimonialCard":
162
245
  return /* @__PURE__ */ jsxRuntime.jsx(TestimonialCard, { quote: s.quote, name: s.name, role: s.role, company: s.company });
163
246
  case "ImageCtaHero":
164
- return /* @__PURE__ */ jsxRuntime.jsx(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref });
247
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageCtaHero, { src: s.src, alt: s.alt, headline: s.headline, subheading: s.subheading, ctaLabel: s.ctaLabel, ctaHref: s.ctaHref, align });
165
248
  case "CtaStrip":
166
- return /* @__PURE__ */ jsxRuntime.jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary });
167
- default:
168
- return null;
249
+ return /* @__PURE__ */ jsxRuntime.jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align });
250
+ // An unrecognised component name used to render nothing at all. A component needs a
251
+ // renderer in three places plus the WordPress plugin, so missing one made its content
252
+ // silently absent on that surface only: no error, no warning, just a shorter page that
253
+ // looks deliberate. That is the failure mode to remove before the registry grows.
254
+ //
255
+ // Falls back to AtomCard when the slots carry something readable, since almost every
256
+ // component is built from an atom and title/content is the shape they share. When there
257
+ // is genuinely nothing to show it still renders nothing, because an empty card is worse
258
+ // than a gap, but the warning fires either way.
259
+ default: {
260
+ console.warn(`[spectare] ComponentRenderer: no renderer for "${item.component}", falling back to AtomCard`);
261
+ if (!s.title && !s.content) return null;
262
+ return /* @__PURE__ */ jsxRuntime.jsx(AtomCard, { title: s.title, content: s.content, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: true });
263
+ }
169
264
  }
170
265
  }
171
266
  function resolvedSize(item) {
@@ -197,39 +292,51 @@ function XrayBadge({ name, size }) {
197
292
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
198
293
  ] });
199
294
  }
200
- var PAIR_STYLE = ".sp-pair{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:640px){.sp-pair{grid-template-columns:1fr}}";
201
- function ComponentRenderer({ assembly, brand, xray = false, zone }) {
295
+ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent }) {
202
296
  const filtered = zone ? filterAssemblyByZone(assembly, zone) : assembly;
203
297
  const layout = buildLayout(filtered);
204
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "20px", fontSize: "16px" }, children: [
205
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: PAIR_STYLE } }),
298
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: SP_ROOT, style: { display: "flex", flexDirection: "column", gap: "20px", fontSize: "16px" }, children: [
299
+ /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: buildTokenCss({ accent }) } }),
206
300
  layout.map((group, i) => {
207
301
  if (group.type === "full") {
208
302
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
209
303
  xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.item.component, size: "full" }),
210
- renderSingle(group.item, brand)
304
+ renderSingle(group.item, brand, align)
211
305
  ] }, i);
212
306
  }
213
307
  if (group.type === "pair") {
214
308
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sp-pair", children: [
215
309
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
216
310
  xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.items[0].component, size: "half" }),
217
- renderSingle(group.items[0], brand)
311
+ renderSingle(group.items[0], brand, align)
218
312
  ] }),
219
313
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
220
314
  xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.items[1].component, size: "half" }),
221
- renderSingle(group.items[1], brand)
315
+ renderSingle(group.items[1], brand, align)
222
316
  ] })
223
317
  ] }, i);
224
318
  }
225
319
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
226
320
  xray && /* @__PURE__ */ jsxRuntime.jsx(XrayBadge, { name: group.item.component, size: "full" }),
227
- renderSingle(group.item, brand)
321
+ renderSingle(group.item, brand, align)
228
322
  ] }, i);
229
323
  })
230
324
  ] });
231
325
  }
232
- 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}`;
326
+ var VARIANT_KEY = (orgSlug) => `_spectare_variant_${orgSlug}`;
327
+ function readVariantId(orgSlug) {
328
+ try {
329
+ return sessionStorage.getItem(VARIANT_KEY(orgSlug));
330
+ } catch (e) {
331
+ return null;
332
+ }
333
+ }
334
+ function writeVariantId(orgSlug, variantId) {
335
+ try {
336
+ sessionStorage.setItem(VARIANT_KEY(orgSlug), variantId);
337
+ } catch (e) {
338
+ }
339
+ }
233
340
  function buildSummary(pageHint, orgSlug) {
234
341
  const parts = [pageHint];
235
342
  const referrer = document.referrer;
@@ -281,31 +388,39 @@ function PersonalisedSection({
281
388
  style,
282
389
  initialAssembly,
283
390
  initialIntent,
284
- zone
391
+ initialVariantId,
392
+ directSummary,
393
+ zone,
394
+ align,
395
+ accent
285
396
  }) {
286
397
  const [assembly, setAssembly] = react.useState(initialAssembly != null ? initialAssembly : []);
287
398
  const [loading, setLoading] = react.useState(!initialAssembly);
288
399
  const [orgName, setOrgName] = react.useState(void 0);
400
+ const [debugMode, setDebugMode] = react.useState(false);
401
+ const [debugIntent, setDebugIntent] = react.useState(null);
289
402
  const tokenRef = react.useRef(null);
290
403
  const conversionFiredRef = react.useRef(false);
404
+ react.useEffect(() => {
405
+ setDebugMode(new URLSearchParams(window.location.search).has("spectare_debug"));
406
+ if (initialIntent) setDebugIntent(initialIntent);
407
+ }, [initialIntent]);
408
+ react.useEffect(() => {
409
+ if (initialVariantId) writeVariantId(orgSlug, initialVariantId);
410
+ }, [orgSlug, initialVariantId]);
291
411
  react.useEffect(() => {
292
412
  let cancelled = false;
293
413
  async function load() {
294
- var _a, _b, _c, _d, _e, _f, _g, _h;
414
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
295
415
  try {
296
- const p = new URLSearchParams(window.location.search);
297
- const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
298
- var _a2;
299
- return (_a2 = p.get(k)) != null ? _a2 : "";
300
- }).filter(Boolean).join("_");
301
- const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
302
- let token = sessionStorage.getItem(tokenKey);
303
- if (!token) {
304
- const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
416
+ let token = null;
417
+ if (directSummary) {
418
+ setLoading(true);
419
+ setAssembly([]);
305
420
  const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
306
421
  method: "POST",
307
422
  headers: { "Content-Type": "application/json" },
308
- body: JSON.stringify({ summary })
423
+ body: JSON.stringify({ summary: directSummary, direct: true })
309
424
  });
310
425
  if (!res2.ok || cancelled) {
311
426
  setLoading(false);
@@ -313,8 +428,30 @@ function PersonalisedSection({
313
428
  }
314
429
  const data = await res2.json();
315
430
  token = (_a = data.token) != null ? _a : null;
316
- if (token) sessionStorage.setItem(tokenKey, token);
317
- if (data.orgName) setOrgName(data.orgName);
431
+ } else {
432
+ const p = new URLSearchParams(window.location.search);
433
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
434
+ var _a2;
435
+ return (_a2 = p.get(k)) != null ? _a2 : "";
436
+ }).filter(Boolean).join("_");
437
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
438
+ token = sessionStorage.getItem(tokenKey);
439
+ if (!token) {
440
+ const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
441
+ const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
442
+ method: "POST",
443
+ headers: { "Content-Type": "application/json" },
444
+ body: JSON.stringify({ summary })
445
+ });
446
+ if (!res2.ok || cancelled) {
447
+ setLoading(false);
448
+ return;
449
+ }
450
+ const data = await res2.json();
451
+ token = (_b = data.token) != null ? _b : null;
452
+ if (token) sessionStorage.setItem(tokenKey, token);
453
+ if (data.orgName) setOrgName(data.orgName);
454
+ }
318
455
  }
319
456
  if (!token || cancelled) {
320
457
  setLoading(false);
@@ -324,9 +461,19 @@ function PersonalisedSection({
324
461
  let res = await fetch(`${baseUrl}/api/assemble/components`, {
325
462
  method: "POST",
326
463
  headers: { "Content-Type": "application/json" },
327
- body: JSON.stringify({ token, orgSlug })
464
+ // Direct: flag it so the server cost-caps the build, and omit the seen-variant
465
+ // exclusion so it can read the shared cache instead of forcing a fresh build.
466
+ body: JSON.stringify(
467
+ directSummary ? { token, orgSlug, direct: true } : { token, orgSlug, excludeVariantId: readVariantId(orgSlug) }
468
+ )
328
469
  });
329
- if (res.status === 401 && !cancelled) {
470
+ if (res.status === 401 && !cancelled && !directSummary) {
471
+ const p = new URLSearchParams(window.location.search);
472
+ const utmSuffix = ["utm_source", "utm_medium", "utm_campaign", "utm_term"].map((k) => {
473
+ var _a2;
474
+ return (_a2 = p.get(k)) != null ? _a2 : "";
475
+ }).filter(Boolean).join("_");
476
+ const tokenKey = `_spectare_token_${orgSlug}${utmSuffix ? "_" + utmSuffix : ""}`;
330
477
  sessionStorage.removeItem(tokenKey);
331
478
  const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
332
479
  const qRes = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
@@ -339,7 +486,7 @@ function PersonalisedSection({
339
486
  return;
340
487
  }
341
488
  const qData = await qRes.json();
342
- token = (_b = qData.token) != null ? _b : null;
489
+ token = (_c = qData.token) != null ? _c : null;
343
490
  if (token) {
344
491
  sessionStorage.setItem(tokenKey, token);
345
492
  tokenRef.current = token;
@@ -351,14 +498,14 @@ function PersonalisedSection({
351
498
  res = await fetch(`${baseUrl}/api/assemble/components`, {
352
499
  method: "POST",
353
500
  headers: { "Content-Type": "application/json" },
354
- body: JSON.stringify({ token, orgSlug })
501
+ body: JSON.stringify({ token, orgSlug, excludeVariantId: readVariantId(orgSlug) })
355
502
  });
356
503
  }
357
504
  if (!res.ok || cancelled) {
358
505
  setLoading(false);
359
506
  return;
360
507
  }
361
- const contentType = (_c = res.headers.get("content-type")) != null ? _c : "";
508
+ const contentType = (_d = res.headers.get("content-type")) != null ? _d : "";
362
509
  if (contentType.includes("text/event-stream")) {
363
510
  const reader = res.body.getReader();
364
511
  const decoder = new TextDecoder();
@@ -370,13 +517,26 @@ function PersonalisedSection({
370
517
  if (done || cancelled) break;
371
518
  buf += decoder.decode(value, { stream: true });
372
519
  const lines = buf.split("\n");
373
- buf = (_d = lines.pop()) != null ? _d : "";
520
+ buf = (_e = lines.pop()) != null ? _e : "";
374
521
  for (const line of lines) {
375
522
  if (!line.startsWith("data: ")) continue;
376
523
  const payload = line.slice(6).trim();
377
524
  if (payload === "[DONE]") break;
378
525
  try {
379
- const comp = JSON.parse(payload);
526
+ const parsed = JSON.parse(payload);
527
+ if (parsed._variant && typeof parsed._variant === "string") {
528
+ writeVariantId(orgSlug, parsed._variant);
529
+ continue;
530
+ }
531
+ if (parsed._error) {
532
+ if (!loadingCleared) {
533
+ setLoading(false);
534
+ loadingCleared = true;
535
+ }
536
+ continue;
537
+ }
538
+ if (!parsed.component) continue;
539
+ const comp = parsed;
380
540
  if (!loadingCleared) {
381
541
  setLoading(false);
382
542
  loadingCleared = true;
@@ -392,10 +552,14 @@ function PersonalisedSection({
392
552
  }
393
553
  } else {
394
554
  const data = await res.json();
555
+ if (data.variantId) writeVariantId(orgSlug, data.variantId);
395
556
  if (cancelled) return;
396
- const clientAudience = (_f = (_e = data.intent) == null ? void 0 : _e.userContext) == null ? void 0 : _f.audience;
397
- const clientStage = (_h = (_g = data.intent) == null ? void 0 : _g.userContext) == null ? void 0 : _h.stage;
557
+ const clientAudience = (_g = (_f = data.intent) == null ? void 0 : _f.userContext) == null ? void 0 : _g.audience;
558
+ const clientStage = (_i = (_h = data.intent) == null ? void 0 : _h.userContext) == null ? void 0 : _i.stage;
398
559
  const ssrMatches = initialIntent && clientAudience === initialIntent.audience && clientStage === initialIntent.stage;
560
+ if (clientAudience && clientStage) {
561
+ setDebugIntent({ audience: clientAudience, stage: clientStage });
562
+ }
399
563
  if (!ssrMatches && Array.isArray(data.assembly) && data.assembly.length > 0) {
400
564
  setAssembly(data.assembly);
401
565
  }
@@ -409,7 +573,7 @@ function PersonalisedSection({
409
573
  return () => {
410
574
  cancelled = true;
411
575
  };
412
- }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent]);
576
+ }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent, directSummary]);
413
577
  react.useEffect(() => {
414
578
  if (!assembly.length || conversionFiredRef.current) return;
415
579
  const token = tokenRef.current;
@@ -454,6 +618,7 @@ function PersonalisedSection({
454
618
  token,
455
619
  orgSlug,
456
620
  url,
621
+ variantId: readVariantId(orgSlug),
457
622
  ...atomsShown.length ? { atomsShown } : {},
458
623
  ...visitPath.length > 1 ? { visitPath } : {},
459
624
  ...data.goalName ? { goalName: data.goalName } : {},
@@ -465,15 +630,39 @@ function PersonalisedSection({
465
630
  });
466
631
  }, [assembly, orgSlug, baseUrl]);
467
632
  if (!loading && assembly.length === 0) return null;
633
+ const debugStyle = debugMode ? { outline: "2px solid rgba(96,165,250,0.5)", borderRadius: "0.5rem", position: "relative", padding: "0.5rem", ...style } : style;
468
634
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
469
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: CSS_TOKENS } }),
470
635
  /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: "@keyframes sp-pulse{0%,100%{opacity:1}50%{opacity:0.4}}" } }),
471
- /* @__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 }) })
636
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: debugStyle, children: [
637
+ debugMode && /* @__PURE__ */ jsxRuntime.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: [
638
+ "Personalised by Spectare",
639
+ debugIntent && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: [
640
+ " ",
641
+ debugIntent.audience,
642
+ "/",
643
+ debugIntent.stage
644
+ ] })
645
+ ] }),
646
+ loading && assembly.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Shimmer, {}) : /* @__PURE__ */ jsxRuntime.jsx(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align, accent })
647
+ ] })
472
648
  ] });
473
649
  }
474
650
 
651
+ exports.AnnouncementBar = AnnouncementBar;
652
+ exports.AtomCard = AtomCard;
653
+ exports.CodeBlock = CodeBlock;
654
+ exports.ComparisonTable = ComparisonTable;
475
655
  exports.ComponentRenderer = ComponentRenderer;
656
+ exports.CtaStrip = CtaStrip;
657
+ exports.DEFAULT_ACCENT = DEFAULT_ACCENT;
658
+ exports.FeatureList = FeatureList;
659
+ exports.HeroStatement = HeroStatement;
660
+ exports.ImageCtaHero = ImageCtaHero;
476
661
  exports.PersonalisedSection = PersonalisedSection;
662
+ exports.SP_ROOT = SP_ROOT;
663
+ exports.StatGrid = StatGrid;
664
+ exports.TestimonialCard = TestimonialCard;
665
+ exports.buildTokenCss = buildTokenCss;
477
666
  exports.filterAssemblyByZone = filterAssemblyByZone;
478
667
  //# sourceMappingURL=index.cjs.map
479
668
  //# sourceMappingURL=index.cjs.map