@salesmind-ai/design-system 1.7.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 (64) hide show
  1. package/dist/ChangeEmail-BuUhjW0o.d.cts +226 -0
  2. package/dist/ChangeEmail-BuUhjW0o.d.ts +226 -0
  3. package/dist/{chunk-3NS6X2R4.cjs → chunk-5EIBL6DQ.cjs} +133 -38
  4. package/dist/chunk-5EIBL6DQ.cjs.map +1 -0
  5. package/dist/chunk-AI2YEYT4.js +1368 -0
  6. package/dist/chunk-AI2YEYT4.js.map +1 -0
  7. package/dist/{chunk-4QQKREVH.cjs → chunk-LM3UJILC.cjs} +3 -3
  8. package/dist/{chunk-4QQKREVH.cjs.map → chunk-LM3UJILC.cjs.map} +1 -1
  9. package/dist/{chunk-K7NQ7TQG.cjs → chunk-M4MLGT4B.cjs} +84 -27
  10. package/dist/chunk-M4MLGT4B.cjs.map +1 -0
  11. package/dist/{chunk-6BUS7RMS.js → chunk-Q3YVBUKQ.js} +133 -38
  12. package/dist/chunk-Q3YVBUKQ.js.map +1 -0
  13. package/dist/{chunk-25TUCNN6.js → chunk-QVGDMAZY.js} +84 -27
  14. package/dist/chunk-QVGDMAZY.js.map +1 -0
  15. package/dist/{chunk-2TC5EVSJ.js → chunk-TPZ3PKSF.js} +3 -3
  16. package/dist/{chunk-2TC5EVSJ.js.map → chunk-TPZ3PKSF.js.map} +1 -1
  17. package/dist/chunk-YWJKSY5C.cjs +1381 -0
  18. package/dist/chunk-YWJKSY5C.cjs.map +1 -0
  19. package/dist/index.cjs +57 -40
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.css +2426 -0
  22. package/dist/index.css.map +1 -1
  23. package/dist/index.d.cts +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +3 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/marketing/index.cjs +27 -27
  28. package/dist/marketing/index.js +1 -1
  29. package/dist/nav/index.cjs +10 -10
  30. package/dist/nav/index.css +54 -0
  31. package/dist/nav/index.css.map +1 -1
  32. package/dist/nav/index.d.cts +9 -0
  33. package/dist/nav/index.d.ts +9 -0
  34. package/dist/nav/index.js +1 -1
  35. package/dist/onboarding/index.cjs +5870 -0
  36. package/dist/onboarding/index.cjs.map +1 -0
  37. package/dist/onboarding/index.css +557 -0
  38. package/dist/onboarding/index.css.map +1 -0
  39. package/dist/onboarding/index.d.cts +1589 -0
  40. package/dist/onboarding/index.d.ts +1589 -0
  41. package/dist/onboarding/index.js +5736 -0
  42. package/dist/onboarding/index.js.map +1 -0
  43. package/dist/styles/styles.css +2593 -0
  44. package/dist/v2-auth/index.cjs +59 -0
  45. package/dist/v2-auth/index.cjs.map +1 -0
  46. package/dist/v2-auth/index.css +3360 -0
  47. package/dist/v2-auth/index.css.map +1 -0
  48. package/dist/v2-auth/index.d.cts +76 -0
  49. package/dist/v2-auth/index.d.ts +76 -0
  50. package/dist/v2-auth/index.js +24 -0
  51. package/dist/v2-auth/index.js.map +1 -0
  52. package/dist/web/index.cjs +7 -7
  53. package/dist/web/index.d.cts +1 -1
  54. package/dist/web/index.d.ts +1 -1
  55. package/dist/web/index.js +1 -1
  56. package/dist/web/server/index.cjs +7 -7
  57. package/dist/web/server/index.d.cts +91 -27
  58. package/dist/web/server/index.d.ts +91 -27
  59. package/dist/web/server/index.js +1 -1
  60. package/package.json +14 -2
  61. package/dist/chunk-25TUCNN6.js.map +0 -1
  62. package/dist/chunk-3NS6X2R4.cjs.map +0 -1
  63. package/dist/chunk-6BUS7RMS.js.map +0 -1
  64. 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"]}
@@ -636,6 +636,24 @@ var CheckIcon = () => /* @__PURE__ */ jsx(
636
636
  children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
637
637
  }
638
638
  );
639
+ var SearchIcon = () => /* @__PURE__ */ jsxs(
640
+ "svg",
641
+ {
642
+ width: "14",
643
+ height: "14",
644
+ viewBox: "0 0 24 24",
645
+ fill: "none",
646
+ stroke: "currentColor",
647
+ strokeWidth: "2",
648
+ strokeLinecap: "round",
649
+ strokeLinejoin: "round",
650
+ "aria-hidden": "true",
651
+ children: [
652
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
653
+ /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
654
+ ]
655
+ }
656
+ );
639
657
  var LocaleDropdown = forwardRef(
640
658
  ({
641
659
  locales,
@@ -645,16 +663,29 @@ var LocaleDropdown = forwardRef(
645
663
  getHref,
646
664
  className,
647
665
  direction = "down",
648
- align = "right"
666
+ align = "right",
667
+ searchable = false,
668
+ searchPlaceholder = "Search languages",
669
+ searchEmptyLabel = "No languages found"
649
670
  }, ref) => {
650
671
  const [isOpen, setIsOpen] = useState(false);
651
672
  const [focusIndex, setFocusIndex] = useState(-1);
673
+ const [query, setQuery] = useState("");
652
674
  const containerRef = useRef(null);
653
675
  const triggerRef = useRef(null);
654
676
  const listRef = useRef(null);
677
+ const searchInputRef = useRef(null);
655
678
  const instanceId = useId();
656
679
  const listboxId = `locale-dropdown-${instanceId}`;
657
680
  const currentLocale = locales.find((l) => l.code === activeLocale) ?? locales[0];
681
+ const filteredLocales = useMemo(() => {
682
+ if (!searchable) return locales;
683
+ const q = query.trim().toLowerCase();
684
+ if (!q) return locales;
685
+ return locales.filter(
686
+ (l) => l.label.toLowerCase().includes(q) || l.code.toLowerCase().includes(q)
687
+ );
688
+ }, [locales, query, searchable]);
658
689
  const handleToggle = useCallback(() => {
659
690
  setIsOpen((prev) => {
660
691
  if (!prev) setFocusIndex(-1);
@@ -664,6 +695,7 @@ var LocaleDropdown = forwardRef(
664
695
  const handleClose = useCallback(() => {
665
696
  setIsOpen(false);
666
697
  setFocusIndex(-1);
698
+ setQuery("");
667
699
  triggerRef.current?.focus();
668
700
  }, []);
669
701
  const handleSelect = useCallback(
@@ -683,6 +715,14 @@ var LocaleDropdown = forwardRef(
683
715
  document.addEventListener("mousedown", handleClickOutside);
684
716
  return () => document.removeEventListener("mousedown", handleClickOutside);
685
717
  }, [isOpen, handleClose]);
718
+ useEffect(() => {
719
+ if (!isOpen || !searchable) return;
720
+ const raf = requestAnimationFrame(() => searchInputRef.current?.focus());
721
+ return () => cancelAnimationFrame(raf);
722
+ }, [isOpen, searchable]);
723
+ useEffect(() => {
724
+ setFocusIndex(-1);
725
+ }, [query]);
686
726
  const handleKeyDown = useCallback(
687
727
  (e) => {
688
728
  if (!isOpen) {
@@ -693,6 +733,7 @@ var LocaleDropdown = forwardRef(
693
733
  }
694
734
  return;
695
735
  }
736
+ const count = filteredLocales.length;
696
737
  switch (e.key) {
697
738
  case "Escape":
698
739
  e.preventDefault();
@@ -700,33 +741,44 @@ var LocaleDropdown = forwardRef(
700
741
  break;
701
742
  case "ArrowDown":
702
743
  e.preventDefault();
703
- setFocusIndex((prev) => (prev + 1) % locales.length);
744
+ if (count > 0) setFocusIndex((prev) => (prev + 1) % count);
704
745
  break;
705
746
  case "ArrowUp":
706
747
  e.preventDefault();
707
- setFocusIndex((prev) => (prev - 1 + locales.length) % locales.length);
748
+ if (count > 0) setFocusIndex((prev) => (prev - 1 + count) % count);
708
749
  break;
709
750
  case "Enter":
710
- case " ":
711
751
  e.preventDefault();
712
- if (focusIndex >= 0 && focusIndex < locales.length) {
713
- handleSelect(locales[focusIndex].code);
752
+ if (focusIndex >= 0 && focusIndex < count) {
753
+ handleSelect(filteredLocales[focusIndex].code);
754
+ }
755
+ break;
756
+ case " ":
757
+ if (!searchable) {
758
+ e.preventDefault();
759
+ if (focusIndex >= 0 && focusIndex < count) {
760
+ handleSelect(filteredLocales[focusIndex].code);
761
+ }
714
762
  }
715
763
  break;
716
764
  case "Home":
717
- e.preventDefault();
718
- setFocusIndex(0);
765
+ if (!searchable) {
766
+ e.preventDefault();
767
+ if (count > 0) setFocusIndex(0);
768
+ }
719
769
  break;
720
770
  case "End":
721
- e.preventDefault();
722
- setFocusIndex(locales.length - 1);
771
+ if (!searchable) {
772
+ e.preventDefault();
773
+ if (count > 0) setFocusIndex(count - 1);
774
+ }
723
775
  break;
724
776
  case "Tab":
725
777
  handleClose();
726
778
  break;
727
779
  }
728
780
  },
729
- [isOpen, focusIndex, locales, handleClose, handleSelect]
781
+ [isOpen, focusIndex, filteredLocales, searchable, handleClose, handleSelect]
730
782
  );
731
783
  useEffect(() => {
732
784
  if (!isOpen || focusIndex < 0 || !listRef.current) return;
@@ -734,6 +786,30 @@ var LocaleDropdown = forwardRef(
734
786
  items[focusIndex]?.scrollIntoView({ block: "nearest" });
735
787
  }, [isOpen, focusIndex]);
736
788
  const activeDescendantId = isOpen && focusIndex >= 0 ? `${listboxId}-option-${focusIndex}` : void 0;
789
+ const renderOption = (locale, index) => {
790
+ const isActive = locale.code === activeLocale;
791
+ const isFocused = index === focusIndex;
792
+ const optionId = `${listboxId}-option-${index}`;
793
+ const optionProps = {
794
+ id: optionId,
795
+ role: "option",
796
+ "aria-selected": isActive,
797
+ className: clsx(
798
+ "ds-locale-dropdown__option",
799
+ isActive && "ds-locale-dropdown__option--active",
800
+ isFocused && "ds-locale-dropdown__option--focused"
801
+ ),
802
+ onClick: () => handleSelect(locale.code),
803
+ ...OptionComponent !== "button" && getHref ? { href: getHref(locale.code) } : { type: "button" }
804
+ };
805
+ return /* @__PURE__ */ jsxs(OptionComponent, { ...optionProps, children: [
806
+ /* @__PURE__ */ jsxs("span", { className: "ds-locale-dropdown__option-content", children: [
807
+ locale.flag && /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-flag", "aria-hidden": "true", children: locale.flag }),
808
+ /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-label", children: locale.label })
809
+ ] }),
810
+ isActive && /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-check", children: /* @__PURE__ */ jsx(CheckIcon, {}) })
811
+ ] }, locale.code);
812
+ };
737
813
  return /* @__PURE__ */ jsx(
738
814
  "div",
739
815
  {
@@ -757,7 +833,7 @@ var LocaleDropdown = forwardRef(
757
833
  "aria-haspopup": "listbox",
758
834
  "aria-expanded": isOpen,
759
835
  "aria-controls": isOpen ? listboxId : void 0,
760
- "aria-activedescendant": activeDescendantId,
836
+ "aria-activedescendant": searchable ? void 0 : activeDescendantId,
761
837
  "aria-label": `Language: ${currentLocale.label}. Change language.`,
762
838
  children: [
763
839
  /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__trigger-icon", children: /* @__PURE__ */ jsx(GlobeIcon, {}) }),
@@ -773,7 +849,49 @@ var LocaleDropdown = forwardRef(
773
849
  ]
774
850
  }
775
851
  ),
776
- /* @__PURE__ */ jsx(
852
+ searchable ? /* @__PURE__ */ jsxs(
853
+ "div",
854
+ {
855
+ className: "ds-locale-dropdown__panel ds-locale-dropdown__panel--searchable",
856
+ "data-state": isOpen ? "open" : "closed",
857
+ children: [
858
+ /* @__PURE__ */ jsxs("div", { className: "ds-locale-dropdown__search", children: [
859
+ /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
860
+ /* @__PURE__ */ jsx(
861
+ "input",
862
+ {
863
+ ref: searchInputRef,
864
+ type: "text",
865
+ className: "ds-locale-dropdown__search-input",
866
+ placeholder: searchPlaceholder,
867
+ value: query,
868
+ onChange: (e) => setQuery(e.target.value),
869
+ onKeyDown: handleKeyDown,
870
+ role: "combobox",
871
+ "aria-expanded": isOpen,
872
+ "aria-controls": listboxId,
873
+ "aria-activedescendant": activeDescendantId,
874
+ "aria-autocomplete": "list",
875
+ "aria-label": searchPlaceholder,
876
+ autoComplete: "off",
877
+ spellCheck: false
878
+ }
879
+ )
880
+ ] }),
881
+ /* @__PURE__ */ jsx(
882
+ "div",
883
+ {
884
+ ref: listRef,
885
+ id: listboxId,
886
+ className: "ds-locale-dropdown__list",
887
+ role: "listbox",
888
+ "aria-label": "Select language",
889
+ children: filteredLocales.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ds-locale-dropdown__empty", role: "status", children: searchEmptyLabel }) : filteredLocales.map(renderOption)
890
+ }
891
+ )
892
+ ]
893
+ }
894
+ ) : /* @__PURE__ */ jsx(
777
895
  "div",
778
896
  {
779
897
  ref: listRef,
@@ -783,30 +901,7 @@ var LocaleDropdown = forwardRef(
783
901
  "aria-label": "Select language",
784
902
  "data-state": isOpen ? "open" : "closed",
785
903
  onKeyDown: handleKeyDown,
786
- children: locales.map((locale, index) => {
787
- const isActive = locale.code === activeLocale;
788
- const isFocused = index === focusIndex;
789
- const optionId = `${listboxId}-option-${index}`;
790
- const optionProps = {
791
- id: optionId,
792
- role: "option",
793
- "aria-selected": isActive,
794
- className: clsx(
795
- "ds-locale-dropdown__option",
796
- isActive && "ds-locale-dropdown__option--active",
797
- isFocused && "ds-locale-dropdown__option--focused"
798
- ),
799
- onClick: () => handleSelect(locale.code),
800
- ...OptionComponent !== "button" && getHref ? { href: getHref(locale.code) } : { type: "button" }
801
- };
802
- return /* @__PURE__ */ jsxs(OptionComponent, { ...optionProps, children: [
803
- /* @__PURE__ */ jsxs("span", { className: "ds-locale-dropdown__option-content", children: [
804
- locale.flag && /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-flag", "aria-hidden": "true", children: locale.flag }),
805
- /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-label", children: locale.label })
806
- ] }),
807
- isActive && /* @__PURE__ */ jsx("span", { className: "ds-locale-dropdown__option-check", children: /* @__PURE__ */ jsx(CheckIcon, {}) })
808
- ] }, locale.code);
809
- })
904
+ children: filteredLocales.map(renderOption)
810
905
  }
811
906
  )
812
907
  ] })
@@ -838,4 +933,4 @@ var MDXOutboundLink = ({
838
933
 
839
934
  export { LocaleDropdown, MDXOutboundLink, NavbarV2, NavbarV2Actions, NavbarV2Brand, NavbarV2Tab, NavbarV2TabMenu, NavbarV2Tabs, SiteFooter };
840
935
  //# sourceMappingURL=out.js.map
841
- //# sourceMappingURL=chunk-6BUS7RMS.js.map
936
+ //# sourceMappingURL=chunk-Q3YVBUKQ.js.map