@salesmind-ai/design-system 1.8.0 → 1.9.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.
Files changed (52) hide show
  1. package/dist/ChangeEmail-BuUhjW0o.d.cts +226 -0
  2. package/dist/ChangeEmail-BuUhjW0o.d.ts +226 -0
  3. package/dist/chunk-AI2YEYT4.js +1368 -0
  4. package/dist/chunk-AI2YEYT4.js.map +1 -0
  5. package/dist/{chunk-4QQKREVH.cjs → chunk-LM3UJILC.cjs} +3 -3
  6. package/dist/{chunk-4QQKREVH.cjs.map → chunk-LM3UJILC.cjs.map} +1 -1
  7. package/dist/{chunk-K7NQ7TQG.cjs → chunk-M4MLGT4B.cjs} +84 -27
  8. package/dist/chunk-M4MLGT4B.cjs.map +1 -0
  9. package/dist/{chunk-25TUCNN6.js → chunk-QVGDMAZY.js} +84 -27
  10. package/dist/chunk-QVGDMAZY.js.map +1 -0
  11. package/dist/{chunk-2TC5EVSJ.js → chunk-TPZ3PKSF.js} +3 -3
  12. package/dist/{chunk-2TC5EVSJ.js.map → chunk-TPZ3PKSF.js.map} +1 -1
  13. package/dist/chunk-YWJKSY5C.cjs +1381 -0
  14. package/dist/chunk-YWJKSY5C.cjs.map +1 -0
  15. package/dist/index.cjs +47 -30
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.css +2372 -0
  18. package/dist/index.css.map +1 -1
  19. package/dist/index.d.cts +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/marketing/index.cjs +27 -27
  24. package/dist/marketing/index.js +1 -1
  25. package/dist/onboarding/index.cjs +5870 -0
  26. package/dist/onboarding/index.cjs.map +1 -0
  27. package/dist/onboarding/index.css +557 -0
  28. package/dist/onboarding/index.css.map +1 -0
  29. package/dist/onboarding/index.d.cts +1589 -0
  30. package/dist/onboarding/index.d.ts +1589 -0
  31. package/dist/onboarding/index.js +5736 -0
  32. package/dist/onboarding/index.js.map +1 -0
  33. package/dist/styles/styles.css +2539 -0
  34. package/dist/v2-auth/index.cjs +59 -0
  35. package/dist/v2-auth/index.cjs.map +1 -0
  36. package/dist/v2-auth/index.css +3360 -0
  37. package/dist/v2-auth/index.css.map +1 -0
  38. package/dist/v2-auth/index.d.cts +76 -0
  39. package/dist/v2-auth/index.d.ts +76 -0
  40. package/dist/v2-auth/index.js +24 -0
  41. package/dist/v2-auth/index.js.map +1 -0
  42. package/dist/web/index.cjs +7 -7
  43. package/dist/web/index.d.cts +1 -1
  44. package/dist/web/index.d.ts +1 -1
  45. package/dist/web/index.js +1 -1
  46. package/dist/web/server/index.cjs +7 -7
  47. package/dist/web/server/index.d.cts +91 -27
  48. package/dist/web/server/index.d.ts +91 -27
  49. package/dist/web/server/index.js +1 -1
  50. package/package.json +14 -2
  51. package/dist/chunk-25TUCNN6.js.map +0 -1
  52. package/dist/chunk-K7NQ7TQG.cjs.map +0 -1
@@ -59,8 +59,15 @@ function createEntityIds(brand) {
59
59
  function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"]) {
60
60
  const ENTITY_IDS = createEntityIds(brand);
61
61
  return {
62
- /** Organization schema — Homepage + About page. */
63
- organization(locale = "en") {
62
+ /**
63
+ * Organization schema — Homepage + About page.
64
+ *
65
+ * Note: schema.org does NOT allow `inLanguage` or `availableLanguage` on
66
+ * Organization. The multilingual signal is carried by `knowsLanguage`
67
+ * (valid on Organization, BCP-47 codes). The `locale` param is retained
68
+ * for call-site signature stability but no longer emitted here.
69
+ */
70
+ organization(_locale = "en") {
64
71
  return {
65
72
  "@context": "https://schema.org",
66
73
  "@type": "Organization",
@@ -72,8 +79,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
72
79
  url: `${brand.url}/logo.svg`
73
80
  },
74
81
  description: brand.description,
75
- inLanguage: locale,
76
- availableLanguage: [...supportedLanguages],
82
+ knowsLanguage: [...supportedLanguages],
77
83
  founder: {
78
84
  "@type": "Person",
79
85
  name: brand.founder
@@ -124,7 +130,17 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
124
130
  name: title,
125
131
  description,
126
132
  inLanguage: locale,
127
- isPartOf: { "@type": "WebSite", "@id": ENTITY_IDS.website },
133
+ // Reference stub to the site-wide WebSite node. `url`/`name` are
134
+ // included (when the brand provides them) so the reference stands as a
135
+ // complete WebSite on pages rendered independently of the WebSite node,
136
+ // per schema.org @id-linking guidance. Guarded so a brand without
137
+ // `url`/`name` never emits `undefined`.
138
+ isPartOf: {
139
+ "@type": "WebSite",
140
+ "@id": ENTITY_IDS.website,
141
+ ...brand.url ? { url: brand.url } : {},
142
+ ...brand.name ? { name: brand.name } : {}
143
+ },
128
144
  breadcrumb: { "@id": ENTITY_IDS.breadcrumb(path) },
129
145
  potentialAction: {
130
146
  "@type": "ReadAction",
@@ -136,6 +152,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
136
152
  /** SoftwareApplication schema — Platform Hub, Platform Feature, Pricing. */
137
153
  softwareApplication(name, description, url, locale = "en", options = {}) {
138
154
  const aggregateRating = options.aggregateRating ?? brand.aggregateRating;
155
+ const offers = options.offers ?? brand.softwareApplicationOffers;
139
156
  return {
140
157
  "@context": "https://schema.org",
141
158
  "@type": "SoftwareApplication",
@@ -145,8 +162,21 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
145
162
  url: url || brand.url,
146
163
  applicationCategory: "BusinessApplication",
147
164
  operatingSystem: "Web",
165
+ // `inLanguage` is valid — SoftwareApplication is a CreativeWork subtype.
166
+ // `availableLanguage` is NOT valid on SoftwareApplication and is dropped.
148
167
  inLanguage: locale,
149
- availableLanguage: [...supportedLanguages],
168
+ // Google requires `offers` with a price. We expose only the price floor
169
+ // via AggregateOffer so A/B-tested tier prices stay hidden.
170
+ ...offers ? {
171
+ offers: {
172
+ "@type": "AggregateOffer",
173
+ lowPrice: offers.lowPrice,
174
+ priceCurrency: offers.priceCurrency,
175
+ offerCount: offers.offerCount,
176
+ url: offers.url ?? `${brand.url}/pricing`,
177
+ availability: "https://schema.org/InStock"
178
+ }
179
+ } : {},
150
180
  ...aggregateRating ? {
151
181
  aggregateRating: {
152
182
  "@type": "AggregateRating",
@@ -226,15 +256,18 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
226
256
  }))
227
257
  };
228
258
  },
229
- /** BreadcrumbList schema — Every public page. */
230
- breadcrumb(items, locale = "en") {
259
+ /**
260
+ * BreadcrumbList schema Every public page.
261
+ * `inLanguage` is NOT valid on BreadcrumbList and is not emitted. The
262
+ * `locale` param is retained for call-site signature stability.
263
+ */
264
+ breadcrumb(items, _locale = "en") {
231
265
  const lastUrl = items.length > 0 ? items[items.length - 1].url : "/";
232
266
  const path = lastUrl.startsWith("http") ? new URL(lastUrl).pathname : lastUrl;
233
267
  return {
234
268
  "@context": "https://schema.org",
235
269
  "@type": "BreadcrumbList",
236
270
  "@id": ENTITY_IDS.breadcrumb(path),
237
- inLanguage: locale,
238
271
  itemListElement: items.map((item, index) => ({
239
272
  "@type": "ListItem",
240
273
  position: index + 1,
@@ -243,8 +276,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
243
276
  }))
244
277
  };
245
278
  },
246
- /** Person schema — Team page, Blog author, Comparison author. */
247
- person(person, locale = "en") {
279
+ /**
280
+ * Person schema — Team page, Blog author, Comparison author.
281
+ * `inLanguage` is NOT valid on Person (Person is not a CreativeWork) and is
282
+ * not emitted. The `locale` param is retained for signature stability.
283
+ */
284
+ person(person, _locale = "en") {
248
285
  const slug = person.slug || person.name.toLowerCase().replace(/\s+/g, "-");
249
286
  return {
250
287
  "@context": "https://schema.org",
@@ -254,7 +291,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
254
291
  jobTitle: person.jobTitle,
255
292
  url: person.url,
256
293
  image: person.image,
257
- inLanguage: locale,
258
294
  worksFor: person.worksFor || {
259
295
  "@type": "Organization",
260
296
  "@id": ENTITY_IDS.organization,
@@ -298,8 +334,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
298
334
  } : {}
299
335
  };
300
336
  },
301
- /** Product + multiple Offers schema — Pricing page. */
302
- productWithOffers(plans, testimonials = [], locale = "en") {
337
+ /**
338
+ * Product + multiple Offers schema Pricing page.
339
+ * `inLanguage` is NOT valid on Product and is not emitted. The `locale`
340
+ * param is retained for call-site signature stability.
341
+ */
342
+ productWithOffers(plans, testimonials = [], _locale = "en") {
303
343
  return {
304
344
  "@context": "https://schema.org",
305
345
  "@type": "Product",
@@ -311,7 +351,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
311
351
  "@id": ENTITY_IDS.organization,
312
352
  name: brand.name
313
353
  },
314
- inLanguage: locale,
315
354
  ...testimonials.length > 0 ? { aggregateRating: aggregateRatingFromTestimonials(testimonials) } : {},
316
355
  offers: plans.map((plan) => ({
317
356
  "@type": "Offer",
@@ -355,7 +394,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
355
394
  ...transcript.transcript_text ? { transcript: transcript.transcript_text } : {}
356
395
  };
357
396
  },
358
- /** CustomerOrganization schema — Customer's company for Success Story pages. */
397
+ /**
398
+ * CustomerOrganization schema — Customer's company for Success Story pages.
399
+ * `industry` is NOT valid on Organization (schema.org allows only the coded
400
+ * `naics` / `isicV4`, plus free-text `keywords`). The free-text industry
401
+ * value is carried by `keywords`.
402
+ */
359
403
  customerOrganization(meta) {
360
404
  return {
361
405
  "@context": "https://schema.org",
@@ -365,7 +409,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
365
409
  ...meta.url ? { url: meta.url } : {},
366
410
  ...meta.logo ? { logo: meta.logo } : {},
367
411
  ...meta.description ? { description: meta.description } : {},
368
- ...meta.industry ? { industry: meta.industry } : {},
412
+ ...meta.industry ? { keywords: meta.industry } : {},
369
413
  ...meta.sameAs && meta.sameAs.length > 0 ? { sameAs: meta.sameAs } : {}
370
414
  };
371
415
  },
@@ -408,8 +452,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
408
452
  }))
409
453
  };
410
454
  },
411
- /** Service schema — Growth motion service offerings. */
412
- service(motion, locale = "en") {
455
+ /**
456
+ * Service schema Growth motion service offerings.
457
+ * Neither `inLanguage` nor `availableLanguage` is valid on Service, so both
458
+ * are dropped. The `locale` param is retained for signature stability.
459
+ */
460
+ service(motion, _locale = "en") {
413
461
  return {
414
462
  "@context": "https://schema.org",
415
463
  "@type": "Service",
@@ -422,8 +470,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
422
470
  "@id": ENTITY_IDS.organization,
423
471
  name: brand.name
424
472
  },
425
- inLanguage: locale,
426
- availableLanguage: [...supportedLanguages],
427
473
  areaServed: "Worldwide",
428
474
  ...motion.price ? {
429
475
  offers: {
@@ -436,8 +482,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
436
482
  termsOfService: `${brand.url}/legal/terms`
437
483
  };
438
484
  },
439
- /** JobPosting schema — Career detail pages. */
440
- jobPosting(job, locale = "en") {
485
+ /**
486
+ * JobPosting schema Career detail pages.
487
+ * `inLanguage` is NOT valid on JobPosting and is not emitted. The `locale`
488
+ * param is retained for call-site signature stability.
489
+ */
490
+ jobPosting(job, _locale = "en") {
441
491
  return {
442
492
  "@context": "https://schema.org",
443
493
  "@type": "JobPosting",
@@ -462,8 +512,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
462
512
  }
463
513
  } : {
464
514
  jobLocationType: "TELECOMMUTE"
465
- },
466
- inLanguage: locale
515
+ }
467
516
  };
468
517
  },
469
518
  /** ItemList schema — Hub/listing pages. */
@@ -499,9 +548,17 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
499
548
  }))
500
549
  };
501
550
  },
502
- /** Speakable — marks content sections suitable for voice assistant reading. */
551
+ /**
552
+ * Speakable — marks content sections suitable for voice assistant reading.
553
+ *
554
+ * Includes its own `@context` so it is valid when rendered as a standalone
555
+ * top-level JSON-LD node. When embedded inside another schema (e.g. as the
556
+ * `speakable` value on WebPage/Article), the surrounding `buildPageGraph`
557
+ * strips the redundant `@context`, so the nested case is unaffected.
558
+ */
503
559
  speakable(cssSelectors) {
504
560
  return {
561
+ "@context": "https://schema.org",
505
562
  "@type": "SpeakableSpecification",
506
563
  cssSelector: cssSelectors
507
564
  };
@@ -569,4 +626,4 @@ exports.canonicalUrl = canonicalUrl;
569
626
  exports.createEntityIds = createEntityIds;
570
627
  exports.createSchemaGenerators = createSchemaGenerators;
571
628
  //# sourceMappingURL=out.js.map
572
- //# sourceMappingURL=chunk-K7NQ7TQG.cjs.map
629
+ //# sourceMappingURL=chunk-M4MLGT4B.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web/seo/schema-generators.ts","../src/web/seo/JsonLd.tsx"],"names":[],"mappings":";AA4DA,SAAS,kBAAkB,UAA0B;AACnD,MAAI,SAAS,WAAW,IAAI,EAAG,QAAO;AACtC,QAAM,QAAQ,SAAS,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,MAAI,MAAM,KAAK,KAAK,EAAG,QAAO;AAC9B,MAAI,MAAM,WAAW,EAAG,QAAO,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACpE,MAAI,MAAM,WAAW,EAAG,QAAO,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACxD,SAAO,KAAK,MAAM,CAAC,CAAC;AACtB;AAGA,SAAS,WAAW,KAAiC;AACnD,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,QAAI,OAAO,SAAS,SAAS,aAAa,KAAK,OAAO,aAAa,IAAI,GAAG,GAAG;AAC3E,aAAO,iCAAiC,OAAO,aAAa,IAAI,GAAG,CAAC;AAAA,IACtE;AACA,QAAI,OAAO,aAAa,YAAY;AAClC,aAAO,gCAAgC,OAAO,QAAQ;AAAA,IACxD;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,kBAAkB,MAA8B;AACvD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,YAAY;AAC/B,MAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AACnC,MAAI,MAAM,SAAS,UAAU,EAAG,QAAO;AACvC,MAAI,MAAM,SAAS,QAAQ,EAAG,QAAO;AACrC,MAAI,MAAM,SAAS,WAAW,KAAK,MAAM,SAAS,MAAM,EAAG,QAAO;AAClE,SAAO;AACT;AAaA,SAAS,oBAAoB,UAAkB,WAA2B;AACxE,MAAI,gBAAgB,KAAK,SAAS,EAAG,QAAO;AAC5C,SAAO,GAAG,QAAQ,GAAG,SAAS;AAChC;AAEO,SAAS,gBAAgB,OAAoB;AAClD,SAAO;AAAA,IACL,cAAc,GAAG,MAAM,GAAG;AAAA,IAC1B,SAAS,GAAG,MAAM,GAAG;AAAA,IACrB,UAAU,GAAG,MAAM,GAAG;AAAA,IACtB,SAAS,GAAG,MAAM,GAAG;AAAA,IACrB,SAAS,CAAC,SAAiB,GAAG,oBAAoB,MAAM,KAAK,IAAI,CAAC;AAAA,IAClE,SAAS,CAAC,SAAiB,GAAG,oBAAoB,MAAM,KAAK,IAAI,CAAC;AAAA,IAClE,KAAK,CAAC,SAAiB,GAAG,oBAAoB,MAAM,KAAK,IAAI,CAAC;AAAA,IAC9D,YAAY,CAAC,SAAiB,GAAG,oBAAoB,MAAM,KAAK,IAAI,CAAC;AAAA,IACrE,QAAQ,CAAC,SAAiB,GAAG,MAAM,GAAG,wBAAwB,IAAI;AAAA,IAClE,OAAO,CAAC,SAAiB,GAAG,oBAAoB,MAAM,KAAK,IAAI,CAAC;AAAA,IAChE,QAAQ,CAAC,OAAe,GAAG,MAAM,GAAG,YAAY,EAAE;AAAA,IAClD,SAAS,CAAC,SAAiB,GAAG,MAAM,GAAG,aAAa,IAAI;AAAA,IACxD,sBAAsB,CAAC,SAAiB,GAAG,MAAM,GAAG,kBAAkB,IAAI;AAAA,IAC1E,gBAAgB,CAAC,SAAiB,GAAG,MAAM,GAAG,qBAAqB,IAAI;AAAA,EACzE;AACF;AAQO,SAAS,uBACd,OACA,qBAAwC,CAAC,MAAM,MAAM,IAAI,GACzD;AACA,QAAM,aAAa,gBAAgB,KAAK;AAExC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASL,aAAa,UAAkB,MAAM;AAEnC,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW;AAAA,QAClB,MAAM,MAAM;AAAA,QACZ,KAAK,MAAM;AAAA,QACX,MAAM;AAAA,UACJ,SAAS;AAAA,UACT,KAAK,GAAG,MAAM,GAAG;AAAA,QACnB;AAAA,QACA,aAAa,MAAM;AAAA,QACnB,eAAe,CAAC,GAAG,kBAAkB;AAAA,QACrC,SAAS;AAAA,UACP,SAAS;AAAA,UACT,MAAM,MAAM;AAAA,QACd;AAAA,QACA,GAAI,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,QAC1E,cAAc,CAAC;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF;AAAA;AAAA,IAGA,QAAQ,SAAiB,MAAM;AAC7B,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW;AAAA,QAClB,MAAM,MAAM;AAAA,QACZ,KAAK,MAAM;AAAA,QACX,aAAa,MAAM;AAAA,QACnB,YAAY;AAAA,QACZ,WAAW;AAAA,UACT,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,iBAAiB;AAAA,UACf;AAAA,YACE,SAAS;AAAA,YACT,QAAQ;AAAA,cACN,SAAS;AAAA,cACT,aAAa,GAAG,MAAM,GAAG;AAAA,YAC3B;AAAA,YACA,eAAe;AAAA,UACjB;AAAA,UACA;AAAA,YACE,SAAS;AAAA,YACT,QAAQ,MAAM;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAGA,QAAQ,MAAc,OAAe,aAAqB,SAAiB,MAAM,WAAqC;AACpH,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,QAAQ,IAAI;AAAA,QAC9B,KAAK,GAAG,MAAM,GAAG,GAAG,IAAI;AAAA,QACxB,MAAM;AAAA,QACN;AAAA,QACA,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMZ,UAAU;AAAA,UACR,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,UACtC,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,QAC3C;AAAA,QACA,YAAY,EAAE,OAAO,WAAW,WAAW,IAAI,EAAE;AAAA,QACjD,iBAAiB;AAAA,UACf,SAAS;AAAA,UACT,QAAQ,GAAG,MAAM,GAAG,GAAG,IAAI;AAAA,QAC7B;AAAA,QACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACnC;AAAA,IACF;AAAA;AAAA,IAGA,oBACE,MACA,aACA,KACA,SAAiB,MACjB,UAA4C,CAAC,GAC7C;AACA,YAAM,kBAAkB,QAAQ,mBAAmB,MAAM;AACzD,YAAM,SAAS,QAAQ,UAAU,MAAM;AAEvC,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW;AAAA,QAClB,MAAM,QAAQ,MAAM;AAAA,QACpB,aAAa,eAAe,MAAM;AAAA,QAClC,KAAK,OAAO,MAAM;AAAA,QAClB,qBAAqB;AAAA,QACrB,iBAAiB;AAAA;AAAA;AAAA,QAGjB,YAAY;AAAA;AAAA;AAAA,QAGZ,GAAI,SACA;AAAA,UACE,QAAQ;AAAA,YACN,SAAS;AAAA,YACT,UAAU,OAAO;AAAA,YACjB,eAAe,OAAO;AAAA,YACtB,YAAY,OAAO;AAAA,YACnB,KAAK,OAAO,OAAO,GAAG,MAAM,GAAG;AAAA,YAC/B,cAAc;AAAA,UAChB;AAAA,QACF,IACA,CAAC;AAAA,QACL,GAAI,kBACA;AAAA,UACE,iBAAiB;AAAA,YACf,SAAS;AAAA,YACT,aAAa,OAAO,gBAAgB,WAAW;AAAA,YAC/C,aAAa,OAAO,gBAAgB,WAAW;AAAA,YAC/C,YAAY,OAAO,gBAAgB,cAAc,CAAC;AAAA,YAClD,aAAa,OAAO,gBAAgB,eAAe,CAAC;AAAA,UACtD;AAAA,QACF,IACA,CAAC;AAAA,QACL,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAAA;AAAA,IAGA,QAAQ,MAAmB,SAAiB,MAAM;AAChD,UAAI;AACJ,UAAI;AACF,eAAO,IAAI,IAAI,KAAK,GAAG,EAAE;AAAA,MAC3B,QAAQ;AACN,eAAO,KAAK,IAAI,WAAW,GAAG,IAAI,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,MAC3D;AAEA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,QAAQ,IAAI;AAAA,QAC9B,UAAU,KAAK;AAAA,QACf,aAAa,KAAK;AAAA,QAClB,KAAK,KAAK,IAAI,WAAW,MAAM,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK,GAAG;AAAA,QACrE,OAAO,KAAK,SAAS,GAAG,MAAM,GAAG;AAAA,QACjC,eAAe,KAAK;AAAA,QACpB,cAAc,KAAK;AAAA,QACnB,YAAY;AAAA,QACZ,QAAQ,KAAK,aACT,EAAE,OAAO,WAAW,OAAO,KAAK,UAAU,EAAE,IAC5C;AAAA,UACE,SAAS;AAAA,UACT,MAAM,KAAK;AAAA,UACX,KAAK,KAAK,aAAa,GAAG,MAAM,GAAG;AAAA,QACrC;AAAA,QACJ,WAAW;AAAA,UACT,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,UACZ,MAAM;AAAA,YACJ,SAAS;AAAA,YACT,KAAK,GAAG,MAAM,GAAG;AAAA,UACnB;AAAA,QACF;AAAA,QACA,UAAU,EAAE,SAAS,WAAW,OAAO,WAAW,QAAQ;AAAA,QAC1D,kBAAkB,EAAE,OAAO,WAAW,QAAQ,IAAI,EAAE;AAAA,QACpD,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK,MAAM,KAAK,IAAI;AAAA,QAC9B,WAAW;AAAA,UACT,SAAS;AAAA,UACT,aAAa,KAAK,yBAAyB,CAAC,cAAc,oBAAoB,eAAe;AAAA,QAC/F;AAAA,QACA,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC1C,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA;AAAA,IAGA,QAAQ,OAAkB,SAAiB,SAAiB,MAAM;AAChE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,IAAI,OAAO;AAAA,QAC7B,YAAY;AAAA,QACZ,YAAY,MAAM,IAAI,CAAC,UAAU;AAAA,UAC/B,SAAS;AAAA,UACT,MAAM,KAAK;AAAA,UACX,gBAAgB;AAAA,YACd,SAAS;AAAA,YACT,MAAM,KAAK;AAAA,YACX,YAAY;AAAA,UACd;AAAA,QACF,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,WAAW,OAAyB,UAAkB,MAAM;AAE1D,YAAM,UAAU,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,CAAC,EAAE,MAAM;AACjE,YAAM,OAAO,QAAQ,WAAW,MAAM,IAAI,IAAI,IAAI,OAAO,EAAE,WAAW;AAEtE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,WAAW,IAAI;AAAA,QACjC,iBAAiB,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,UAC3C,SAAS;AAAA,UACT,UAAU,QAAQ;AAAA,UAClB,MAAM,KAAK;AAAA,UACX,MAAM,KAAK,IAAI,WAAW,MAAM,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK,GAAG;AAAA,QACxE,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,OAAO,QAAoB,UAAkB,MAAM;AAEjD,YAAM,OAAO,OAAO,QAAQ,OAAO,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAEzE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,OAAO,oBAAoB,WAAW,OAAO,IAAI;AAAA,QACxD,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,KAAK,OAAO;AAAA,QACZ,OAAO,OAAO;AAAA,QACd,UAAU,OAAO,YAAY;AAAA,UAC3B,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,QAAQ,OAAO,UAAU,CAAC;AAAA,MAC5B;AAAA,IACF;AAAA;AAAA,IAGA,OAAO,aAAmC;AACxC,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,OAAO,YAAY,EAAE;AAAA,QACvC,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,GAAI,YAAY,iBAAiB,EAAE,OAAO,YAAY,eAAe,IAAI,CAAC;AAAA,UAC1E,MAAM,YAAY;AAAA,UAClB,GAAI,YAAY,aAAa,EAAE,UAAU,YAAY,WAAW,IAAI,CAAC;AAAA,QACvE;AAAA,QACA,YAAY,YAAY,SAAS;AAAA,QACjC,GAAI,YAAY,cACZ;AAAA,UACE,cAAc;AAAA,YACZ,SAAS;AAAA,YACT,aAAa,OAAO,YAAY,WAAW;AAAA,YAC3C,YAAY;AAAA,YACZ,aAAa;AAAA,UACf;AAAA,QACF,IACA,CAAC;AAAA,QACL,GAAI,YAAY,cAAc,EAAE,eAAe,YAAY,YAAY,IAAI,CAAC;AAAA,QAC5E,cAAc;AAAA,UACZ,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,GAAI,YAAY,gBACZ;AAAA,UACE,WAAW;AAAA,YACT,SAAS;AAAA,YACT,MAAM,YAAY;AAAA,UACpB;AAAA,QACF,IACA,CAAC;AAAA,MACP;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,kBACE,OACA,eAAuC,CAAC,GACxC,UAAkB,MAClB;AAEA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW;AAAA,QAClB,MAAM,MAAM;AAAA,QACZ,aAAa,MAAM;AAAA,QACnB,OAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,GAAI,aAAa,SAAS,IACtB,EAAE,iBAAiB,gCAAgC,YAAY,EAAE,IACjE,CAAC;AAAA,QACL,QAAQ,MAAM,IAAI,CAAC,UAAU;AAAA,UAC3B,SAAS;AAAA,UACT,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,OAAO,KAAK,MAAM,QAAQ,YAAY,EAAE,KAAK;AAAA,UAC7C,eAAe,KAAK,iBAAiB;AAAA,UACrC,cAAc;AAAA,UACd,KAAK,GAAG,MAAM,GAAG;AAAA,UACjB,GAAI,KAAK,gBACL;AAAA,YACE,oBAAoB;AAAA,cAClB,SAAS;AAAA,cACT,OAAO,KAAK,MAAM,QAAQ,YAAY,EAAE,KAAK;AAAA,cAC7C,eAAe,KAAK,iBAAiB;AAAA,cACrC,iBAAiB,KAAK;AAAA,YACxB;AAAA,UACF,IACA,CAAC;AAAA,QACP,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA,IAGA,YAAY,YAAiC,SAAiB,MAAM;AAClE,YAAM,OAAO,WAAW,QAAQ,0BAA0B,WAAW,IAAI;AAEzE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,MAAM,IAAI;AAAA,QAC5B,MAAM,WAAW;AAAA,QACjB,aAAa,WAAW,WAAW,WAAW;AAAA,QAC9C,GAAI,WAAW,aAAa,EAAE,YAAY,WAAW,WAAW,IAAI,CAAC;AAAA,QACrE,GAAI,WAAW,aAAa,EAAE,UAAU,WAAW,WAAW,UAAU,EAAE,IAAI,CAAC;AAAA,QAC/E,GAAI,WAAW,WACX,EAAE,UAAU,kBAAkB,WAAW,QAAQ,EAAE,IACnD,CAAC;AAAA,QACL,GAAI,WAAW,eAAe,EAAE,YAAY,WAAW,aAAa,IAAI,CAAC;AAAA,QACzE,GAAI,WAAW,eAAe,EAAE,cAAc,WAAW,aAAa,IAAI,CAAC;AAAA,QAC3E,YAAY;AAAA,QACZ,WAAW;AAAA,UACT,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,GAAI,WAAW,kBAAkB,EAAE,YAAY,WAAW,gBAAgB,IAAI,CAAC;AAAA,MACjF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,qBAAqB,MAAgC;AACnD,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,qBAAqB,KAAK,IAAI;AAAA,QAChD,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QACpC,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QACvC,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,QAC5D,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,QACnD,GAAI,KAAK,UAAU,KAAK,OAAO,SAAS,IAAI,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,MACzE;AAAA,IACF;AAAA;AAAA,IAGA,mBAAmB,MAAuB,SAAiB,MAAM;AAC/D,YAAM,UAAU,KAAK,WACjB,GAAG,MAAM,GAAG,GAAG,KAAK,IAAI,IAAI,KAAK,QAAQ,KACzC,WAAW,MAAM,KAAK,IAAI;AAC9B,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO;AAAA,QACP,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,QAC5D,GAAI,KAAK,WAAW,EAAE,YAAY,KAAK,SAAS,IAAI,CAAC;AAAA,QACrD,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,QACnD,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,QAC/D,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,QACnD,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,YAAY;AAAA,QACZ,WAAW,EAAE,OAAO,WAAW,aAAa;AAAA,MAC9C;AAAA,IACF;AAAA;AAAA,IAGA,MAAM,OAAoB,MAAiB,SAAiB,MAAM;AAChE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,YAAY;AAAA,QACZ,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,QACtD,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC1C,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,UAChC,SAAS;AAAA,UACT,UAAU,QAAQ;AAAA,UAClB,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,UACX,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,UACpC,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC5C,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,QAAQ,QAAuB,UAAkB,MAAM;AAErD,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,WAAW,QAAQ,OAAO,IAAI;AAAA,QACrC,MAAM,OAAO;AAAA,QACb,aAAa,OAAO;AAAA,QACpB,KAAK,OAAO,IAAI,WAAW,MAAM,IAAI,OAAO,MAAM,GAAG,MAAM,GAAG,GAAG,OAAO,GAAG;AAAA,QAC3E,UAAU;AAAA,UACR,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,QACd;AAAA,QACA,YAAY;AAAA,QACZ,GAAI,OAAO,QACP;AAAA,UACE,QAAQ;AAAA,YACN,SAAS;AAAA,YACT,OAAO,OAAO,MAAM,QAAQ,YAAY,EAAE,KAAK;AAAA,YAC/C,eAAe,OAAO,iBAAiB;AAAA,YACvC,cAAc;AAAA,UAChB;AAAA,QACF,IACA,CAAC;AAAA,QACL,gBAAgB,GAAG,MAAM,GAAG;AAAA,MAC9B;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,WAAW,KAAsB,UAAkB,MAAM;AAEvD,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,GAAG,MAAM,GAAG,YAAY,IAAI,IAAI;AAAA,QACvC,OAAO,IAAI;AAAA,QACX,aAAa,IAAI,eAAe;AAAA,QAChC,YAAY,IAAI,gBAAgB,IAAI;AAAA,QACpC,gBAAgB,kBAAkB,IAAI,IAAI;AAAA,QAC1C,oBAAoB;AAAA,UAClB,SAAS;AAAA,UACT,OAAO,WAAW;AAAA,UAClB,MAAM,MAAM;AAAA,UACZ,QAAQ,MAAM;AAAA,QAChB;AAAA,QACA,GAAI,IAAI,WACJ;AAAA,UACE,aAAa;AAAA,YACX,SAAS;AAAA,YACT,SAAS;AAAA,cACP,SAAS;AAAA,cACT,iBAAiB,IAAI;AAAA,YACvB;AAAA,UACF;AAAA,QACF,IACA;AAAA,UACE,iBAAiB;AAAA,QACnB;AAAA,MACN;AAAA,IACF;AAAA;AAAA,IAGA,SAAS,OAAsB,UAAkB;AAC/C,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,MAAM;AAAA,QACN,eAAe,MAAM;AAAA,QACrB,iBAAiB,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,UAC3C,SAAS;AAAA,UACT,UAAU,KAAK,YAAY,QAAQ;AAAA,UACnC,MAAM,KAAK;AAAA,UACX,KAAK,KAAK,IAAI,WAAW,MAAM,IAAI,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK,GAAG;AAAA,UACrE,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,UAC5D,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC5C,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA,IAGA,eAAe,OAAgC,SAAiB,MAAM;AACpE,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,OAAO,GAAG,MAAM,GAAG;AAAA,QACnB,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,gBAAgB,MAAM,IAAI,CAAC,UAAU;AAAA,UACnC,SAAS;AAAA,UACT,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,KAAK,KAAK,OAAO,GAAG,MAAM,GAAG,uBAAuB,KAAK,IAAI;AAAA,QAC/D,EAAE;AAAA,MACJ;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,UAAU,cAAiD;AACzD,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,IACF;AAAA;AAAA,IAGA,WAAW;AAAA,EACb;AACF;AASO,SAAS,gCAAgC,cAAsC;AACpF,QAAM,QAAQ,aAAa,OAAO,CAAC,MAAM,EAAE,eAAe,QAAQ,EAAE,cAAc,CAAC;AAEnF,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,aAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,MAAM,MAAM,OAAO,CAAC,KAAK,MAAM,OAAO,EAAE,eAAe,IAAI,CAAC;AAClE,QAAM,OAAO,MAAM,MAAM,QAAQ,QAAQ,CAAC;AAE1C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,aAAa;AAAA,IACb,aAAa,OAAO,MAAM,MAAM;AAAA,IAChC,YAAY;AAAA,IACZ,aAAa;AAAA,EACf;AACF;AAgBO,SAAS,kBACX,SACsB;AACzB,QAAM,QAAQ,QAAQ,IAAI,CAAC,WAAW;AAEpC,UAAM,EAAE,YAAY,GAAG,GAAG,KAAK,IAAI;AACnC,WAAO;AAAA,EACT,CAAC;AAED,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,UAAU;AAAA,EACZ;AACF;AAUO,SAAS,aAAa,SAAiB,UAAkB,QAAyB;AACvF,QAAM,QAAQ,aAAa,MAAM,MAAM,SAAS,QAAQ,QAAQ,EAAE;AAElE,MAAI,UAAU,WAAW,MAAM;AAC7B,UAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,KAAK,MAAM,OAAO,GAAG,IAAI,KAAK;AACxE,UAAM,OAAO,aAAa,MAAM,KAAK;AACrC,WAAO,GAAG,OAAO,IAAI,MAAM,GAAG,IAAI;AAAA,EACpC;AAEA,SAAO,GAAG,OAAO,GAAG,KAAK;AAC3B;;;AClwBI;AAJG,SAAS,OAAO,EAAE,MAAM,MAAM,GAAgB;AACnD,QAAM,OAAO,KAAK,UAAU,IAAI,EAAE,QAAQ,gBAAgB,aAAa;AAEvE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,yBAAyB,EAAE,QAAQ,KAAK;AAAA;AAAA,EAC1C;AAEJ","sourcesContent":["/**\n * Schema.org Generator System\n *\n * Pure functions that generate structured data objects for SEO.\n * Each generator accepts a `BrandConfig` and an optional `locale`. `inLanguage`\n * is emitted ONLY on schema types where schema.org permits it (CreativeWork\n * subtypes: WebPage, WebSite, Article, FAQPage, VideoObject, DefinedTermSet,\n * HowTo, SoftwareApplication). Types that forbid it (Organization, Person,\n * BreadcrumbList, Product, Service, JobPosting) accept `locale` for signature\n * stability but do not emit it.\n *\n * Architecture:\n * - Each generator returns an object with its own `@context` (standalone-safe).\n * - `buildPageGraph()` strips individual `@context` and wraps all schemas\n * in a single `{\"@context\": \"...\", \"@graph\": [...]}` for unified output.\n * - Use `JsonLd` React component to render the @graph into a single <script> tag.\n *\n * @example\n * ```ts\n * import { createSchemaGenerators, buildPageGraph } from '@salesmind-ai/design-system/web';\n *\n * const seo = createSchemaGenerators({\n * name: 'SalesMind AI',\n * url: 'https://sales-mind.ai',\n * description: 'AI sales automation',\n * tagline: 'AI-powered sales',\n * founder: 'Julien Gadea',\n * });\n *\n * const graph = buildPageGraph(\n * seo.organization(),\n * seo.website(),\n * seo.webPage('/pricing', 'Pricing', '...'),\n * );\n * ```\n */\n\nimport type {\n BrandConfig,\n ArticleMeta,\n BreadcrumbItem,\n CareerForSchema,\n CustomerOrganizationMeta,\n FAQItem,\n GlossaryTermForSchema,\n HowToMeta,\n HowToStep,\n ItemForList,\n PersonMeta,\n PricingPlan,\n ServiceMotion,\n SoftwareApplicationSchemaOptions,\n TestimonialForSchema,\n TranscriptForSchema,\n VideoObjectMeta,\n} from './types';\n\n// ─── Internal Helpers ────────────────────────────────────────────────────────\n\n/** Convert \"MM:SS\" or \"HH:MM:SS\" to ISO 8601 duration \"PT…\" */\nfunction toISO8601Duration(duration: string): string {\n if (duration.startsWith('PT')) return duration;\n const parts = duration.split(':').map(Number);\n if (parts.some(isNaN)) return duration;\n if (parts.length === 3) return `PT${parts[0]}H${parts[1]}M${parts[2]}S`;\n if (parts.length === 2) return `PT${parts[0]}M${parts[1]}S`;\n return `PT${parts[0]}S`;\n}\n\n/** Convert YouTube watch URL to embed URL */\nfunction toEmbedUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url);\n if (parsed.hostname.includes('youtube.com') && parsed.searchParams.has('v')) {\n return `https://www.youtube.com/embed/${parsed.searchParams.get('v')}`;\n }\n if (parsed.hostname === 'youtu.be') {\n return `https://www.youtube.com/embed${parsed.pathname}`;\n }\n return url;\n } catch {\n return undefined;\n }\n}\n\n/** Map career type string to Schema.org employmentType */\nfunction mapEmploymentType(type?: string | null): string {\n if (!type) return 'FULL_TIME';\n const lower = type.toLowerCase();\n if (lower.includes('part')) return 'PART_TIME';\n if (lower.includes('contract')) return 'CONTRACTOR';\n if (lower.includes('intern')) return 'INTERN';\n if (lower.includes('temporary') || lower.includes('temp')) return 'TEMPORARY';\n return 'FULL_TIME';\n}\n\n// ─── Entity ID Factory ──────────────────────────────────────────────────────\n\n/**\n * Create stable entity ID anchors for @graph cross-referencing.\n * Every entity in the knowledge graph gets a permanent @id.\n * Google resolves @id references across the entire site.\n */\n// Returns either `pathOrUrl` as-is (when it is an absolute http(s) URL) or\n// concatenated to `brand.url`. Defensive against callers that pass an absolute\n// URL where a relative path is expected — prevents the doubled-prefix bug\n// `https://brand.aihttps://brand.ai/page#anchor`.\nfunction normalizeEntityPath(brandUrl: string, pathOrUrl: string): string {\n if (/^https?:\\/\\//i.test(pathOrUrl)) return pathOrUrl;\n return `${brandUrl}${pathOrUrl}`;\n}\n\nexport function createEntityIds(brand: BrandConfig) {\n return {\n organization: `${brand.url}/#organization`,\n website: `${brand.url}/#website`,\n software: `${brand.url}/#software`,\n product: `${brand.url}/pricing#product`,\n webpage: (path: string) => `${normalizeEntityPath(brand.url, path)}#webpage`,\n article: (path: string) => `${normalizeEntityPath(brand.url, path)}#article`,\n faq: (path: string) => `${normalizeEntityPath(brand.url, path)}#faq`,\n breadcrumb: (path: string) => `${normalizeEntityPath(brand.url, path)}#breadcrumb`,\n person: (slug: string) => `${brand.url}/company/team#person-${slug}`,\n video: (path: string) => `${normalizeEntityPath(brand.url, path)}#video`,\n review: (id: string) => `${brand.url}/#review-${id}`,\n service: (slug: string) => `${brand.url}/#service-${slug}`,\n customerOrganization: (slug: string) => `${brand.url}/#customer-org-${slug}`,\n customerPerson: (slug: string) => `${brand.url}/#customer-person-${slug}`,\n } as const;\n}\n\n// ─── Schema Generator Factory ───────────────────────────────────────────────\n\n/**\n * Create all schema generators bound to a specific brand configuration.\n * Returns an object with methods for each schema type.\n */\nexport function createSchemaGenerators(\n brand: BrandConfig,\n supportedLanguages: readonly string[] = ['en', 'fr', 'es'],\n) {\n const ENTITY_IDS = createEntityIds(brand);\n\n return {\n /**\n * Organization schema — Homepage + About page.\n *\n * Note: schema.org does NOT allow `inLanguage` or `availableLanguage` on\n * Organization. The multilingual signal is carried by `knowsLanguage`\n * (valid on Organization, BCP-47 codes). The `locale` param is retained\n * for call-site signature stability but no longer emitted here.\n */\n organization(_locale: string = 'en') {\n void _locale;\n return {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n url: brand.url,\n logo: {\n '@type': 'ImageObject',\n url: `${brand.url}/logo.svg`,\n },\n description: brand.description,\n knowsLanguage: [...supportedLanguages],\n founder: {\n '@type': 'Person',\n name: brand.founder,\n },\n ...(brand.sameAs && brand.sameAs.length > 0 ? { sameAs: brand.sameAs } : {}),\n contactPoint: [] as Record<string, unknown>[],\n areaServed: 'Worldwide',\n };\n },\n\n /** WebSite schema — Homepage only (with SearchAction). */\n website(locale: string = 'en') {\n return {\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n '@id': ENTITY_IDS.website,\n name: brand.name,\n url: brand.url,\n description: brand.tagline,\n inLanguage: locale,\n publisher: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n potentialAction: [\n {\n '@type': 'SearchAction',\n target: {\n '@type': 'EntryPoint',\n urlTemplate: `${brand.url}/blog?q={search_term_string}`,\n },\n 'query-input': 'required name=search_term_string',\n },\n {\n '@type': 'ReadAction',\n target: brand.url,\n },\n ],\n };\n },\n\n /** WebPage schema — Every public page. Links to WebSite + Breadcrumb via @id. */\n webPage(path: string, title: string, description: string, locale: string = 'en', speakable?: Record<string, unknown>) {\n return {\n '@context': 'https://schema.org',\n '@type': 'WebPage',\n '@id': ENTITY_IDS.webpage(path),\n url: `${brand.url}${path}`,\n name: title,\n description,\n inLanguage: locale,\n // Reference stub to the site-wide WebSite node. `url`/`name` are\n // included (when the brand provides them) so the reference stands as a\n // complete WebSite on pages rendered independently of the WebSite node,\n // per schema.org @id-linking guidance. Guarded so a brand without\n // `url`/`name` never emits `undefined`.\n isPartOf: {\n '@type': 'WebSite',\n '@id': ENTITY_IDS.website,\n ...(brand.url ? { url: brand.url } : {}),\n ...(brand.name ? { name: brand.name } : {}),\n },\n breadcrumb: { '@id': ENTITY_IDS.breadcrumb(path) },\n potentialAction: {\n '@type': 'ReadAction',\n target: `${brand.url}${path}`,\n },\n ...(speakable ? { speakable } : {}),\n };\n },\n\n /** SoftwareApplication schema — Platform Hub, Platform Feature, Pricing. */\n softwareApplication(\n name?: string,\n description?: string,\n url?: string,\n locale: string = 'en',\n options: SoftwareApplicationSchemaOptions = {},\n ) {\n const aggregateRating = options.aggregateRating ?? brand.aggregateRating;\n const offers = options.offers ?? brand.softwareApplicationOffers;\n\n return {\n '@context': 'https://schema.org',\n '@type': 'SoftwareApplication',\n '@id': ENTITY_IDS.software,\n name: name || brand.name,\n description: description || brand.description,\n url: url || brand.url,\n applicationCategory: 'BusinessApplication',\n operatingSystem: 'Web',\n // `inLanguage` is valid — SoftwareApplication is a CreativeWork subtype.\n // `availableLanguage` is NOT valid on SoftwareApplication and is dropped.\n inLanguage: locale,\n // Google requires `offers` with a price. We expose only the price floor\n // via AggregateOffer so A/B-tested tier prices stay hidden.\n ...(offers\n ? {\n offers: {\n '@type': 'AggregateOffer',\n lowPrice: offers.lowPrice,\n priceCurrency: offers.priceCurrency,\n offerCount: offers.offerCount,\n url: offers.url ?? `${brand.url}/pricing`,\n availability: 'https://schema.org/InStock',\n },\n }\n : {}),\n ...(aggregateRating\n ? {\n aggregateRating: {\n '@type': 'AggregateRating',\n ratingValue: String(aggregateRating.ratingValue),\n ratingCount: String(aggregateRating.reviewCount),\n bestRating: String(aggregateRating.bestRating ?? 5),\n worstRating: String(aggregateRating.worstRating ?? 1),\n },\n }\n : {}),\n author: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n };\n },\n\n /** Article schema — Blog posts, Comparison pages. */\n article(meta: ArticleMeta, locale: string = 'en') {\n let path: string;\n try {\n path = new URL(meta.url).pathname;\n } catch {\n path = meta.url.startsWith('/') ? meta.url : `/${meta.url}`;\n }\n\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n '@id': ENTITY_IDS.article(path),\n headline: meta.title,\n description: meta.description,\n url: meta.url.startsWith('http') ? meta.url : `${brand.url}${meta.url}`,\n image: meta.image || `${brand.url}/og-default.png`,\n datePublished: meta.publishedTime,\n dateModified: meta.modifiedTime,\n inLanguage: locale,\n author: meta.authorSlug\n ? { '@id': ENTITY_IDS.person(meta.authorSlug) }\n : {\n '@type': 'Person',\n name: meta.authorName,\n url: meta.authorUrl || `${brand.url}/company/team`,\n },\n publisher: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n logo: {\n '@type': 'ImageObject',\n url: `${brand.url}/logo.svg`,\n },\n },\n isPartOf: { '@type': 'WebSite', '@id': ENTITY_IDS.website },\n mainEntityOfPage: { '@id': ENTITY_IDS.webpage(path) },\n articleSection: meta.section,\n keywords: meta.tags?.join(', '),\n speakable: {\n '@type': 'SpeakableSpecification',\n cssSelector: meta.speakableCssSelectors || ['article h1', 'article .summary', 'article .lead'],\n },\n ...(meta.about ? { about: meta.about } : {}),\n ...(meta.video ? { video: meta.video } : {}),\n };\n },\n\n /** FAQPage schema — FAQ hub, Solutions, Platform Features, Comparisons, Integrations. */\n faqPage(items: FAQItem[], pageUrl: string, locale: string = 'en') {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n '@id': ENTITY_IDS.faq(pageUrl),\n inLanguage: locale,\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: item.answer,\n inLanguage: locale,\n },\n })),\n };\n },\n\n /**\n * BreadcrumbList schema — Every public page.\n * `inLanguage` is NOT valid on BreadcrumbList and is not emitted. The\n * `locale` param is retained for call-site signature stability.\n */\n breadcrumb(items: BreadcrumbItem[], _locale: string = 'en') {\n void _locale;\n const lastUrl = items.length > 0 ? items[items.length - 1].url : '/';\n const path = lastUrl.startsWith('http') ? new URL(lastUrl).pathname : lastUrl;\n\n return {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n '@id': ENTITY_IDS.breadcrumb(path),\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: item.name,\n item: item.url.startsWith('http') ? item.url : `${brand.url}${item.url}`,\n })),\n };\n },\n\n /**\n * Person schema — Team page, Blog author, Comparison author.\n * `inLanguage` is NOT valid on Person (Person is not a CreativeWork) and is\n * not emitted. The `locale` param is retained for signature stability.\n */\n person(person: PersonMeta, _locale: string = 'en') {\n void _locale;\n const slug = person.slug || person.name.toLowerCase().replace(/\\s+/g, '-');\n\n return {\n '@context': 'https://schema.org',\n '@type': 'Person',\n '@id': person.entityIdOverride || ENTITY_IDS.person(slug),\n name: person.name,\n jobTitle: person.jobTitle,\n url: person.url,\n image: person.image,\n worksFor: person.worksFor || {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n sameAs: person.sameAs || [],\n };\n },\n\n /** Review schema — Generated from testimonials. */\n review(testimonial: TestimonialForSchema) {\n return {\n '@context': 'https://schema.org',\n '@type': 'Review',\n '@id': ENTITY_IDS.review(testimonial.id),\n author: {\n '@type': 'Person',\n ...(testimonial.authorEntityId ? { '@id': testimonial.authorEntityId } : {}),\n name: testimonial.customer_name,\n ...(testimonial.occupation ? { jobTitle: testimonial.occupation } : {}),\n },\n reviewBody: testimonial.quote || '',\n ...(testimonial.star_rating\n ? {\n reviewRating: {\n '@type': 'Rating',\n ratingValue: String(testimonial.star_rating),\n bestRating: '5',\n worstRating: '1',\n },\n }\n : {}),\n ...(testimonial.review_date ? { datePublished: testimonial.review_date } : {}),\n itemReviewed: {\n '@type': 'SoftwareApplication',\n '@id': ENTITY_IDS.software,\n name: brand.name,\n },\n ...(testimonial.review_source\n ? {\n publisher: {\n '@type': 'Organization',\n name: testimonial.review_source,\n },\n }\n : {}),\n };\n },\n\n /**\n * Product + multiple Offers schema — Pricing page.\n * `inLanguage` is NOT valid on Product and is not emitted. The `locale`\n * param is retained for call-site signature stability.\n */\n productWithOffers(\n plans: PricingPlan[],\n testimonials: TestimonialForSchema[] = [],\n _locale: string = 'en',\n ) {\n void _locale;\n return {\n '@context': 'https://schema.org',\n '@type': 'Product',\n '@id': ENTITY_IDS.product,\n name: brand.name,\n description: brand.description,\n brand: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n ...(testimonials.length > 0\n ? { aggregateRating: aggregateRatingFromTestimonials(testimonials) }\n : {}),\n offers: plans.map((plan) => ({\n '@type': 'Offer',\n name: plan.name,\n description: plan.description,\n price: plan.price.replace(/[^0-9.]/g, '') || '0',\n priceCurrency: plan.priceCurrency || 'USD',\n availability: 'https://schema.org/InStock',\n url: `${brand.url}/pricing`,\n ...(plan.billingPeriod\n ? {\n priceSpecification: {\n '@type': 'UnitPriceSpecification',\n price: plan.price.replace(/[^0-9.]/g, '') || '0',\n priceCurrency: plan.priceCurrency || 'USD',\n billingDuration: plan.billingPeriod,\n },\n }\n : {}),\n })),\n };\n },\n\n /** VideoObject schema — Transcript detail pages. */\n videoObject(transcript: TranscriptForSchema, locale: string = 'en') {\n const path = transcript.path || `/resources/transcripts/${transcript.slug}`;\n\n return {\n '@context': 'https://schema.org',\n '@type': 'VideoObject',\n '@id': ENTITY_IDS.video(path),\n name: transcript.title,\n description: transcript.summary || transcript.title,\n ...(transcript.video_link ? { contentUrl: transcript.video_link } : {}),\n ...(transcript.video_link ? { embedUrl: toEmbedUrl(transcript.video_link) } : {}),\n ...(transcript.duration\n ? { duration: toISO8601Duration(transcript.duration) }\n : {}),\n ...(transcript.published_at ? { uploadDate: transcript.published_at } : {}),\n ...(transcript.thumbnailUrl ? { thumbnailUrl: transcript.thumbnailUrl } : {}),\n inLanguage: locale,\n publisher: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n ...(transcript.transcript_text ? { transcript: transcript.transcript_text } : {}),\n };\n },\n\n /**\n * CustomerOrganization schema — Customer's company for Success Story pages.\n * `industry` is NOT valid on Organization (schema.org allows only the coded\n * `naics` / `isicV4`, plus free-text `keywords`). The free-text industry\n * value is carried by `keywords`.\n */\n customerOrganization(meta: CustomerOrganizationMeta) {\n return {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n '@id': ENTITY_IDS.customerOrganization(meta.slug),\n name: meta.name,\n ...(meta.url ? { url: meta.url } : {}),\n ...(meta.logo ? { logo: meta.logo } : {}),\n ...(meta.description ? { description: meta.description } : {}),\n ...(meta.industry ? { keywords: meta.industry } : {}),\n ...(meta.sameAs && meta.sameAs.length > 0 ? { sameAs: meta.sameAs } : {}),\n };\n },\n\n /** Generic VideoObject — Success story main video and shorts. */\n videoObjectGeneric(meta: VideoObjectMeta, locale: string = 'en') {\n const videoId = meta.fragment\n ? `${brand.url}${meta.path}#${meta.fragment}`\n : ENTITY_IDS.video(meta.path);\n return {\n '@context': 'https://schema.org',\n '@type': 'VideoObject',\n '@id': videoId,\n name: meta.title,\n ...(meta.description ? { description: meta.description } : {}),\n ...(meta.videoUrl ? { contentUrl: meta.videoUrl } : {}),\n ...(meta.embedUrl ? { embedUrl: meta.embedUrl } : {}),\n ...(meta.thumbnailUrl ? { thumbnailUrl: meta.thumbnailUrl } : {}),\n ...(meta.duration ? { duration: meta.duration } : {}),\n ...(meta.uploadDate ? { uploadDate: meta.uploadDate } : {}),\n ...(meta.transcript ? { transcript: meta.transcript } : {}),\n inLanguage: locale,\n publisher: { '@id': ENTITY_IDS.organization },\n };\n },\n\n /** HowTo schema — Solution pages, tutorial blog posts. */\n howTo(steps: HowToStep[], meta: HowToMeta, locale: string = 'en') {\n return {\n '@context': 'https://schema.org',\n '@type': 'HowTo',\n name: meta.name,\n description: meta.description,\n inLanguage: locale,\n ...(meta.totalTime ? { totalTime: meta.totalTime } : {}),\n ...(meta.image ? { image: meta.image } : {}),\n step: steps.map((step, index) => ({\n '@type': 'HowToStep',\n position: index + 1,\n name: step.name,\n text: step.text,\n ...(step.url ? { url: step.url } : {}),\n ...(step.image ? { image: step.image } : {}),\n })),\n };\n },\n\n /**\n * Service schema — Growth motion service offerings.\n * Neither `inLanguage` nor `availableLanguage` is valid on Service, so both\n * are dropped. The `locale` param is retained for signature stability.\n */\n service(motion: ServiceMotion, _locale: string = 'en') {\n void _locale;\n return {\n '@context': 'https://schema.org',\n '@type': 'Service',\n '@id': ENTITY_IDS.service(motion.slug),\n name: motion.name,\n description: motion.description,\n url: motion.url.startsWith('http') ? motion.url : `${brand.url}${motion.url}`,\n provider: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n },\n areaServed: 'Worldwide',\n ...(motion.price\n ? {\n offers: {\n '@type': 'Offer',\n price: motion.price.replace(/[^0-9.]/g, '') || '0',\n priceCurrency: motion.priceCurrency || 'USD',\n availability: 'https://schema.org/InStock',\n },\n }\n : {}),\n termsOfService: `${brand.url}/legal/terms`,\n };\n },\n\n /**\n * JobPosting schema — Career detail pages.\n * `inLanguage` is NOT valid on JobPosting and is not emitted. The `locale`\n * param is retained for call-site signature stability.\n */\n jobPosting(job: CareerForSchema, _locale: string = 'en') {\n void _locale;\n return {\n '@context': 'https://schema.org',\n '@type': 'JobPosting',\n '@id': `${brand.url}/careers/${job.slug}#jobposting`,\n title: job.title,\n description: job.description || '',\n datePosted: job.published_at || job.created_at,\n employmentType: mapEmploymentType(job.type),\n hiringOrganization: {\n '@type': 'Organization',\n '@id': ENTITY_IDS.organization,\n name: brand.name,\n sameAs: brand.url,\n },\n ...(job.location\n ? {\n jobLocation: {\n '@type': 'Place',\n address: {\n '@type': 'PostalAddress',\n addressLocality: job.location,\n },\n },\n }\n : {\n jobLocationType: 'TELECOMMUTE',\n }),\n };\n },\n\n /** ItemList schema — Hub/listing pages. */\n itemList(items: ItemForList[], listName: string) {\n return {\n '@context': 'https://schema.org',\n '@type': 'ItemList',\n name: listName,\n numberOfItems: items.length,\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem',\n position: item.position ?? index + 1,\n name: item.name,\n url: item.url.startsWith('http') ? item.url : `${brand.url}${item.url}`,\n ...(item.description ? { description: item.description } : {}),\n ...(item.image ? { image: item.image } : {}),\n })),\n };\n },\n\n /** DefinedTermSet schema — Glossary page. */\n definedTermSet(terms: GlossaryTermForSchema[], locale: string = 'en') {\n return {\n '@context': 'https://schema.org',\n '@type': 'DefinedTermSet',\n '@id': `${brand.url}/resources/glossary#termset`,\n name: 'AI Sales Glossary',\n inLanguage: locale,\n hasDefinedTerm: terms.map((term) => ({\n '@type': 'DefinedTerm',\n name: term.term,\n description: term.definition,\n url: term.url || `${brand.url}/resources/glossary#${term.slug}`,\n })),\n };\n },\n\n /**\n * Speakable — marks content sections suitable for voice assistant reading.\n *\n * Includes its own `@context` so it is valid when rendered as a standalone\n * top-level JSON-LD node. When embedded inside another schema (e.g. as the\n * `speakable` value on WebPage/Article), the surrounding `buildPageGraph`\n * strips the redundant `@context`, so the nested case is unaffected.\n */\n speakable(cssSelectors: string[]): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'SpeakableSpecification',\n cssSelector: cssSelectors,\n };\n },\n\n /** Access the entity IDs for cross-referencing */\n entityIds: ENTITY_IDS,\n };\n}\n\n// ─── Standalone Utilities ───────────────────────────────────────────────────\n\n/**\n * Compute AggregateRating from real testimonial data.\n * Falls back to hardcoded 4.7/150 when no rated testimonials exist.\n * Returns a fragment (no @context) — meant to be embedded in Product/SoftwareApp.\n */\nexport function aggregateRatingFromTestimonials(testimonials: TestimonialForSchema[]) {\n const rated = testimonials.filter((t) => t.star_rating != null && t.star_rating > 0);\n\n if (rated.length === 0) {\n return {\n '@type': 'AggregateRating',\n ratingValue: '4.7',\n ratingCount: '150',\n bestRating: '5',\n worstRating: '1',\n };\n }\n\n const sum = rated.reduce((acc, t) => acc + (t.star_rating || 0), 0);\n const avg = (sum / rated.length).toFixed(1);\n\n return {\n '@type': 'AggregateRating',\n ratingValue: avg,\n ratingCount: String(rated.length),\n bestRating: '5',\n worstRating: '1',\n };\n}\n\n/**\n * Assemble multiple schema entities into a single @graph array.\n * Strips individual `@context` from each schema — the wrapper provides it once.\n *\n * @example\n * ```ts\n * const graph = buildPageGraph(\n * seo.organization(),\n * seo.website(),\n * seo.webPage('/pricing', 'Pricing', '...'),\n * seo.breadcrumb(crumbs),\n * );\n * ```\n */\nexport function buildPageGraph(\n ...schemas: Record<string, unknown>[]\n): Record<string, unknown> {\n const graph = schemas.map((schema) => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { '@context': _, ...rest } = schema;\n return rest;\n });\n\n return {\n '@context': 'https://schema.org',\n '@graph': graph,\n };\n}\n\n/**\n * Build canonical URL from pathname.\n * Removes trailing slash (except root) and query params.\n *\n * @param baseUrl - The site's base URL (e.g. \"https://sales-mind.ai\")\n * @param pathname - The route path (e.g. \"/pricing\", \"/blog/post/foo\")\n * @param locale - BCP-47 locale code. 'en' = no prefix (default). Others get /{locale}/ prefix.\n */\nexport function canonicalUrl(baseUrl: string, pathname: string, locale?: string): string {\n const clean = pathname === '/' ? '/' : pathname.replace(/\\/+$/, '');\n\n if (locale && locale !== 'en') {\n const stripped = clean.replace(new RegExp(`^/${locale}(/|$)`), '$1') || '/';\n const path = stripped === '/' ? '' : stripped;\n return `${baseUrl}/${locale}${path}`;\n }\n\n return `${baseUrl}${clean}`;\n}\n","/**\n * JsonLd — Renders structured data as a `<script type=\"application/ld+json\">` tag.\n *\n * Framework-agnostic: works with any React-based framework (Next.js, Vite, Remix, etc.).\n * Safely serializes the data object and escapes `</script>` sequences.\n *\n * @example\n * ```tsx\n * import { JsonLd, buildPageGraph } from '@salesmind-ai/design-system/web';\n *\n * <JsonLd data={buildPageGraph(seo.organization(), seo.website())} />\n * ```\n */\nexport interface JsonLdProps {\n /** The structured data object (schema.org format) */\n data: Record<string, unknown>;\n /** Optional nonce for CSP headers */\n nonce?: string;\n}\n\nexport function JsonLd({ data, nonce }: JsonLdProps) {\n const json = JSON.stringify(data).replace(/<\\/script>/gi, '<\\\\/script>');\n\n return (\n <script\n type=\"application/ld+json\"\n nonce={nonce}\n dangerouslySetInnerHTML={{ __html: json }}\n />\n );\n}\n"]}
@@ -57,8 +57,15 @@ function createEntityIds(brand) {
57
57
  function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"]) {
58
58
  const ENTITY_IDS = createEntityIds(brand);
59
59
  return {
60
- /** Organization schema — Homepage + About page. */
61
- organization(locale = "en") {
60
+ /**
61
+ * Organization schema — Homepage + About page.
62
+ *
63
+ * Note: schema.org does NOT allow `inLanguage` or `availableLanguage` on
64
+ * Organization. The multilingual signal is carried by `knowsLanguage`
65
+ * (valid on Organization, BCP-47 codes). The `locale` param is retained
66
+ * for call-site signature stability but no longer emitted here.
67
+ */
68
+ organization(_locale = "en") {
62
69
  return {
63
70
  "@context": "https://schema.org",
64
71
  "@type": "Organization",
@@ -70,8 +77,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
70
77
  url: `${brand.url}/logo.svg`
71
78
  },
72
79
  description: brand.description,
73
- inLanguage: locale,
74
- availableLanguage: [...supportedLanguages],
80
+ knowsLanguage: [...supportedLanguages],
75
81
  founder: {
76
82
  "@type": "Person",
77
83
  name: brand.founder
@@ -122,7 +128,17 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
122
128
  name: title,
123
129
  description,
124
130
  inLanguage: locale,
125
- isPartOf: { "@type": "WebSite", "@id": ENTITY_IDS.website },
131
+ // Reference stub to the site-wide WebSite node. `url`/`name` are
132
+ // included (when the brand provides them) so the reference stands as a
133
+ // complete WebSite on pages rendered independently of the WebSite node,
134
+ // per schema.org @id-linking guidance. Guarded so a brand without
135
+ // `url`/`name` never emits `undefined`.
136
+ isPartOf: {
137
+ "@type": "WebSite",
138
+ "@id": ENTITY_IDS.website,
139
+ ...brand.url ? { url: brand.url } : {},
140
+ ...brand.name ? { name: brand.name } : {}
141
+ },
126
142
  breadcrumb: { "@id": ENTITY_IDS.breadcrumb(path) },
127
143
  potentialAction: {
128
144
  "@type": "ReadAction",
@@ -134,6 +150,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
134
150
  /** SoftwareApplication schema — Platform Hub, Platform Feature, Pricing. */
135
151
  softwareApplication(name, description, url, locale = "en", options = {}) {
136
152
  const aggregateRating = options.aggregateRating ?? brand.aggregateRating;
153
+ const offers = options.offers ?? brand.softwareApplicationOffers;
137
154
  return {
138
155
  "@context": "https://schema.org",
139
156
  "@type": "SoftwareApplication",
@@ -143,8 +160,21 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
143
160
  url: url || brand.url,
144
161
  applicationCategory: "BusinessApplication",
145
162
  operatingSystem: "Web",
163
+ // `inLanguage` is valid — SoftwareApplication is a CreativeWork subtype.
164
+ // `availableLanguage` is NOT valid on SoftwareApplication and is dropped.
146
165
  inLanguage: locale,
147
- availableLanguage: [...supportedLanguages],
166
+ // Google requires `offers` with a price. We expose only the price floor
167
+ // via AggregateOffer so A/B-tested tier prices stay hidden.
168
+ ...offers ? {
169
+ offers: {
170
+ "@type": "AggregateOffer",
171
+ lowPrice: offers.lowPrice,
172
+ priceCurrency: offers.priceCurrency,
173
+ offerCount: offers.offerCount,
174
+ url: offers.url ?? `${brand.url}/pricing`,
175
+ availability: "https://schema.org/InStock"
176
+ }
177
+ } : {},
148
178
  ...aggregateRating ? {
149
179
  aggregateRating: {
150
180
  "@type": "AggregateRating",
@@ -224,15 +254,18 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
224
254
  }))
225
255
  };
226
256
  },
227
- /** BreadcrumbList schema — Every public page. */
228
- breadcrumb(items, locale = "en") {
257
+ /**
258
+ * BreadcrumbList schema Every public page.
259
+ * `inLanguage` is NOT valid on BreadcrumbList and is not emitted. The
260
+ * `locale` param is retained for call-site signature stability.
261
+ */
262
+ breadcrumb(items, _locale = "en") {
229
263
  const lastUrl = items.length > 0 ? items[items.length - 1].url : "/";
230
264
  const path = lastUrl.startsWith("http") ? new URL(lastUrl).pathname : lastUrl;
231
265
  return {
232
266
  "@context": "https://schema.org",
233
267
  "@type": "BreadcrumbList",
234
268
  "@id": ENTITY_IDS.breadcrumb(path),
235
- inLanguage: locale,
236
269
  itemListElement: items.map((item, index) => ({
237
270
  "@type": "ListItem",
238
271
  position: index + 1,
@@ -241,8 +274,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
241
274
  }))
242
275
  };
243
276
  },
244
- /** Person schema — Team page, Blog author, Comparison author. */
245
- person(person, locale = "en") {
277
+ /**
278
+ * Person schema — Team page, Blog author, Comparison author.
279
+ * `inLanguage` is NOT valid on Person (Person is not a CreativeWork) and is
280
+ * not emitted. The `locale` param is retained for signature stability.
281
+ */
282
+ person(person, _locale = "en") {
246
283
  const slug = person.slug || person.name.toLowerCase().replace(/\s+/g, "-");
247
284
  return {
248
285
  "@context": "https://schema.org",
@@ -252,7 +289,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
252
289
  jobTitle: person.jobTitle,
253
290
  url: person.url,
254
291
  image: person.image,
255
- inLanguage: locale,
256
292
  worksFor: person.worksFor || {
257
293
  "@type": "Organization",
258
294
  "@id": ENTITY_IDS.organization,
@@ -296,8 +332,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
296
332
  } : {}
297
333
  };
298
334
  },
299
- /** Product + multiple Offers schema — Pricing page. */
300
- productWithOffers(plans, testimonials = [], locale = "en") {
335
+ /**
336
+ * Product + multiple Offers schema Pricing page.
337
+ * `inLanguage` is NOT valid on Product and is not emitted. The `locale`
338
+ * param is retained for call-site signature stability.
339
+ */
340
+ productWithOffers(plans, testimonials = [], _locale = "en") {
301
341
  return {
302
342
  "@context": "https://schema.org",
303
343
  "@type": "Product",
@@ -309,7 +349,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
309
349
  "@id": ENTITY_IDS.organization,
310
350
  name: brand.name
311
351
  },
312
- inLanguage: locale,
313
352
  ...testimonials.length > 0 ? { aggregateRating: aggregateRatingFromTestimonials(testimonials) } : {},
314
353
  offers: plans.map((plan) => ({
315
354
  "@type": "Offer",
@@ -353,7 +392,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
353
392
  ...transcript.transcript_text ? { transcript: transcript.transcript_text } : {}
354
393
  };
355
394
  },
356
- /** CustomerOrganization schema — Customer's company for Success Story pages. */
395
+ /**
396
+ * CustomerOrganization schema — Customer's company for Success Story pages.
397
+ * `industry` is NOT valid on Organization (schema.org allows only the coded
398
+ * `naics` / `isicV4`, plus free-text `keywords`). The free-text industry
399
+ * value is carried by `keywords`.
400
+ */
357
401
  customerOrganization(meta) {
358
402
  return {
359
403
  "@context": "https://schema.org",
@@ -363,7 +407,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
363
407
  ...meta.url ? { url: meta.url } : {},
364
408
  ...meta.logo ? { logo: meta.logo } : {},
365
409
  ...meta.description ? { description: meta.description } : {},
366
- ...meta.industry ? { industry: meta.industry } : {},
410
+ ...meta.industry ? { keywords: meta.industry } : {},
367
411
  ...meta.sameAs && meta.sameAs.length > 0 ? { sameAs: meta.sameAs } : {}
368
412
  };
369
413
  },
@@ -406,8 +450,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
406
450
  }))
407
451
  };
408
452
  },
409
- /** Service schema — Growth motion service offerings. */
410
- service(motion, locale = "en") {
453
+ /**
454
+ * Service schema Growth motion service offerings.
455
+ * Neither `inLanguage` nor `availableLanguage` is valid on Service, so both
456
+ * are dropped. The `locale` param is retained for signature stability.
457
+ */
458
+ service(motion, _locale = "en") {
411
459
  return {
412
460
  "@context": "https://schema.org",
413
461
  "@type": "Service",
@@ -420,8 +468,6 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
420
468
  "@id": ENTITY_IDS.organization,
421
469
  name: brand.name
422
470
  },
423
- inLanguage: locale,
424
- availableLanguage: [...supportedLanguages],
425
471
  areaServed: "Worldwide",
426
472
  ...motion.price ? {
427
473
  offers: {
@@ -434,8 +480,12 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
434
480
  termsOfService: `${brand.url}/legal/terms`
435
481
  };
436
482
  },
437
- /** JobPosting schema — Career detail pages. */
438
- jobPosting(job, locale = "en") {
483
+ /**
484
+ * JobPosting schema Career detail pages.
485
+ * `inLanguage` is NOT valid on JobPosting and is not emitted. The `locale`
486
+ * param is retained for call-site signature stability.
487
+ */
488
+ jobPosting(job, _locale = "en") {
439
489
  return {
440
490
  "@context": "https://schema.org",
441
491
  "@type": "JobPosting",
@@ -460,8 +510,7 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
460
510
  }
461
511
  } : {
462
512
  jobLocationType: "TELECOMMUTE"
463
- },
464
- inLanguage: locale
513
+ }
465
514
  };
466
515
  },
467
516
  /** ItemList schema — Hub/listing pages. */
@@ -497,9 +546,17 @@ function createSchemaGenerators(brand, supportedLanguages = ["en", "fr", "es"])
497
546
  }))
498
547
  };
499
548
  },
500
- /** Speakable — marks content sections suitable for voice assistant reading. */
549
+ /**
550
+ * Speakable — marks content sections suitable for voice assistant reading.
551
+ *
552
+ * Includes its own `@context` so it is valid when rendered as a standalone
553
+ * top-level JSON-LD node. When embedded inside another schema (e.g. as the
554
+ * `speakable` value on WebPage/Article), the surrounding `buildPageGraph`
555
+ * strips the redundant `@context`, so the nested case is unaffected.
556
+ */
501
557
  speakable(cssSelectors) {
502
558
  return {
559
+ "@context": "https://schema.org",
503
560
  "@type": "SpeakableSpecification",
504
561
  cssSelector: cssSelectors
505
562
  };
@@ -562,4 +619,4 @@ function JsonLd({ data, nonce }) {
562
619
 
563
620
  export { JsonLd, aggregateRatingFromTestimonials, buildPageGraph, canonicalUrl, createEntityIds, createSchemaGenerators };
564
621
  //# sourceMappingURL=out.js.map
565
- //# sourceMappingURL=chunk-25TUCNN6.js.map
622
+ //# sourceMappingURL=chunk-QVGDMAZY.js.map