@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 +241 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -5
- package/dist/index.d.ts +167 -5
- package/dist/index.js +229 -53
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +78 -0
- package/dist/tokens.css.map +1 -0
- package/dist/tokens.d.cts +2 -0
- package/dist/tokens.d.ts +2 -0
- package/package.json +4 -3
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsx("a", { href: primary.href, style: { background: "
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
|
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 = (
|
|
397
|
-
const clientStage = (
|
|
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.
|
|
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
|