@spectare-personalisation/react 0.2.2 → 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,12 +31,83 @@ 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
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
+ }
39
111
  function HeroStatement({ headline, subheading, align = "left" }) {
40
112
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: flexItems(align), textAlign: align, paddingTop: "24px", paddingBottom: "24px" }, children: [
41
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 }),
@@ -43,11 +115,18 @@ function HeroStatement({ headline, subheading, align = "left" }) {
43
115
  ] });
44
116
  }
45
117
  var ATOM_LAYOUT_DEFAULT = ["title", "content", "stats", "cta"];
46
- 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 }) {
47
126
  const order = layout && layout.length ? layout : ATOM_LAYOUT_DEFAULT;
48
127
  const element = (name) => {
49
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;
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;
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;
51
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: [
52
131
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "22px", fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
53
132
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
@@ -135,7 +214,7 @@ function CtaStrip({ headline, primary, secondary, align = "center" }) {
135
214
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid var(--sp-border)", borderRadius: "12px", padding: "32px", textAlign: align, background: "var(--sp-bg)" }, children: [
136
215
  /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "24px", fontWeight: 700, color: "var(--sp-text)", margin: "0 0 24px" }, children: headline }),
137
216
  /* @__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 }),
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 }),
139
218
  secondary && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: secondary.href, style: { fontSize: "15px", color: "var(--sp-text-sub)", textDecoration: "none" }, children: [
140
219
  secondary.label,
141
220
  " \u2192"
@@ -147,10 +226,13 @@ function renderSingle(item, brand, align) {
147
226
  var _a, _b;
148
227
  const s = (_a = item.slots) != null ? _a : {};
149
228
  switch (item.component) {
229
+ case "AnnouncementBar":
230
+ return /* @__PURE__ */ jsxRuntime.jsx(AnnouncementBar, { text: s.text, link: s.link });
150
231
  case "HeroStatement":
151
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.
152
234
  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 });
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" });
154
236
  case "StatGrid":
155
237
  return /* @__PURE__ */ jsxRuntime.jsx(StatGrid, { stats: s.stats, caption: s.caption });
156
238
  case "CodeBlock":
@@ -165,8 +247,20 @@ function renderSingle(item, brand, align) {
165
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 });
166
248
  case "CtaStrip":
167
249
  return /* @__PURE__ */ jsxRuntime.jsx(CtaStrip, { headline: s.headline, primary: s.primary, secondary: s.secondary, align });
168
- default:
169
- return null;
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
+ }
170
264
  }
171
265
  }
172
266
  function resolvedSize(item) {
@@ -198,12 +292,11 @@ function XrayBadge({ name, size }) {
198
292
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
199
293
  ] });
200
294
  }
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 }) {
295
+ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent }) {
203
296
  const filtered = zone ? filterAssemblyByZone(assembly, zone) : assembly;
204
297
  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 } }),
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 }) } }),
207
300
  layout.map((group, i) => {
208
301
  if (group.type === "full") {
209
302
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: xray ? { position: "relative" } : void 0, children: [
@@ -230,7 +323,20 @@ function ComponentRenderer({ assembly, brand, xray = false, zone, align }) {
230
323
  })
231
324
  ] });
232
325
  }
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}`;
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
+ }
234
340
  function buildSummary(pageHint, orgSlug) {
235
341
  const parts = [pageHint];
236
342
  const referrer = document.referrer;
@@ -282,32 +388,39 @@ function PersonalisedSection({
282
388
  style,
283
389
  initialAssembly,
284
390
  initialIntent,
391
+ initialVariantId,
392
+ directSummary,
285
393
  zone,
286
- align
394
+ align,
395
+ accent
287
396
  }) {
288
397
  const [assembly, setAssembly] = react.useState(initialAssembly != null ? initialAssembly : []);
289
398
  const [loading, setLoading] = react.useState(!initialAssembly);
290
399
  const [orgName, setOrgName] = react.useState(void 0);
400
+ const [debugMode, setDebugMode] = react.useState(false);
401
+ const [debugIntent, setDebugIntent] = react.useState(null);
291
402
  const tokenRef = react.useRef(null);
292
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]);
293
411
  react.useEffect(() => {
294
412
  let cancelled = false;
295
413
  async function load() {
296
- var _a, _b, _c, _d, _e, _f, _g, _h;
414
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
297
415
  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);
416
+ let token = null;
417
+ if (directSummary) {
418
+ setLoading(true);
419
+ setAssembly([]);
307
420
  const res2 = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
308
421
  method: "POST",
309
422
  headers: { "Content-Type": "application/json" },
310
- body: JSON.stringify({ summary })
423
+ body: JSON.stringify({ summary: directSummary, direct: true })
311
424
  });
312
425
  if (!res2.ok || cancelled) {
313
426
  setLoading(false);
@@ -315,8 +428,30 @@ function PersonalisedSection({
315
428
  }
316
429
  const data = await res2.json();
317
430
  token = (_a = data.token) != null ? _a : null;
318
- if (token) sessionStorage.setItem(tokenKey, token);
319
- 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
+ }
320
455
  }
321
456
  if (!token || cancelled) {
322
457
  setLoading(false);
@@ -326,9 +461,19 @@ function PersonalisedSection({
326
461
  let res = await fetch(`${baseUrl}/api/assemble/components`, {
327
462
  method: "POST",
328
463
  headers: { "Content-Type": "application/json" },
329
- 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
+ )
330
469
  });
331
- 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 : ""}`;
332
477
  sessionStorage.removeItem(tokenKey);
333
478
  const summary = buildSummary(pageHint != null ? pageHint : `Visiting ${document.title}`, orgSlug);
334
479
  const qRes = await fetch(`${baseUrl}/api/qualify/${orgSlug}`, {
@@ -341,7 +486,7 @@ function PersonalisedSection({
341
486
  return;
342
487
  }
343
488
  const qData = await qRes.json();
344
- token = (_b = qData.token) != null ? _b : null;
489
+ token = (_c = qData.token) != null ? _c : null;
345
490
  if (token) {
346
491
  sessionStorage.setItem(tokenKey, token);
347
492
  tokenRef.current = token;
@@ -353,14 +498,14 @@ function PersonalisedSection({
353
498
  res = await fetch(`${baseUrl}/api/assemble/components`, {
354
499
  method: "POST",
355
500
  headers: { "Content-Type": "application/json" },
356
- body: JSON.stringify({ token, orgSlug })
501
+ body: JSON.stringify({ token, orgSlug, excludeVariantId: readVariantId(orgSlug) })
357
502
  });
358
503
  }
359
504
  if (!res.ok || cancelled) {
360
505
  setLoading(false);
361
506
  return;
362
507
  }
363
- const contentType = (_c = res.headers.get("content-type")) != null ? _c : "";
508
+ const contentType = (_d = res.headers.get("content-type")) != null ? _d : "";
364
509
  if (contentType.includes("text/event-stream")) {
365
510
  const reader = res.body.getReader();
366
511
  const decoder = new TextDecoder();
@@ -372,13 +517,26 @@ function PersonalisedSection({
372
517
  if (done || cancelled) break;
373
518
  buf += decoder.decode(value, { stream: true });
374
519
  const lines = buf.split("\n");
375
- buf = (_d = lines.pop()) != null ? _d : "";
520
+ buf = (_e = lines.pop()) != null ? _e : "";
376
521
  for (const line of lines) {
377
522
  if (!line.startsWith("data: ")) continue;
378
523
  const payload = line.slice(6).trim();
379
524
  if (payload === "[DONE]") break;
380
525
  try {
381
- 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;
382
540
  if (!loadingCleared) {
383
541
  setLoading(false);
384
542
  loadingCleared = true;
@@ -394,10 +552,14 @@ function PersonalisedSection({
394
552
  }
395
553
  } else {
396
554
  const data = await res.json();
555
+ if (data.variantId) writeVariantId(orgSlug, data.variantId);
397
556
  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;
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;
400
559
  const ssrMatches = initialIntent && clientAudience === initialIntent.audience && clientStage === initialIntent.stage;
560
+ if (clientAudience && clientStage) {
561
+ setDebugIntent({ audience: clientAudience, stage: clientStage });
562
+ }
401
563
  if (!ssrMatches && Array.isArray(data.assembly) && data.assembly.length > 0) {
402
564
  setAssembly(data.assembly);
403
565
  }
@@ -411,7 +573,7 @@ function PersonalisedSection({
411
573
  return () => {
412
574
  cancelled = true;
413
575
  };
414
- }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent]);
576
+ }, [orgSlug, baseUrl, pageHint, initialAssembly, initialIntent, directSummary]);
415
577
  react.useEffect(() => {
416
578
  if (!assembly.length || conversionFiredRef.current) return;
417
579
  const token = tokenRef.current;
@@ -456,6 +618,7 @@ function PersonalisedSection({
456
618
  token,
457
619
  orgSlug,
458
620
  url,
621
+ variantId: readVariantId(orgSlug),
459
622
  ...atomsShown.length ? { atomsShown } : {},
460
623
  ...visitPath.length > 1 ? { visitPath } : {},
461
624
  ...data.goalName ? { goalName: data.goalName } : {},
@@ -467,15 +630,39 @@ function PersonalisedSection({
467
630
  });
468
631
  }, [assembly, orgSlug, baseUrl]);
469
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;
470
634
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
471
- /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: CSS_TOKENS } }),
472
635
  /* @__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 }) })
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
+ ] })
474
648
  ] });
475
649
  }
476
650
 
651
+ exports.AnnouncementBar = AnnouncementBar;
652
+ exports.AtomCard = AtomCard;
653
+ exports.CodeBlock = CodeBlock;
654
+ exports.ComparisonTable = ComparisonTable;
477
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;
478
661
  exports.PersonalisedSection = PersonalisedSection;
662
+ exports.SP_ROOT = SP_ROOT;
663
+ exports.StatGrid = StatGrid;
664
+ exports.TestimonialCard = TestimonialCard;
665
+ exports.buildTokenCss = buildTokenCss;
479
666
  exports.filterAssemblyByZone = filterAssemblyByZone;
480
667
  //# sourceMappingURL=index.cjs.map
481
668
  //# sourceMappingURL=index.cjs.map