@flamingo-stack/openframe-frontend-core 0.0.285 → 0.0.286

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 (211) hide show
  1. package/dist/chunk-3MCHAFHB.js +89 -0
  2. package/dist/chunk-3MCHAFHB.js.map +1 -0
  3. package/dist/{chunk-IMNGGLKL.js → chunk-3ZXUQQL4.js} +2 -2
  4. package/dist/{chunk-44ZFLMYC.cjs → chunk-4WACBTZU.cjs} +30 -30
  5. package/dist/{chunk-44ZFLMYC.cjs.map → chunk-4WACBTZU.cjs.map} +1 -1
  6. package/dist/{chunk-23UOJUDF.js → chunk-5BTZOVDQ.js} +4 -4
  7. package/dist/chunk-5KD3S25X.cjs +312 -0
  8. package/dist/chunk-5KD3S25X.cjs.map +1 -0
  9. package/dist/chunk-6N26CURS.cjs +2417 -0
  10. package/dist/chunk-6N26CURS.cjs.map +1 -0
  11. package/dist/{chunk-XX7P4IL2.js → chunk-7NM7DEUK.js} +4 -143
  12. package/dist/chunk-7NM7DEUK.js.map +1 -0
  13. package/dist/chunk-BBZ7AX5H.cjs +290 -0
  14. package/dist/chunk-BBZ7AX5H.cjs.map +1 -0
  15. package/dist/chunk-C6SCWXDP.js +290 -0
  16. package/dist/chunk-C6SCWXDP.js.map +1 -0
  17. package/dist/{chunk-GVSTRN36.cjs → chunk-D3LEFMOA.cjs} +3 -3
  18. package/dist/{chunk-GVSTRN36.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
  19. package/dist/{chunk-P45Q4QCU.cjs → chunk-D5YY5U6J.cjs} +19 -15
  20. package/dist/chunk-D5YY5U6J.cjs.map +1 -0
  21. package/dist/chunk-DDAT4RKX.js +2417 -0
  22. package/dist/chunk-DDAT4RKX.js.map +1 -0
  23. package/dist/{chunk-PVXCVPJD.cjs → chunk-EI4WALN2.cjs} +67 -1626
  24. package/dist/chunk-EI4WALN2.cjs.map +1 -0
  25. package/dist/{chunk-ZIODAAFY.cjs → chunk-EL5GCMPU.cjs} +20 -159
  26. package/dist/chunk-EL5GCMPU.cjs.map +1 -0
  27. package/dist/{chunk-VQW7CEGS.js → chunk-FSBDVT6R.js} +1831 -3672
  28. package/dist/chunk-FSBDVT6R.js.map +1 -0
  29. package/dist/{chunk-DPKUSBQH.cjs → chunk-GDF2R2ER.cjs} +1073 -2914
  30. package/dist/chunk-GDF2R2ER.cjs.map +1 -0
  31. package/dist/chunk-HLQW7MWJ.cjs +1594 -0
  32. package/dist/chunk-HLQW7MWJ.cjs.map +1 -0
  33. package/dist/chunk-I6ZPGKZ2.cjs +151 -0
  34. package/dist/chunk-I6ZPGKZ2.cjs.map +1 -0
  35. package/dist/{chunk-J66U3LPZ.cjs → chunk-KBKZYJRI.cjs} +33 -332
  36. package/dist/chunk-KBKZYJRI.cjs.map +1 -0
  37. package/dist/{chunk-USPATRSC.js → chunk-N6ZM5PYZ.js} +45 -344
  38. package/dist/chunk-N6ZM5PYZ.js.map +1 -0
  39. package/dist/{chunk-RGALV4UX.js → chunk-POKKCWKF.js} +3 -82
  40. package/dist/chunk-POKKCWKF.js.map +1 -0
  41. package/dist/{chunk-XDI5JSWB.cjs → chunk-QCKN37OP.cjs} +45 -41
  42. package/dist/chunk-QCKN37OP.cjs.map +1 -0
  43. package/dist/{chunk-KNCDUWY2.js → chunk-QF2X6PTD.js} +10 -6
  44. package/dist/{chunk-KNCDUWY2.js.map → chunk-QF2X6PTD.js.map} +1 -1
  45. package/dist/{chunk-Z3V2SGQZ.js → chunk-R2KT5GDD.js} +324 -1883
  46. package/dist/chunk-R2KT5GDD.js.map +1 -0
  47. package/dist/{chunk-RJUGNL2A.cjs → chunk-RQ6RTBKF.cjs} +16 -12
  48. package/dist/chunk-RQ6RTBKF.cjs.map +1 -0
  49. package/dist/chunk-TFSYSWPS.cjs +89 -0
  50. package/dist/chunk-TFSYSWPS.cjs.map +1 -0
  51. package/dist/chunk-TYZEMPPH.js +312 -0
  52. package/dist/chunk-TYZEMPPH.js.map +1 -0
  53. package/dist/{chunk-O5N57CRV.js → chunk-VCQ3CTYK.js} +12 -8
  54. package/dist/{chunk-O5N57CRV.js.map → chunk-VCQ3CTYK.js.map} +1 -1
  55. package/dist/chunk-VRSXJ5QJ.js +1599 -0
  56. package/dist/chunk-VRSXJ5QJ.js.map +1 -0
  57. package/dist/{chunk-53CFL7PH.js → chunk-XQL4WDML.js} +11 -7
  58. package/dist/{chunk-53CFL7PH.js.map → chunk-XQL4WDML.js.map} +1 -1
  59. package/dist/chunk-Y4JNA4W6.cjs +1599 -0
  60. package/dist/chunk-Y4JNA4W6.cjs.map +1 -0
  61. package/dist/{chunk-ZSUNQJRO.cjs → chunk-YETA25JW.cjs} +2 -81
  62. package/dist/chunk-YETA25JW.cjs.map +1 -0
  63. package/dist/chunk-YVB3VDIQ.js +1594 -0
  64. package/dist/chunk-YVB3VDIQ.js.map +1 -0
  65. package/dist/chunk-ZLN6SM2U.js +151 -0
  66. package/dist/chunk-ZLN6SM2U.js.map +1 -0
  67. package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -1
  68. package/dist/components/chat/index.cjs +12 -6
  69. package/dist/components/chat/index.cjs.map +1 -1
  70. package/dist/components/chat/index.js +45 -39
  71. package/dist/components/contact/index.cjs +11 -7
  72. package/dist/components/contact/index.cjs.map +1 -1
  73. package/dist/components/contact/index.js +10 -6
  74. package/dist/components/docs/doc-navigation-context.d.ts +39 -0
  75. package/dist/components/docs/doc-navigation-context.d.ts.map +1 -0
  76. package/dist/components/docs/doc-viewer.d.ts +66 -0
  77. package/dist/components/docs/doc-viewer.d.ts.map +1 -0
  78. package/dist/components/docs/index.cjs +34 -0
  79. package/dist/components/docs/index.cjs.map +1 -0
  80. package/dist/components/docs/index.d.ts +8 -0
  81. package/dist/components/docs/index.d.ts.map +1 -0
  82. package/dist/components/docs/index.js +34 -0
  83. package/dist/components/docs/index.js.map +1 -0
  84. package/dist/components/docs/use-document-tree.d.ts +41 -0
  85. package/dist/components/docs/use-document-tree.d.ts.map +1 -0
  86. package/dist/components/docs/use-scroll-spy.d.ts +16 -0
  87. package/dist/components/docs/use-scroll-spy.d.ts.map +1 -0
  88. package/dist/components/embeds/embed-iframe.d.ts +29 -0
  89. package/dist/components/embeds/embed-iframe.d.ts.map +1 -0
  90. package/dist/components/embeds/figma-embed.d.ts +27 -0
  91. package/dist/components/embeds/figma-embed.d.ts.map +1 -0
  92. package/dist/components/embeds/google-sheets-viewer.d.ts +8 -0
  93. package/dist/components/embeds/google-sheets-viewer.d.ts.map +1 -0
  94. package/dist/components/embeds/index.cjs +37 -0
  95. package/dist/components/embeds/index.cjs.map +1 -0
  96. package/dist/components/embeds/index.d.ts +9 -0
  97. package/dist/components/embeds/index.d.ts.map +1 -0
  98. package/dist/components/embeds/index.js +37 -0
  99. package/dist/components/embeds/index.js.map +1 -0
  100. package/dist/components/embeds/pdf-viewer.d.ts +10 -0
  101. package/dist/components/embeds/pdf-viewer.d.ts.map +1 -0
  102. package/dist/components/faq/index.cjs +12 -8
  103. package/dist/components/faq/index.cjs.map +1 -1
  104. package/dist/components/faq/index.js +11 -7
  105. package/dist/components/features/index.cjs +10 -6
  106. package/dist/components/features/index.cjs.map +1 -1
  107. package/dist/components/features/index.js +9 -5
  108. package/dist/components/index.cjs +247 -1120
  109. package/dist/components/index.cjs.map +1 -1
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/index.js +645 -1518
  113. package/dist/components/index.js.map +1 -1
  114. package/dist/components/navigation/index.cjs +15 -6
  115. package/dist/components/navigation/index.cjs.map +1 -1
  116. package/dist/components/navigation/index.d.ts +2 -0
  117. package/dist/components/navigation/index.d.ts.map +1 -1
  118. package/dist/components/navigation/index.js +15 -6
  119. package/dist/components/navigation/multi-level-navigation.d.ts +28 -0
  120. package/dist/components/navigation/multi-level-navigation.d.ts.map +1 -0
  121. package/dist/components/onboarding-guides/index.cjs +34 -29
  122. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  123. package/dist/components/onboarding-guides/index.js +13 -8
  124. package/dist/components/onboarding-guides/index.js.map +1 -1
  125. package/dist/components/related-content/index.cjs +12 -8
  126. package/dist/components/related-content/index.cjs.map +1 -1
  127. package/dist/components/related-content/index.js +11 -7
  128. package/dist/components/tickets/index.cjs +75 -70
  129. package/dist/components/tickets/index.cjs.map +1 -1
  130. package/dist/components/tickets/index.js +16 -11
  131. package/dist/components/tickets/index.js.map +1 -1
  132. package/dist/components/ui/index.cjs +12 -6
  133. package/dist/components/ui/index.cjs.map +1 -1
  134. package/dist/components/ui/index.js +65 -59
  135. package/dist/embed-shims/index.cjs +3 -3
  136. package/dist/embed-shims/index.cjs.map +1 -1
  137. package/dist/embed-shims/index.js +8 -8
  138. package/dist/hooks/index.cjs +6 -3
  139. package/dist/hooks/index.cjs.map +1 -1
  140. package/dist/hooks/index.js +7 -4
  141. package/dist/index.cjs +62 -6
  142. package/dist/index.cjs.map +1 -1
  143. package/dist/index.js +257 -201
  144. package/dist/types/doc-source.cjs +4 -0
  145. package/dist/types/doc-source.cjs.map +1 -0
  146. package/dist/types/doc-source.d.ts +86 -0
  147. package/dist/types/doc-source.d.ts.map +1 -0
  148. package/dist/types/doc-source.js +3 -0
  149. package/dist/types/doc-source.js.map +1 -0
  150. package/dist/types/index.d.ts +1 -0
  151. package/dist/types/index.d.ts.map +1 -1
  152. package/dist/utils/doc-path-utils.d.ts +24 -0
  153. package/dist/utils/doc-path-utils.d.ts.map +1 -0
  154. package/dist/utils/doc-tree-nav.d.ts +35 -0
  155. package/dist/utils/doc-tree-nav.d.ts.map +1 -0
  156. package/dist/utils/embed-url-converters.d.ts +16 -0
  157. package/dist/utils/embed-url-converters.d.ts.map +1 -0
  158. package/dist/utils/format.d.ts +9 -0
  159. package/dist/utils/format.d.ts.map +1 -1
  160. package/dist/utils/index.cjs +297 -0
  161. package/dist/utils/index.cjs.map +1 -1
  162. package/dist/utils/index.d.ts +7 -1
  163. package/dist/utils/index.d.ts.map +1 -1
  164. package/dist/utils/index.js +276 -1
  165. package/dist/utils/index.js.map +1 -1
  166. package/dist/utils/markdown-section-extractor.d.ts +23 -0
  167. package/dist/utils/markdown-section-extractor.d.ts.map +1 -0
  168. package/dist/utils/markdown-to-plain.d.ts +32 -0
  169. package/dist/utils/markdown-to-plain.d.ts.map +1 -0
  170. package/dist/utils/tree-builder.d.ts +30 -0
  171. package/dist/utils/tree-builder.d.ts.map +1 -0
  172. package/package.json +19 -1
  173. package/src/components/chat/chat-attachment-bar.tsx +6 -11
  174. package/src/components/docs/doc-navigation-context.tsx +123 -0
  175. package/src/components/docs/doc-viewer.tsx +345 -0
  176. package/src/components/docs/index.ts +12 -0
  177. package/src/components/docs/use-document-tree.ts +427 -0
  178. package/src/components/docs/use-scroll-spy.ts +93 -0
  179. package/src/components/embeds/embed-iframe.tsx +103 -0
  180. package/src/components/embeds/figma-embed.tsx +147 -0
  181. package/src/components/embeds/google-sheets-viewer.tsx +54 -0
  182. package/src/components/embeds/index.ts +11 -0
  183. package/src/components/embeds/pdf-viewer.tsx +64 -0
  184. package/src/components/index.ts +2 -0
  185. package/src/components/navigation/index.ts +5 -0
  186. package/src/components/navigation/multi-level-navigation.tsx +358 -0
  187. package/src/types/doc-source.ts +88 -0
  188. package/src/types/index.ts +4 -0
  189. package/src/utils/doc-path-utils.ts +47 -0
  190. package/src/utils/doc-tree-nav.ts +111 -0
  191. package/src/utils/embed-url-converters.ts +77 -0
  192. package/src/utils/format.ts +16 -0
  193. package/src/utils/index.ts +11 -0
  194. package/src/utils/markdown-section-extractor.ts +106 -0
  195. package/src/utils/markdown-to-plain.ts +69 -0
  196. package/src/utils/tree-builder.ts +114 -0
  197. package/dist/chunk-DPKUSBQH.cjs.map +0 -1
  198. package/dist/chunk-J66U3LPZ.cjs.map +0 -1
  199. package/dist/chunk-P45Q4QCU.cjs.map +0 -1
  200. package/dist/chunk-PVXCVPJD.cjs.map +0 -1
  201. package/dist/chunk-RGALV4UX.js.map +0 -1
  202. package/dist/chunk-RJUGNL2A.cjs.map +0 -1
  203. package/dist/chunk-USPATRSC.js.map +0 -1
  204. package/dist/chunk-VQW7CEGS.js.map +0 -1
  205. package/dist/chunk-XDI5JSWB.cjs.map +0 -1
  206. package/dist/chunk-XX7P4IL2.js.map +0 -1
  207. package/dist/chunk-Z3V2SGQZ.js.map +0 -1
  208. package/dist/chunk-ZIODAAFY.cjs.map +0 -1
  209. package/dist/chunk-ZSUNQJRO.cjs.map +0 -1
  210. /package/dist/{chunk-IMNGGLKL.js.map → chunk-3ZXUQQL4.js.map} +0 -0
  211. /package/dist/{chunk-23UOJUDF.js.map → chunk-5BTZOVDQ.js.map} +0 -0
@@ -1,18 +1,22 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkGVSTRN36cjs = require('./chunk-GVSTRN36.cjs');
4
+ var _chunkD3LEFMOAcjs = require('./chunk-D3LEFMOA.cjs');
5
5
 
6
6
 
7
+ var _chunkGDF2R2ERcjs = require('./chunk-GDF2R2ER.cjs');
7
8
 
8
9
 
9
10
 
10
- var _chunkDPKUSBQHcjs = require('./chunk-DPKUSBQH.cjs');
11
11
 
12
+ var _chunk6N26CURScjs = require('./chunk-6N26CURS.cjs');
12
13
 
13
14
 
14
15
 
15
- var _chunkPVXCVPJDcjs = require('./chunk-PVXCVPJD.cjs');
16
+ var _chunkEI4WALN2cjs = require('./chunk-EI4WALN2.cjs');
17
+
18
+
19
+ var _chunkY4JNA4W6cjs = require('./chunk-Y4JNA4W6.cjs');
16
20
 
17
21
 
18
22
 
@@ -82,7 +86,7 @@ function FaqAccordion({ items, defaultOpenIds = [] }) {
82
86
  children: [
83
87
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "min-w-0 pr-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { children: item.question }) }),
84
88
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
85
- _chunkDPKUSBQHcjs.ChevronButton,
89
+ _chunkGDF2R2ERcjs.ChevronButton,
86
90
  {
87
91
  "aria-label": isOpen ? "Collapse question" : "Expand question",
88
92
  size: "md",
@@ -142,7 +146,7 @@ function buildFaqJsonLdFromFaqs(faqs, opts = {}) {
142
146
 
143
147
  var DEFAULT_HEADING_TEXT = "Frequently Asked Questions";
144
148
  function buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl = "") {
145
- return _chunkDPKUSBQHcjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
149
+ return _chunk6N26CURScjs.buildSuggestionUrl.call(void 0, "/api/faqs", { apiBaseUrl, entityType, entityId, count: minResults });
146
150
  }
147
151
  function sectionSlug(section) {
148
152
  return "faq-" + section.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -212,7 +216,7 @@ function GroupedFaqList({
212
216
  (e, slug) => {
213
217
  e.preventDefault();
214
218
  setActiveSlug(slug);
215
- _chunkDPKUSBQHcjs.scrollElementIntoView.call(void 0, document.getElementById(slug), {
219
+ _chunk6N26CURScjs.scrollElementIntoView.call(void 0, document.getElementById(slug), {
216
220
  headerOffset: FAQ_NAV_HEADER_OFFSET
217
221
  });
218
222
  if (typeof history !== "undefined") history.replaceState(null, "", `#${slug}`);
@@ -243,7 +247,7 @@ function GroupedFaqList({
243
247
  id: _nullishCoalesce(group.slug, () => ( void 0)),
244
248
  className: "scroll-mt-24 space-y-4",
245
249
  children: [
246
- group.section && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CategoryHeading, { className: _chunkPVXCVPJDcjs.SECTION_HEADING_CLASS, children: group.section }),
250
+ group.section && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CategoryHeading, { className: _chunkY4JNA4W6cjs.SECTION_HEADING_CLASS, children: group.section }),
247
251
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaqAccordion, { items: group.items })
248
252
  ]
249
253
  },
@@ -276,10 +280,10 @@ function FaqSection({
276
280
  () => initialFaqs ? { faqs: initialFaqs } : void 0,
277
281
  [initialFaqs]
278
282
  );
279
- const { data, isLoading, error } = _chunkGVSTRN36cjs.useSelfFetch.call(void 0, url, { initialData });
283
+ const { data, isLoading, error } = _chunkD3LEFMOAcjs.useSelfFetch.call(void 0, url, { initialData });
280
284
  const faqs = _nullishCoalesce(_optionalChain([data, 'optionalAccess', _7 => _7.faqs]), () => ( []));
281
285
  const groups = _react.useMemo.call(void 0, () => faqs.length > 0 ? groupFaqsBySection(faqs) : [], [faqs]);
282
- const headingNode = heading === void 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: _chunkPVXCVPJDcjs.SECTION_HEADING_CLASS, children: DEFAULT_HEADING_TEXT }) : heading;
286
+ const headingNode = heading === void 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: _chunkY4JNA4W6cjs.SECTION_HEADING_CLASS, children: DEFAULT_HEADING_TEXT }) : heading;
283
287
  if (error) return null;
284
288
  if (!isLoading && faqs.length === 0) return null;
285
289
  if (isLoading && faqs.length === 0) {
@@ -295,7 +299,7 @@ function FaqSection({
295
299
  "script",
296
300
  {
297
301
  type: "application/ld+json",
298
- dangerouslySetInnerHTML: { __html: _chunkDPKUSBQHcjs.serializeJsonLd.call(void 0, schema) }
302
+ dangerouslySetInnerHTML: { __html: _chunk6N26CURScjs.serializeJsonLd.call(void 0, schema) }
299
303
  }
300
304
  )
301
305
  ] });
@@ -318,7 +322,7 @@ function FaqDocumentPage({
318
322
  label: _nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _8 => _8.label]), () => ( "Back to home")),
319
323
  onClick: () => router.push(_nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _9 => _9.href]), () => ( "/")))
320
324
  };
321
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPVXCVPJDcjs.PageShell, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkPVXCVPJDcjs.PageLayout, { title, subtitle, backButton: backCfg, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
325
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEI4WALN2cjs.PageShell, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEI4WALN2cjs.PageLayout, { title, subtitle, backButton: backCfg, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
322
326
  FaqSection,
323
327
  {
324
328
  heading: null,
@@ -335,4 +339,4 @@ function FaqDocumentPage({
335
339
 
336
340
 
337
341
  exports.FaqAccordion = FaqAccordion; exports.FaqSection = FaqSection; exports.FaqDocumentPage = FaqDocumentPage;
338
- //# sourceMappingURL=chunk-RJUGNL2A.cjs.map
342
+ //# sourceMappingURL=chunk-RQ6RTBKF.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-RQ6RTBKF.cjs","../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"names":["jsx","jsxs"],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;AC1BA,8BAAiE;AD4BjE;AACA;AE7BA;AAEA,uCAAA,CAAA;AA6DY,+CAAA;AA/CZ,IAAM,kBAAA,EAAoB,CAAC,MAAA,EAAA,GAAoB;AAC7C,EAAA,MAAM,IAAA,EAAM,2BAAA,IAAkC,CAAA;AAC9C,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,EAAA,EAAI,6BAAA,KAAsB,CAAA;AAExD,EAAA,MAAM,QAAA,EAAU,gCAAA,CAAY,EAAA,GAAM;AAChC,IAAA,GAAA,CAAI,GAAA,CAAI,OAAA,EAAS;AACf,MAAA,MAAM,OAAA,EAAS,GAAA,CAAI,OAAA,CAAQ,YAAA;AAC3B,MAAA,YAAA,CAAa,CAAA,EAAA;AACf,IAAA;AACG,EAAA;AAGW,EAAA;AACF,IAAA;AACF,MAAA;AACH,IAAA;AACQ,MAAA;AACf,IAAA;AACU,EAAA;AAEE,EAAA;AAChB;AAEgB;AACE,EAAA;AAEA,EAAA;AACH,IAAA;AACI,MAAA;AACA,MAAA;AACC,MAAA;AACP,MAAA;AACR,IAAA;AACH,EAAA;AAGE,EAAA;AAEmB,IAAA;AACF,IAAA;AAGX,IAAA;AAAC,MAAA;AAAA,MAAA;AAEY,QAAA;AAGX,QAAA;AAAA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACM,cAAA;AACL,cAAA;AACA,cAAA;AACA,cAAA;AACM,gBAAA;AACA,kBAAA;AACF,kBAAA;AACF,gBAAA;AACF,cAAA;AACA,cAAA;AACA,cAAA;AAEA,cAAA;AAAA,gCAAA;AAKA,gCAAA;AACG,kBAAA;AAAA,kBAAA;AACC,oBAAA;AACA,oBAAA;AACA,oBAAA;AACA,oBAAA;AACA,oBAAA;AAAY,kBAAA;AAEhB,gBAAA;AAAA,cAAA;AAAA,YAAA;AACF,UAAA;AAEA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACC,cAAA;AACA,cAAA;AAEA,cAAA;AAEA,YAAA;AACF,UAAA;AAAA,QAAA;AAAA,MAAA;AAxCU,MAAA;AAyCZ,IAAA;AAGN,EAAA;AAEJ;AFOoB;AACA;AC1GpB;AD4GoB;AACA;AGpGC;AACf;AAGU;AACP,EAAA;AACO,IAAA;AACH,IAAA;AACE,IAAA;AACE,IAAA;AACI,IAAA;AACnB,EAAA;AACF;AAEgB;AACP,EAAA;AACF,IAAA;AACS,IAAA;AACD,MAAA;AACC,MAAA;AACV,MAAA;AACW,QAAA;AACC,QAAA;AACZ,MAAA;AACA,IAAA;AACJ,EAAA;AACF;AHmGoB;AACA;ACsDN;AAvJR;AAKG;AAMA,EAAA;AACT;AAKS;AAGL,EAAA;AAKJ;AAcS;AACkB,EAAA;AACV,EAAA;AACX,EAAA;AACc,EAAA;AACQ,IAAA;AACX,IAAA;AACF,IAAA;AACJ,MAAA;AACS,MAAA;AACd,MAAA;AACF,IAAA;AACY,IAAA;AACA,IAAA;AACA,MAAA;AACC,MAAA;AACA,MAAA;AACb,IAAA;AACY,IAAA;AACd,EAAA;AACe,EAAA;AACI,EAAA;AACZ,EAAA;AACT;AAIM;AAYG;AACP,EAAA;AACA,EAAA;AASC;AACK,EAAA;AACY,EAAA;AACX,EAAA;AAGS,EAAA;AAMA,EAAA;AACA,IAAA;AACD,IAAA;AACP,IAAA;AACS,MAAA;AACA,QAAA;AACH,UAAA;AACI,UAAA;AACA,UAAA;AACZ,QAAA;AACI,QAAA;AACA,QAAA;AACQ,QAAA;AACA,UAAA;AACR,YAAA;AACA,YAAA;AACF,UAAA;AACF,QAAA;AACY,QAAA;AACd,MAAA;AACc,MAAA;AAChB,IAAA;AACW,IAAA;AACE,MAAA;AACH,MAAA;AACV,IAAA;AACa,IAAA;AAGH,EAAA;AAEN,EAAA;AACsD,IAAA;AACtD,MAAA;AACY,MAAA;AACd,MAAA;AACE,QAAA;AACD,MAAA;AACU,MAAA;AACb,IAAA;AACC,IAAA;AACH,EAAA;AAGE,EAAA;AACa,IAAA;AAGC,MAAA;AAEJ,MAAA;AAAC,QAAA;AAAA,QAAA;AAEW,UAAA;AACV,UAAA;AACU,UAAA;AACV,UAAA;AACE,YAAA;AACA,YAAA;AAGF,UAAA;AAEC,UAAA;AAAM,QAAA;AAXI,QAAA;AAYb,MAAA;AAGN,IAAA;AAEFA,oBAAAA;AAEK,MAAA;AAAA,MAAA;AAEW,QAAA;AACA,QAAA;AAET,QAAA;AAAM,UAAA;AAGP,0BAAA;AAAkC,QAAA;AAAA,MAAA;AAPf,uBAAA;AAUzB,IAAA;AACF,EAAA;AAEJ;AAES;AAEL,EAAA;AACEA,oBAAAA;AACAA,oBAAAA;AAGM,sBAAA;AACA,sBAAA;AAGN,IAAA;AACF,EAAA;AAEJ;AAuB2B;AACzB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACa,EAAA;AACb,EAAA;AACA,EAAA;AACA,EAAA;AACa,EAAA;AACK;AACN,EAAA;AAGN,EAAA;AACG,IAAA;AACK,IAAA;AACd,EAAA;AACc,EAAA;AAED,EAAA;AAEE,EAAA;AAKT,EAAA;AAKY,EAAA;AACA,EAAA;AACD,EAAA;AAEb,IAAA;AAIJ,EAAA;AAEe,EAAA;AAGb,EAAA;AACEC,oBAAAA;AACG,MAAA;AACD,sBAAA;AACF,IAAA;AAEE,IAAA;AAAC,MAAA;AAAA,MAAA;AACM,QAAA;AAIL,QAAA;AAA2D,MAAA;AAC7D,IAAA;AAEJ,EAAA;AAEJ;AD1BoB;AACA;AIrSpB;AA4CQD;AAxBQ;AACN,EAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACuB;AACR,EAAA;AAKb,EAAA;AAGa,IAAA;AACQ,IAAA;AACjB,EAAA;AAGJ,EAAA;AAEK,IAAA;AAAA,IAAA;AACU,MAAA;AACT,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAA,IAAA;AAGN,EAAA;AAEJ;AJ2QoB;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-RQ6RTBKF.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { scrollElementIntoView } from '../../utils/scroll-into-view'\nimport { cn } from '../../utils/cn'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node above the grouped list. `undefined` → default\n * `<h2>`\"Frequently Asked Questions\". `null` → no heading (the host page\n * owns the `<h1>`, as the standalone /faqs surface does). A React node lets a\n * platform drill its own <PageHeading> without the lib referencing platform\n * state. Also drives category nesting so the document outline stays correct:\n * `null` → categories render `<h2>` (directly under the page `<h1>`);\n * otherwise categories render `<h3>` beneath this heading.\n */\n heading?: React.ReactNode | null\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\n/** Stable, URL-safe anchor id for a category. Prefixed so it can't collide\n * with other in-page ids, and so a bare numeric/blank section still yields a\n * valid id. */\nfunction sectionSlug(section: string): string {\n return (\n 'faq-' +\n section\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n\ninterface FaqGroup {\n /** null → the uncategorized bucket: no heading, no jump pill, rendered last. */\n section: string | null\n slug: string | null\n items: FaqItem[]\n}\n\n/** Group FAQs by `faq.section`, preserving the server's first-seen\n * (display_order) order for BOTH the section order and the rows within each\n * section. The uncategorized bucket (blank/missing section) sinks to the end\n * since it renders without a heading. Items carry NO badge here — the `<h2>`\n * IS the category, so a per-row chip would be redundant. */\nfunction groupFaqsBySection(faqs: Faq[]): FaqGroup[] {\n const order: string[] = []\n const byName = new Map<string, FaqGroup>()\n let uncategorized: FaqGroup | null = null\n for (const faq of faqs) {\n const item: FaqItem = { id: faq.id, question: faq.question, answer: faq.answer }\n const name = faq.section?.trim()\n if (!name) {\n if (!uncategorized) uncategorized = { section: null, slug: null, items: [] }\n uncategorized.items.push(item)\n continue\n }\n let group = byName.get(name)\n if (!group) {\n group = { section: name, slug: sectionSlug(name), items: [] }\n byName.set(name, group)\n order.push(name)\n }\n group.items.push(item)\n }\n const groups = order.map((name) => byName.get(name)!)\n if (uncategorized) groups.push(uncategorized)\n return groups\n}\n\n/** The standard hub sticky-header height — same offset `useNavLink`'s hash\n * scroll uses, so a category jump lands below the header, not under it. */\nconst FAQ_NAV_HEADER_OFFSET = 96\n\n/**\n * Grouped FAQ layout: a category jump-nav above stacked `<h2>` category\n * sections (each its own accordion). Isolated into its own component so the\n * scroll-spy hooks only mount in grouped mode — `FaqSection`'s own hooks stay\n * unconditional.\n *\n * The pills are real `<a href=\"#slug\">` anchors (crawlable in-page links,\n * deep-linkable, work without JS); the click handler upgrades the jump to the\n * cancellation-proof `scrollElementIntoView` tween and syncs the URL hash.\n */\nfunction GroupedFaqList({\n groups,\n categoryHeadingAs,\n}: {\n groups: FaqGroup[]\n /** Heading tag for each category, so the document outline nests correctly\n * under whatever owns the heading above this block: `h2` on the standalone\n * page (the page owns the `<h1>`), `h3` beneath an embed's `<h2>` title.\n * The VISUAL is `SECTION_HEADING_CLASS` either way, so categories look\n * identical on every surface. */\n categoryHeadingAs: 'h2' | 'h3'\n}) {\n const CategoryHeading = categoryHeadingAs\n const navGroups = useMemo(() => groups.filter((g) => g.slug), [groups])\n const [activeSlug, setActiveSlug] = useState<string | null>(navGroups[0]?.slug ?? null)\n // Identity-stable key for the section set so the observer re-binds only when\n // the categories actually change (not on every parent re-render).\n const slugKey = navGroups.map((g) => g.slug).join('|')\n\n // Scroll-spy: mark the pill for the category currently at the top of the\n // viewport. rootMargin drops the trigger line just below the sticky header\n // and ignores the bottom ~55% so \"active\" is the section being read, not the\n // next one peeking in.\n useEffect(() => {\n if (navGroups.length < 2) return\n const tops = new Map<string, number>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id\n if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top)\n else tops.delete(id)\n }\n let bestId: string | null = null\n let bestTop = Number.POSITIVE_INFINITY\n for (const [id, top] of tops) {\n if (top < bestTop) {\n bestTop = top\n bestId = id\n }\n }\n if (bestId) setActiveSlug(bestId)\n },\n { rootMargin: `-${FAQ_NAV_HEADER_OFFSET}px 0px -55% 0px`, threshold: 0 },\n )\n for (const group of navGroups) {\n const el = group.slug ? document.getElementById(group.slug) : null\n if (el) observer.observe(el)\n }\n return () => observer.disconnect()\n // slugKey encodes the section set; re-observe only when it changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slugKey])\n\n const handleJump = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>, slug: string) => {\n e.preventDefault()\n setActiveSlug(slug)\n scrollElementIntoView(document.getElementById(slug), {\n headerOffset: FAQ_NAV_HEADER_OFFSET,\n })\n if (typeof history !== 'undefined') history.replaceState(null, '', `#${slug}`)\n },\n [],\n )\n\n return (\n <div className=\"space-y-8\">\n {navGroups.length > 1 && (\n <nav aria-label=\"FAQ categories\" className=\"flex flex-wrap gap-2\">\n {navGroups.map((group) => {\n const isActive = group.slug === activeSlug\n return (\n <a\n key={group.slug}\n href={`#${group.slug}`}\n aria-current={isActive ? 'true' : undefined}\n onClick={(e) => handleJump(e, group.slug as string)}\n className={cn(\n \"rounded-full border px-4 py-2 text-sm font-medium font-['DM_Sans'] transition-colors\",\n isActive\n ? 'border-ods-text-primary bg-ods-card text-ods-text-primary'\n : 'border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {group.section}\n </a>\n )\n })}\n </nav>\n )}\n <div className=\"space-y-10\">\n {groups.map((group) => (\n <section\n key={group.slug ?? 'faq-uncategorized'}\n id={group.slug ?? undefined}\n className=\"scroll-mt-24 space-y-4\"\n >\n {group.section && (\n <CategoryHeading className={SECTION_HEADING_CLASS}>{group.section}</CategoryHeading>\n )}\n <FaqAccordion items={group.items} />\n </section>\n ))}\n </div>\n </div>\n )\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-3xl border border-ods-border overflow-hidden bg-ods-card divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between px-6 md:px-8 py-6\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"h-10 w-10 rounded-md bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * The FAQ display surface — ONE rendering on every host: the list grouped by\n * `faq.section` (each category a heading + its own accordion, with a category\n * jump-nav once there are 2+ categories). There is no flat/ungrouped mode and\n * no page-vs-embedded shell fork; the standalone /faqs page and every embed\n * render through this single path, so they cannot drift.\n *\n * - Standalone /faqs page: pass `initialFaqs` (SSR) + `heading={null}` (the\n * page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error\n * (or zero FAQs) the component renders nothing so the host page isn't\n * disfigured. The host always supplies the page shell — this renders a bare\n * <section>.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n // Grouped before the early returns so the hook order stays stable.\n const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs])\n\n // `undefined` → default <h2> title; `null` → the host page owns the <h1>, so\n // no title renders here. `heading === null` also makes the category headings\n // <h2> (directly under the page <h1>); otherwise they nest as <h3>.\n const headingNode =\n heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading\n\n // Degrade silently — never show an error banner or an empty section shell\n // where FAQs would be (host pages and the standalone surface both rely on it).\n if (error) return null\n if (!isLoading && faqs.length === 0) return null\n if (isLoading && faqs.length === 0) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n return (\n <>\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n <GroupedFaqList groups={groups} categoryHeadingAs={heading === null ? 'h2' : 'h3'} />\n </section>\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { ChevronButton } from './ui/chevron-button'\nimport { cn } from \"../utils/cn\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center justify-between px-6 md:px-8 py-6 text-left focus:outline-none transition-colors cursor-pointer\"\n >\n <div className=\"min-w-0 pr-4\">\n <h3>\n {item.question}\n </h3>\n </div>\n <div className=\"flex-shrink-0\">\n <ChevronButton\n aria-label={isOpen ? 'Collapse question' : 'Expand question'}\n size=\"md\"\n isExpanded={isOpen}\n backgroundColor=\"transparent\"\n borderColor=\"#3A3A3A\"\n />\n </div>\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden group-hover:bg-[#1E1E1E]/30\"\n >\n <div ref={ref} className=\"px-6 md:px-8 pb-6 text-ods-text-primary text-h4\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around the\n * bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host passes only config + a back button.\n *\n * `<FaqSection heading={null}>` lets `PageLayout`'s `TitleBlock` own the heading +\n * back button; it self-fetches `${apiBaseUrl}/api/faqs` via the authed\n * `useSelfFetch`, and renders nothing on a fetch error or zero FAQs.\n */\n\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\n\nexport interface FaqDocumentPageProps {\n /** Page title (PageLayout TitleBlock). Default \"FAQs\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'FAQs',\n subtitle,\n backButton,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage/DevSectionPage. Hide entirely\n // when the caller passes `false` (embed-mode where the host owns nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n return (\n <PageShell>\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg}>\n <FaqSection\n heading={null}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n </PageShell>\n );\n}\n"]}
@@ -0,0 +1,89 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
+
3
+
4
+
5
+ var _chunkYETA25JWcjs = require('./chunk-YETA25JW.cjs');
6
+
7
+ // src/hooks/use-near-viewport.ts
8
+ var _react = require('react');
9
+ var observers = /* @__PURE__ */ new Map();
10
+ var subscribers = /* @__PURE__ */ new WeakMap();
11
+ function getObserverFor(rootMargin) {
12
+ const existing = observers.get(rootMargin);
13
+ if (existing) return existing;
14
+ const io = new IntersectionObserver(
15
+ (entries) => {
16
+ entries.forEach((entry) => {
17
+ if (!entry.isIntersecting) return;
18
+ const cb = subscribers.get(entry.target);
19
+ if (cb) {
20
+ cb();
21
+ io.unobserve(entry.target);
22
+ subscribers.delete(entry.target);
23
+ }
24
+ });
25
+ },
26
+ { rootMargin }
27
+ );
28
+ observers.set(rootMargin, io);
29
+ return io;
30
+ }
31
+ function useNearViewport(rootMargin = "500px") {
32
+ const [isNear, setIsNear] = _react.useState.call(void 0, false);
33
+ const elRef = _react.useRef.call(void 0, null);
34
+ const ref = _react.useCallback.call(void 0,
35
+ (node) => {
36
+ const prev = elRef.current;
37
+ if (prev) {
38
+ const stillOurs = subscribers.get(prev);
39
+ if (stillOurs) {
40
+ subscribers.delete(prev);
41
+ _optionalChain([observers, 'access', _ => _.get, 'call', _2 => _2(rootMargin), 'optionalAccess', _3 => _3.unobserve, 'call', _4 => _4(prev)]);
42
+ }
43
+ }
44
+ elRef.current = node;
45
+ if (!node) return;
46
+ const cb = () => setIsNear(true);
47
+ subscribers.set(node, cb);
48
+ getObserverFor(rootMargin).observe(node);
49
+ },
50
+ [rootMargin]
51
+ );
52
+ _react.useEffect.call(void 0, () => {
53
+ return () => {
54
+ const el = elRef.current;
55
+ if (!el) return;
56
+ if (subscribers.get(el)) {
57
+ subscribers.delete(el);
58
+ _optionalChain([observers, 'access', _5 => _5.get, 'call', _6 => _6(rootMargin), 'optionalAccess', _7 => _7.unobserve, 'call', _8 => _8(el)]);
59
+ }
60
+ };
61
+ }, [rootMargin]);
62
+ return { ref, isNear };
63
+ }
64
+
65
+ // src/hooks/use-og-placeholder.ts
66
+
67
+ function useOgPlaceholder(buildUrl, title, siteName = "", enabled = true, aspect = "wide") {
68
+ return _react.useMemo.call(void 0, () => {
69
+ if (!enabled || !title) return null;
70
+ const options = {};
71
+ if (siteName) options.site = siteName;
72
+ if (aspect === "square") options.aspect = "square";
73
+ return buildUrl(title, options);
74
+ }, [buildUrl, title, siteName, enabled, aspect]);
75
+ }
76
+
77
+ // src/utils/embed-content-fetch.ts
78
+ var contentFetch = (input, init) => {
79
+ if (!_chunkYETA25JWcjs.hasEmbedAuthAdapter.call(void 0, )) return fetch(input, init);
80
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
81
+ return _chunkYETA25JWcjs.embedAuthedFetch.call(void 0, url, init);
82
+ };
83
+
84
+
85
+
86
+
87
+
88
+ exports.contentFetch = contentFetch; exports.useNearViewport = useNearViewport; exports.useOgPlaceholder = useOgPlaceholder;
89
+ //# sourceMappingURL=chunk-TFSYSWPS.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TFSYSWPS.cjs","../src/hooks/use-near-viewport.ts","../src/hooks/use-og-placeholder.ts","../src/utils/embed-content-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TFSYSWPS.cjs","sourcesContent":[null,"/**\n * useNearViewport — module-level shared IntersectionObserver in hook form.\n *\n * Single IO instance per `rootMargin` value, shared across every component\n * that mounts the hook. Reduces overhead vs. one IO per component on\n * grid/list pages where many subscribers observe the viewport with the same\n * margin. Promoted from the inline singleton at\n * `multi-platform-hub/components/shared/video-bites-display.tsx:21-43`,\n * which is the only IO pattern in either repo today.\n *\n * Usage:\n * ```tsx\n * function MyCard() {\n * const { ref, isNear } = useNearViewport('500px');\n * return <div ref={ref}>{isNear ? <HeavyChild /> : <Placeholder />}</div>;\n * }\n * ```\n *\n * StrictMode safety: cleanup uses an identity check on the registered\n * callback so React's dev double-mount (mount → cleanup → re-mount) does\n * not drop the second mount's freshly-set subscription. The IO callback\n * also checks `subscribers.get(target)` before invoking so a fire that\n * races with unmount cannot crash on a torn-down component.\n *\n * The hook fires once — on first intersection it sets `isNear=true` and\n * unobserves the element. Callers that need re-observation should\n * unmount and remount (or fork the hook for two-way behavior).\n */\n\nimport { useEffect, useRef, useState, useCallback } from 'react';\n\n// Per-rootMargin IO map. Multiple call sites with different margins each\n// get their own singleton observer.\nconst observers = new Map<string, IntersectionObserver>();\nconst subscribers = new WeakMap<Element, () => void>();\n\nfunction getObserverFor(rootMargin: string): IntersectionObserver {\n const existing = observers.get(rootMargin);\n if (existing) return existing;\n\n const io = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (!entry.isIntersecting) return;\n // Race-safe: re-read the callback at fire time. A late IO firing\n // after cleanup must not invoke a stale callback.\n const cb = subscribers.get(entry.target);\n if (cb) {\n cb();\n io.unobserve(entry.target);\n subscribers.delete(entry.target);\n }\n });\n },\n { rootMargin }\n );\n observers.set(rootMargin, io);\n return io;\n}\n\nexport interface UseNearViewportResult<T extends Element = HTMLElement> {\n /** Ref to attach to the element you want to gate on visibility. */\n ref: (node: T | null) => void;\n /** Flips to `true` once the element enters within `rootMargin` of the viewport. Never flips back. */\n isNear: boolean;\n}\n\n/**\n * @param rootMargin Margin around the viewport (CSS-style string).\n * '500px' = element starts mounting 500px before scroll-in.\n * '1000px' = a full viewport's worth of lookahead.\n * '0px' = strict on-screen detection.\n */\nexport function useNearViewport<T extends Element = HTMLElement>(\n rootMargin: string = '500px'\n): UseNearViewportResult<T> {\n const [isNear, setIsNear] = useState(false);\n const elRef = useRef<T | null>(null);\n\n // Subscribe/unsubscribe on element change.\n const ref = useCallback(\n (node: T | null) => {\n const prev = elRef.current;\n\n // Unsubscribe previous, if any. Identity-check the callback so a\n // StrictMode re-mount that has already re-registered keeps its sub.\n if (prev) {\n const stillOurs = subscribers.get(prev);\n if (stillOurs) {\n subscribers.delete(prev);\n observers.get(rootMargin)?.unobserve(prev);\n }\n }\n\n elRef.current = node;\n if (!node) return;\n\n const cb = () => setIsNear(true);\n subscribers.set(node, cb);\n getObserverFor(rootMargin).observe(node);\n },\n [rootMargin]\n );\n\n // Unsubscribe on unmount. Identity check guards the StrictMode race.\n useEffect(() => {\n return () => {\n const el = elRef.current;\n if (!el) return;\n if (subscribers.get(el)) {\n subscribers.delete(el);\n observers.get(rootMargin)?.unobserve(el);\n }\n };\n }, [rootMargin]);\n\n return { ref, isNear };\n}\n","'use client'\n\nimport { useMemo } from 'react'\n\n/**\n * Hook that generates a branded placeholder image URL using the\n * host's `/api/og-placeholder` server-side image generator (or any\n * builder the caller supplies).\n *\n * THIS HOOK IS A THIN CLIENT-SIDE WRAPPER. The hub-side wrapper\n * (`hooks/use-og-placeholder.ts` in multi-platform-hub) injects its\n * canonical `buildOgPlaceholderUrl` from `lib/utils/entity-image.ts`.\n * The lib version takes the builder as an argument so the lib has no\n * hub dependency.\n *\n * @param buildUrl - Function that builds the placeholder URL from a\n * title + options object (`{ site?, aspect? }`).\n * Hub passes `buildOgPlaceholderUrl` from\n * `lib/utils/entity-image.ts`. Embedded apps can\n * wire any equivalent that hits their own placeholder\n * route.\n * @param title - Text to display on the placeholder\n * @param siteName - Site name shown below the title (optional —\n * defaults to empty)\n * @param enabled - Whether to generate the URL (default: true)\n * @param aspect - `'wide'` (1200×630 social-card; default) or `'square'`\n * (1024×1024 — used by compact chat-inline card slots\n * so `object-cover` doesn't crop the title off).\n * @returns Placeholder image URL or null if disabled / no title\n */\nexport function useOgPlaceholder(\n buildUrl: (title: string, options: { site?: string; aspect?: 'wide' | 'square' }) => string,\n title: string | undefined | null,\n siteName: string = '',\n enabled: boolean = true,\n aspect: 'wide' | 'square' = 'wide',\n): string | null {\n return useMemo(() => {\n if (!enabled || !title) return null\n const options: { site?: string; aspect?: 'wide' | 'square' } = {}\n if (siteName) options.site = siteName\n if (aspect === 'square') options.aspect = 'square'\n return buildUrl(title, options)\n }, [buildUrl, title, siteName, enabled, aspect])\n}\n","'use client'\n\n/**\n * `contentFetch` — the fetch the lib's SELF-FETCHING content surfaces use:\n * roadmap (list + vote + per-task refresh), delivery, product releases,\n * onboarding guides (catalog + detail), legal docs, and the release-detail\n * injected roadmap/delivery sections.\n *\n * It deliberately reuses the SINGLE embed-auth knob — the registered\n * `EmbedAuthAdapter` — instead of introducing a second registration:\n * - adapter registered (host opted into embedded auth, e.g. openframe-frontend\n * for its embedded chat) → route through `embedAuthedFetch`, so content GETs/\n * POSTs carry the SAME bearer/cookie + 401-refresh as the chat.\n * - no adapter (the public hub, or the embedding example whose proxy injects\n * auth server-side) → a plain, byte-for-byte unchanged `fetch`.\n *\n * So a host wires auth ONCE (the chat adapter) and content inherits it; there is\n * no content-specific fetcher to configure.\n */\n\nimport { embedAuthedFetch, hasEmbedAuthAdapter } from './embed-authed-fetch'\n\nexport const contentFetch: typeof fetch = (input, init) => {\n if (!hasEmbedAuthAdapter()) return fetch(input, init)\n // embedAuthedFetch takes a string url; coerce the broader `fetch` input shape\n // (content surfaces always pass strings, but handle URL/Request defensively).\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.href : (input as Request).url\n return embedAuthedFetch(url, init)\n}\n"]}
@@ -0,0 +1,312 @@
1
+ "use client";
2
+ import {
3
+ FlamingoLogo,
4
+ MiamiCyberGangLogoFaceOnly,
5
+ OpenFrameLogo,
6
+ OpenmspLogo
7
+ } from "./chunk-V4IIBNTA.js";
8
+
9
+ // src/utils/platform-config.tsx
10
+ import { Globe } from "lucide-react";
11
+ import { jsx } from "react/jsx-runtime";
12
+ var platformIcons = {
13
+ openframe: /* @__PURE__ */ jsx(OpenFrameLogo, { className: "h-5 w-5", lowerPathColor: "#FFC008", upperPathColor: "#ffffff" }),
14
+ openmsp: /* @__PURE__ */ jsx(OpenmspLogo, { className: "h-5 w-5" }),
15
+ flamingo: /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#EC4899" }),
16
+ "flamingo-teaser": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#EC4899" }),
17
+ "marketing-hub": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#F357BB" }),
18
+ "product-hub": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#5EA62E" }),
19
+ "revenue-hub": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#FFC008" }),
20
+ "people-hub": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#5EFAF0" }),
21
+ "company-hub": /* @__PURE__ */ jsx(FlamingoLogo, { className: "h-5 w-5", fill: "#f36666" }),
22
+ tmcg: /* @__PURE__ */ jsx(MiamiCyberGangLogoFaceOnly, { className: "h-5 w-5" }),
23
+ universal: /* @__PURE__ */ jsx(Globe, { className: "h-5 w-5 text-[#10B981]" })
24
+ };
25
+ var platformColors = {
26
+ openmsp: "bg-[#3B82F6]",
27
+ openframe: "bg-[#8B5CF6]",
28
+ flamingo: "bg-[#EC4899]",
29
+ "flamingo-teaser": "bg-[#F59E0B]",
30
+ "marketing-hub": "bg-[#F357BB]",
31
+ "product-hub": "bg-[#5EA62E]",
32
+ "revenue-hub": "bg-[#FFC008]",
33
+ "people-hub": "bg-[#5EFAF0]",
34
+ "company-hub": "bg-[#f36666]",
35
+ tmcg: "bg-[#FF6B6B]",
36
+ universal: "bg-[#10B981]"
37
+ };
38
+ var platformDisplayNames = {
39
+ openmsp: "OpenMSP",
40
+ openframe: "OpenFrame",
41
+ flamingo: "Flamingo",
42
+ "flamingo-teaser": "Flamingo Teaser",
43
+ "marketing-hub": "Flamingo Marketing Hub",
44
+ "product-hub": "Flamingo Product Hub",
45
+ "revenue-hub": "Flamingo Revenue Hub",
46
+ "people-hub": "Flamingo People Hub",
47
+ "company-hub": "Flamingo Company Hub",
48
+ tmcg: "TMCG",
49
+ universal: "Universal"
50
+ };
51
+ var platformDescriptions = {
52
+ openmsp: "Comprehensive directory and comparison platform for managed service providers (MSPs) and technology vendors. Reduce vendor costs and discover open-source alternatives.",
53
+ openframe: "AI-driven open-source security operations center (SOC) and endpoint detection platform for MSPs.",
54
+ flamingo: "AI-driven open-source OS for MSPs. Swap bloated vendor tools for open ones. Automate the boring crap. Take your margin back.",
55
+ "flamingo-teaser": "Preview of Flamingo - the AI-driven open-source OS for MSPs.",
56
+ tmcg: "The Miami Cyber Gang - A cybersecurity community focused on education and collaboration.",
57
+ universal: "Cross-platform universal content."
58
+ };
59
+ var platformSlogans = {
60
+ openmsp: "Find Your Perfect MSP Partner",
61
+ openframe: "Open-Source Security Operations",
62
+ flamingo: "Open-Source OS for MSPs",
63
+ "flamingo-teaser": "Coming Soon: Open-Source OS for MSPs",
64
+ tmcg: "Miami Cyber Community",
65
+ universal: "Universal Platform"
66
+ };
67
+ var platformHexColors = {
68
+ openmsp: "#FFC008",
69
+ openframe: "#FFC008",
70
+ flamingo: "#FF6B9D",
71
+ universal: "#FFC008",
72
+ "flamingo-teaser": "#F59E0B",
73
+ "marketing-hub": "#F357BB",
74
+ "product-hub": "#5EA62E",
75
+ "revenue-hub": "#FFC008",
76
+ "people-hub": "#5EFAF0",
77
+ "company-hub": "#f36666",
78
+ tmcg: "#FF6B6B"
79
+ };
80
+ var platformIconNames = {
81
+ openmsp: "openmsp-logo",
82
+ openframe: "openframe-logo",
83
+ flamingo: "flamingo-logo",
84
+ universal: "globe",
85
+ "flamingo-teaser": "flamingo-logo",
86
+ "marketing-hub": "flamingo-logo",
87
+ "product-hub": "flamingo-logo",
88
+ "revenue-hub": "flamingo-logo",
89
+ "people-hub": "flamingo-logo",
90
+ "company-hub": "flamingo-logo",
91
+ tmcg: "tmcg-logo"
92
+ };
93
+ function getDefaultColorForPlatform(platformName) {
94
+ return platformHexColors[platformName] || platformHexColors.universal;
95
+ }
96
+ function getDefaultIconForPlatform(platformName) {
97
+ return platformIconNames[platformName] || platformIconNames.universal;
98
+ }
99
+ function transformPlatformConfigsToOptions(platformConfigs) {
100
+ return platformConfigs.map((platform) => ({
101
+ id: platform.id,
102
+ // Database UUID for matching
103
+ name: platform.name,
104
+ // Platform name enum
105
+ displayName: platform.display_name,
106
+ // Human-readable name
107
+ description: platform.description,
108
+ icon: platformIcons[platform.name] || platformIcons.universal,
109
+ color: platformColors[platform.name] || platformColors.universal
110
+ }));
111
+ }
112
+ function getPlatformIcon(platformName) {
113
+ return platformIcons[platformName] || platformIcons.universal;
114
+ }
115
+ function getPlatformColor(platformName) {
116
+ return platformColors[platformName] || platformColors.universal;
117
+ }
118
+ function getPlatformDisplayName(platformName) {
119
+ return platformDisplayNames[platformName] || platformName;
120
+ }
121
+ function getPlatformDescription(platformName) {
122
+ return platformDescriptions[platformName] || platformName;
123
+ }
124
+ function getPlatformSlogan(platformName) {
125
+ return platformSlogans[platformName] || platformName;
126
+ }
127
+ function getSmallPlatformIcon(platformName) {
128
+ const className = "h-4 w-4 flex-shrink-0";
129
+ switch (platformName) {
130
+ case "openframe":
131
+ return /* @__PURE__ */ jsx(OpenFrameLogo, { className, lowerPathColor: "#FFC008", upperPathColor: "#ffffff" });
132
+ case "openmsp":
133
+ return /* @__PURE__ */ jsx(OpenmspLogo, { className, frontBubbleColor: "#f1f1f1", innerFrontBubbleColor: "#000000", backBubbleColor: "#FFC008" });
134
+ case "flamingo":
135
+ case "flamingo-teaser":
136
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className: `${className}`, fill: "#EC4899" });
137
+ case "marketing-hub":
138
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-flamingo-pink-base)" });
139
+ case "product-hub":
140
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-green-success)" });
141
+ case "revenue-hub":
142
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-yellow-warning)" });
143
+ case "people-hub":
144
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-flamingo-cyan-base)" });
145
+ case "company-hub":
146
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-red-error)" });
147
+ case "tmcg":
148
+ return /* @__PURE__ */ jsx(MiamiCyberGangLogoFaceOnly, { className });
149
+ case "universal":
150
+ default:
151
+ return /* @__PURE__ */ jsx(Globe, { className });
152
+ }
153
+ }
154
+ function getPlatformIconComponent(platformName, className = "h-6 w-6") {
155
+ switch (platformName) {
156
+ case "openframe":
157
+ return /* @__PURE__ */ jsx(OpenFrameLogo, { className });
158
+ case "openmsp":
159
+ return /* @__PURE__ */ jsx(OpenmspLogo, { className, color: "#f1f1f1" });
160
+ case "flamingo":
161
+ case "flamingo-teaser":
162
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className: `${className} text-white` });
163
+ case "marketing-hub":
164
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-flamingo-pink-base)" });
165
+ case "product-hub":
166
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-green-success)" });
167
+ case "revenue-hub":
168
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-yellow-warning)" });
169
+ case "people-hub":
170
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-flamingo-cyan-base)" });
171
+ case "company-hub":
172
+ return /* @__PURE__ */ jsx(FlamingoLogo, { className, fill: "var(--ods-attention-red-error)" });
173
+ case "tmcg":
174
+ return /* @__PURE__ */ jsx(MiamiCyberGangLogoFaceOnly, { size: 24, className });
175
+ case "universal":
176
+ default:
177
+ return /* @__PURE__ */ jsx(Globe, { className });
178
+ }
179
+ }
180
+
181
+ // src/types/tool.types.ts
182
+ var ToolTypeValues = {
183
+ TACTICAL_RMM: "TACTICAL_RMM",
184
+ FLEET_MDM: "FLEET_MDM",
185
+ MESHCENTRAL: "MESHCENTRAL",
186
+ AUTHENTIK: "AUTHENTIK",
187
+ OPENFRAME: "OPENFRAME",
188
+ OPENFRAME_CHAT: "OPENFRAME_CHAT",
189
+ OPENFRAME_CLIENT: "OPENFRAME_CLIENT",
190
+ OSQUERY: "OSQUERY",
191
+ SYSTEM: "SYSTEM"
192
+ };
193
+ var toolLabels = {
194
+ TACTICAL_RMM: "Tactical",
195
+ FLEET_MDM: "Fleet",
196
+ MESHCENTRAL: "MeshCentral",
197
+ AUTHENTIK: "Authentik",
198
+ OPENFRAME: "OpenFrame",
199
+ OPENFRAME_CHAT: "OpenFrame Chat",
200
+ OPENFRAME_CLIENT: "OpenFrame Client",
201
+ OSQUERY: "Osquery",
202
+ SYSTEM: "System"
203
+ };
204
+
205
+ // src/utils/access-code-client.ts
206
+ async function validateAccessCode(email, code, endpoints) {
207
+ try {
208
+ const response = await fetch(endpoints.validateUrl, {
209
+ method: "POST",
210
+ headers: {
211
+ "Content-Type": "application/json"
212
+ },
213
+ body: JSON.stringify({ email, code })
214
+ });
215
+ if (!response.ok) {
216
+ const error = await response.json().catch(() => ({}));
217
+ throw new Error(error.error || "Validation request failed");
218
+ }
219
+ return await response.json();
220
+ } catch (error) {
221
+ return {
222
+ valid: false,
223
+ message: error instanceof Error ? error.message : "Validation failed"
224
+ };
225
+ }
226
+ }
227
+ async function consumeAccessCode(email, code, endpoints) {
228
+ try {
229
+ const response = await fetch(endpoints.consumeUrl, {
230
+ method: "POST",
231
+ headers: {
232
+ "Content-Type": "application/json"
233
+ },
234
+ body: JSON.stringify({ email, code })
235
+ });
236
+ if (!response.ok) {
237
+ const error = await response.json().catch(() => ({}));
238
+ throw new Error(error.error || "Consumption request failed");
239
+ }
240
+ return await response.json();
241
+ } catch (error) {
242
+ return {
243
+ success: false,
244
+ consumed: false,
245
+ message: error instanceof Error ? error.message : "Consumption failed"
246
+ };
247
+ }
248
+ }
249
+ async function validateAndConsumeAccessCode(email, code, endpoints) {
250
+ const validation = await validateAccessCode(email, code, endpoints);
251
+ if (!validation.valid) {
252
+ return validation;
253
+ }
254
+ const consumption = await consumeAccessCode(email, code, endpoints);
255
+ return {
256
+ ...validation,
257
+ consumed: consumption.consumed,
258
+ message: consumption.consumed ? `Access granted for ${validation.cohort_name}` : consumption.message || validation.message
259
+ };
260
+ }
261
+
262
+ // src/utils/humanity-signals.ts
263
+ var HONEYPOT_FIELD = "contact_url_confirm";
264
+ var ELAPSED_MS_FIELD = "form_elapsed_ms";
265
+ var DEFAULT_MIN_FILL_MS = 700;
266
+ function extractHumanitySignals(body) {
267
+ const b = body ?? {};
268
+ const rawHp = b[HONEYPOT_FIELD];
269
+ const honeypot = rawHp == null ? "" : String(rawHp);
270
+ const rawMs = b[ELAPSED_MS_FIELD];
271
+ const elapsedMs = typeof rawMs === "number" && Number.isFinite(rawMs) ? rawMs : null;
272
+ return { honeypot, elapsedMs };
273
+ }
274
+ function evaluateHumanitySignals(body, opts) {
275
+ const { honeypot, elapsedMs } = extractHumanitySignals(body);
276
+ if (honeypot.trim() !== "") return { ok: false, reason: "honeypot" };
277
+ if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: "too_fast" };
278
+ return { ok: true };
279
+ }
280
+ var splitCsvEnv = (s) => s?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
281
+
282
+ export {
283
+ platformIcons,
284
+ platformColors,
285
+ platformDisplayNames,
286
+ platformDescriptions,
287
+ platformSlogans,
288
+ platformHexColors,
289
+ platformIconNames,
290
+ getDefaultColorForPlatform,
291
+ getDefaultIconForPlatform,
292
+ transformPlatformConfigsToOptions,
293
+ getPlatformIcon,
294
+ getPlatformColor,
295
+ getPlatformDisplayName,
296
+ getPlatformDescription,
297
+ getPlatformSlogan,
298
+ getSmallPlatformIcon,
299
+ getPlatformIconComponent,
300
+ ToolTypeValues,
301
+ toolLabels,
302
+ validateAccessCode,
303
+ consumeAccessCode,
304
+ validateAndConsumeAccessCode,
305
+ HONEYPOT_FIELD,
306
+ ELAPSED_MS_FIELD,
307
+ DEFAULT_MIN_FILL_MS,
308
+ extractHumanitySignals,
309
+ evaluateHumanitySignals,
310
+ splitCsvEnv
311
+ };
312
+ //# sourceMappingURL=chunk-TYZEMPPH.js.map