@spectare-personalisation/react 0.5.0 → 0.7.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
@@ -92,19 +92,47 @@ var COMPONENT_CATEGORIES = {
92
92
 
93
93
  // src/tokens.ts
94
94
  var DEFAULT_ACCENT = "#60a5fa";
95
+ var DEFAULT_ACCENT_LIGHT = "#2563eb";
95
96
  var SP_ROOT = "sp-root";
96
97
  var STYLESHEET_SCOPE = `:where(:root),.${SP_ROOT}`;
97
- function palette(accent) {
98
- return {
98
+ function palette(accent, theme) {
99
+ const shared = {
99
100
  "--sp-accent": accent,
100
101
  "--sp-accent-bg": `color-mix(in srgb,${accent} 10%,transparent)`,
101
- "--sp-accent-border": `color-mix(in srgb,${accent} 20%,transparent)`,
102
+ "--sp-accent-border": `color-mix(in srgb,${accent} 20%,transparent)`
103
+ };
104
+ if (theme === "light") {
105
+ return {
106
+ ...shared,
107
+ "--sp-bg": "#ffffff",
108
+ "--sp-surface": "#f4f4f5",
109
+ "--sp-border": "#e4e4e7",
110
+ "--sp-text": "#18181b",
111
+ "--sp-text-sub": "#3f3f46",
112
+ "--sp-text-strong": "#27272a",
113
+ "--sp-text-muted": "#6b6b74",
114
+ "--sp-text-dim": "#82828b",
115
+ "--sp-text-body": "#52525b",
116
+ "--sp-positive": "#047857",
117
+ "--sp-cta-bg": "#18181b",
118
+ "--sp-cta-text": "#ffffff",
119
+ "--sp-divider": "rgba(0,0,0,0.06)",
120
+ "--sp-code-bg": "rgba(0,0,0,0.05)",
121
+ "--sp-code-border": "rgba(0,0,0,0.08)",
122
+ "--sp-pre-bg": "rgba(0,0,0,0.04)"
123
+ };
124
+ }
125
+ return {
126
+ ...shared,
102
127
  "--sp-bg": "#18181b",
103
128
  "--sp-surface": "#09090b",
104
129
  "--sp-border": "#27272a",
105
130
  "--sp-text": "#f4f4f5",
106
- "--sp-text-sub": "#d4d4d8",
107
- "--sp-text-strong": "#e4e4e7",
131
+ // Body prose colour. Was #d4d4d8, judged too grey against near-black at reading
132
+ // sizes; one step brighter keeps the hierarchy below --sp-text while reading as
133
+ // text rather than chrome.
134
+ "--sp-text-sub": "#e4e4e7",
135
+ "--sp-text-strong": "#ececee",
108
136
  "--sp-text-muted": "#82828b",
109
137
  "--sp-text-dim": "#6b6b74",
110
138
  "--sp-text-body": "#a1a1aa",
@@ -133,8 +161,9 @@ function rules(unit) {
133
161
  "@media(max-width:640px){.sp-root .sp-pair{grid-template-columns:1fr}}"
134
162
  ].join("");
135
163
  }
136
- function buildTokenCss({ accent = DEFAULT_ACCENT, unit = "px", scope = `.${SP_ROOT}` } = {}) {
137
- const decls = Object.entries(palette(accent)).map(([k, v]) => `${k}:${v}`).join(";");
164
+ function buildTokenCss({ accent, unit = "px", scope = `.${SP_ROOT}`, theme = "dark" } = {}) {
165
+ const resolved = accent != null ? accent : theme === "light" ? DEFAULT_ACCENT_LIGHT : DEFAULT_ACCENT;
166
+ const decls = Object.entries(palette(resolved, theme)).map(([k, v]) => `${k}:${v}`).join(";");
138
167
  return `${scope}{${decls}}${rules(unit)}`;
139
168
  }
140
169
 
@@ -188,17 +217,28 @@ var clampStyle = {
188
217
  WebkitBoxOrient: "vertical",
189
218
  overflow: "hidden"
190
219
  };
191
- function AtomCard({ title, content, layout, stats = [], ctaText, ctaUrl, full, unit = "px" }) {
220
+ function AtomCard({ title, content, summary, layout, stats = [], ctaText, ctaUrl, full, variant, unit = "px" }) {
192
221
  const u = scaler(unit);
222
+ if (variant === "card") {
223
+ const face = (summary == null ? void 0 : summary.trim()) || (content ? `${content.replace(/\s+/g, " ").split(/(?<=[.!?])\s/)[0]}` : "");
224
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(12), padding: u(20) }, children: [
225
+ title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: u(18), fontWeight: 600, color: "var(--sp-text)", margin: 0, lineHeight: 1.35 }, children: title }),
226
+ face && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(16), color: "var(--sp-text-sub)", lineHeight: 1.6, margin: `${u(8)} 0 0` }, children: face }),
227
+ content && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { style: { marginTop: u(10) }, children: [
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { style: { cursor: "pointer", fontSize: u(14), fontWeight: 600, color: "var(--sp-accent)", listStyle: "none" }, children: "Read more" }),
229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sp-prose", style: { fontSize: u(16), color: "var(--sp-text-sub)", lineHeight: 1.6, marginTop: u(10) }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } })
230
+ ] })
231
+ ] });
232
+ }
193
233
  const order = layout && layout.length ? layout : ATOM_LAYOUT_DEFAULT;
194
234
  const element = (name) => {
195
- if (name === "title") return title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: u(16), fontWeight: 600, color: "var(--sp-text)", margin: `0 0 ${u(12)}` }, children: title }, "title") : null;
196
- if (name === "content") return content ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sp-prose", style: { fontSize: u(15), color: "var(--sp-text-sub)", lineHeight: 1.6, ...full ? {} : clampStyle }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
197
- if (name === "stats") return stats.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: u(20), margin: `0 0 ${u(12)}`, paddingBottom: u(12), borderBottom: "1px solid var(--sp-border)" }, children: stats.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: u(2) }, children: [
235
+ if (name === "title") return title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: u(18), fontWeight: 600, color: "var(--sp-text)", margin: `0 0 ${u(12)}` }, children: title }, "title") : null;
236
+ if (name === "content") return content ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sp-prose", style: { fontSize: u(16), color: "var(--sp-text-sub)", lineHeight: 1.6, ...full ? {} : clampStyle }, dangerouslySetInnerHTML: { __html: renderMarkdown(content) } }, "content") : null;
237
+ if (name === "stats") return stats.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: u(20), margin: `${u(18)} 0 ${u(12)}`, padding: `0 0 ${u(14)}`, borderBottom: "1px solid var(--sp-border)" }, children: stats.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: u(2) }, children: [
198
238
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(22), fontWeight: 700, color: "var(--sp-text)", lineHeight: 1, fontVariantNumeric: "tabular-nums" }, children: s.value }),
199
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(11), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: u(12), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }, children: s.label })
200
240
  ] }, i)) }, "stats") : null;
201
- if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: ctaUrl, style: { display: "inline-flex", alignItems: "center", gap: u(4), marginTop: u(12), fontSize: u(13), fontWeight: 600, color: "var(--sp-accent)", textDecoration: "none" }, children: [
241
+ if (name === "cta") return ctaText && ctaUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: ctaUrl, style: { display: "inline-flex", alignItems: "center", gap: u(4), marginTop: u(12), fontSize: u(14), fontWeight: 600, color: "var(--sp-accent)", textDecoration: "none" }, children: [
202
242
  ctaText,
203
243
  " \u2192"
204
244
  ] }, "cta") : null;
@@ -211,10 +251,10 @@ function StatGrid({ stats = [], caption, unit = "px" }) {
211
251
  const cols = stats.length === 2 ? 2 : stats.length === 3 ? 3 : 4;
212
252
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
213
253
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: u(12) }, children: stats.map(({ value, label }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { background: "var(--sp-bg)", border: "1px solid var(--sp-border)", borderRadius: u(10), padding: `${u(16)} ${u(20)}`, display: "flex", flexDirection: "column", justifyContent: "space-between", minHeight: u(88) }, children: [
214
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(11), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", margin: `0 0 ${u(12)}`, lineHeight: 1.3 }, children: label }),
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(12), color: "var(--sp-text-muted)", textTransform: "uppercase", letterSpacing: "0.06em", margin: `0 0 ${u(12)}`, lineHeight: 1.3 }, children: label }),
215
255
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(20), fontWeight: 500, color: "var(--sp-text)", fontVariantNumeric: "tabular-nums", lineHeight: 1, margin: 0 }, children: value })
216
256
  ] }, label)) }),
217
- caption && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(12)} 0 0` }, children: caption })
257
+ caption && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(14), color: "var(--sp-text-muted)", margin: `${u(12)} 0 0` }, children: caption })
218
258
  ] });
219
259
  }
220
260
  function CodeBlock({ code, lang = "typescript", caption, unit = "px" }) {
@@ -232,14 +272,14 @@ function ComparisonTable({ brand = "Us", competitor, rows = [], unit = "px" }) {
232
272
  const cell = { textAlign: "left", padding: `${u(12)} ${u(16)}` };
233
273
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { border: "1px solid var(--sp-border)", borderRadius: u(12), overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: { width: "100%", fontSize: u(14), borderCollapse: "collapse" }, children: [
234
274
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { style: { borderBottom: "1px solid var(--sp-border)", background: "var(--sp-bg)" }, children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12), width: "33%" } }),
236
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, fontWeight: 600, color: "var(--sp-accent)", fontSize: u(12) }, children: brand }),
237
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(12) }, children: competitor })
275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(13), width: "33%" } }),
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, fontWeight: 600, color: "var(--sp-accent)", fontSize: u(13) }, children: brand }),
277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", style: { ...cell, color: "var(--sp-text-muted)", fontWeight: 500, fontSize: u(13) }, children: competitor })
238
278
  ] }) }),
239
279
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map(({ label, spectare, them }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { style: { borderBottom: "1px solid var(--sp-border)" }, children: [
240
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "row", style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
241
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
242
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(13), color: "var(--sp-text-muted)" }, children: them })
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "row", style: { ...cell, fontSize: u(14), color: "var(--sp-text-muted)", fontWeight: 500 }, children: label }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(14), color: "var(--sp-positive)", fontWeight: 500 }, children: spectare }),
282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...cell, fontSize: u(14), color: "var(--sp-text-muted)" }, children: them })
243
283
  ] }, label)) })
244
284
  ] }) }) });
245
285
  }
@@ -249,7 +289,7 @@ function FeatureList({ items = [], unit = "px" }) {
249
289
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--sp-accent)", flexShrink: 0, fontSize: u(15), marginTop: u(2) }, children: "\u2713" }),
250
290
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
251
291
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(15), fontWeight: 500, color: "var(--sp-text)", lineHeight: 1.4, margin: 0 }, children: text }),
252
- sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", lineHeight: 1.5, margin: `${u(4)} 0 0` }, children: sub })
292
+ sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(14), color: "var(--sp-text-muted)", lineHeight: 1.5, margin: `${u(4)} 0 0` }, children: sub })
253
293
  ] })
254
294
  ] }, text)) }) });
255
295
  }
@@ -263,7 +303,7 @@ function TestimonialCard({ quote, name, role, company, unit = "px" }) {
263
303
  ] }),
264
304
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderTop: "1px solid var(--sp-border)", paddingTop: u(16) }, children: [
265
305
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: u(14), fontWeight: 600, color: "var(--sp-text)", margin: 0 }, children: name }),
266
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: u(13), color: "var(--sp-text-muted)", margin: `${u(2)} 0 0` }, children: [
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { fontSize: u(14), color: "var(--sp-text-muted)", margin: `${u(2)} 0 0` }, children: [
267
307
  role,
268
308
  company ? `, ${company}` : ""
269
309
  ] })
@@ -307,7 +347,7 @@ function renderSingle(item, brand, align, unit = "px") {
307
347
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeroStatement, { headline: s.headline, subheading: s.subheading, align, unit });
308
348
  // A full-width card has nothing beside it, so there is nothing for a clamp to protect.
309
349
  case "AtomCard":
310
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AtomCard, { title: s.title, content: s.content, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: s.full || resolvedSize(item) === "full", unit });
350
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AtomCard, { title: s.title, content: s.content, summary: s.summary, layout: s.layout, stats: s.stats, ctaText: s.ctaText, ctaUrl: s.ctaUrl, full: s.full || resolvedSize(item) === "full", variant: s.variant, unit });
311
351
  case "StatGrid":
312
352
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatGrid, { stats: s.stats, caption: s.caption, unit });
313
353
  case "CodeBlock":
@@ -373,7 +413,7 @@ function XrayBadge({ name, size }) {
373
413
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: PX(10), fontFamily: "monospace", color: "#71717a", lineHeight: 1 }, children: size })
374
414
  ] });
375
415
  }
376
- function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent, unit = "px", renderOverride, renderItem, rootProps }) {
416
+ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent, theme, unit = "px", renderOverride, renderItem, rootProps }) {
377
417
  const u = scaler(unit);
378
418
  const filtered = zone ? filterAssemblyByZone(assembly, zone) : assembly;
379
419
  const layout = buildLayout(filtered);
@@ -384,7 +424,7 @@ function ComponentRenderer({ assembly, brand, xray = false, zone, align, accent,
384
424
  return renderItem ? renderItem(item, node) : node;
385
425
  };
386
426
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: extraClass ? `${SP_ROOT} ${extraClass}` : SP_ROOT, ...restRoot, style: { ...extraStyle, display: "flex", flexDirection: "column", gap: u(20), ...unit === "px" ? { fontSize: "16px" } : {} }, children: [
387
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: buildTokenCss({ accent, unit }) } }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: buildTokenCss({ accent, unit, theme }) } }),
388
428
  layout.map((group, i) => {
389
429
  if (group.type === "pair") {
390
430
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sp-pair", children: [
@@ -477,7 +517,8 @@ function PersonalisedSection({
477
517
  directSummary,
478
518
  zone,
479
519
  align,
480
- accent
520
+ accent,
521
+ theme
481
522
  }) {
482
523
  const [assembly, setAssembly] = (0, import_react.useState)(initialAssembly != null ? initialAssembly : []);
483
524
  const [loading, setLoading] = (0, import_react.useState)(!initialAssembly);
@@ -728,7 +769,7 @@ function PersonalisedSection({
728
769
  debugIntent.stage
729
770
  ] })
730
771
  ] }),
731
- loading && assembly.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Shimmer, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align, accent })
772
+ loading && assembly.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Shimmer, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComponentRenderer, { assembly: zone ? filterAssemblyByZone(assembly, zone) : assembly, brand: brand != null ? brand : orgName, align, accent, theme })
732
773
  ] })
733
774
  ] });
734
775
  }