@flamingo-stack/openframe-frontend-core 0.0.292 → 0.0.293-snapshot.20260619104458

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 (163) hide show
  1. package/dist/{chunk-6FHO73AP.js → chunk-26PKDALD.js} +7 -79
  2. package/dist/chunk-26PKDALD.js.map +1 -0
  3. package/dist/{chunk-OXOTKEYY.cjs → chunk-4W7NYJ3B.cjs} +23 -23
  4. package/dist/{chunk-OXOTKEYY.cjs.map → chunk-4W7NYJ3B.cjs.map} +1 -1
  5. package/dist/{chunk-KBKZYJRI.cjs → chunk-5E2HOSSH.cjs} +66 -19
  6. package/dist/chunk-5E2HOSSH.cjs.map +1 -0
  7. package/dist/{chunk-CUQH4SHH.js → chunk-6GCI7JOE.js} +2 -2
  8. package/dist/{chunk-2NJ44RTT.cjs → chunk-6LUVYHKD.cjs} +30 -30
  9. package/dist/{chunk-2NJ44RTT.cjs.map → chunk-6LUVYHKD.cjs.map} +1 -1
  10. package/dist/{chunk-E2YXRSDG.js → chunk-6SG25O2N.js} +15 -13
  11. package/dist/chunk-6SG25O2N.js.map +1 -0
  12. package/dist/{chunk-CDJOKNCS.cjs → chunk-73QT66LJ.cjs} +25 -19
  13. package/dist/chunk-73QT66LJ.cjs.map +1 -0
  14. package/dist/{chunk-N6ZM5PYZ.js → chunk-7RIYT7ZH.js} +49 -2
  15. package/dist/chunk-7RIYT7ZH.js.map +1 -0
  16. package/dist/{chunk-FFP2A77V.cjs → chunk-AFKRDSRS.cjs} +12 -12
  17. package/dist/{chunk-FFP2A77V.cjs.map → chunk-AFKRDSRS.cjs.map} +1 -1
  18. package/dist/{chunk-PZZGDS5I.cjs → chunk-B4T3RTFX.cjs} +24 -22
  19. package/dist/chunk-B4T3RTFX.cjs.map +1 -0
  20. package/dist/{chunk-5R5OODNE.cjs → chunk-CLWQ7MHW.cjs} +40 -40
  21. package/dist/{chunk-5R5OODNE.cjs.map → chunk-CLWQ7MHW.cjs.map} +1 -1
  22. package/dist/{chunk-C667P6LZ.js → chunk-DEBURY5R.js} +13 -7
  23. package/dist/{chunk-C667P6LZ.js.map → chunk-DEBURY5R.js.map} +1 -1
  24. package/dist/{chunk-HTYUZXQP.js → chunk-E24HKKIE.js} +5 -5
  25. package/dist/{chunk-VK4B6UGU.js → chunk-E4XABBSU.js} +16 -8
  26. package/dist/{chunk-VK4B6UGU.js.map → chunk-E4XABBSU.js.map} +1 -1
  27. package/dist/{chunk-DUIWR7RQ.js → chunk-EJXHZX2E.js} +3 -3
  28. package/dist/{chunk-5PELVUFT.cjs → chunk-EYEW6PTA.cjs} +44 -36
  29. package/dist/chunk-EYEW6PTA.cjs.map +1 -0
  30. package/dist/{chunk-SLP4KXP6.js → chunk-FQJK446R.js} +8 -2
  31. package/dist/chunk-FQJK446R.js.map +1 -0
  32. package/dist/{chunk-JC5RN7ZS.cjs → chunk-FT4FCV7L.cjs} +6 -6
  33. package/dist/{chunk-JC5RN7ZS.cjs.map → chunk-FT4FCV7L.cjs.map} +1 -1
  34. package/dist/{chunk-ZHNL2IPK.cjs → chunk-J54Z3OCR.cjs} +8 -2
  35. package/dist/chunk-J54Z3OCR.cjs.map +1 -0
  36. package/dist/{chunk-Z6BK4XHH.cjs → chunk-KXCRGTRN.cjs} +10 -82
  37. package/dist/chunk-KXCRGTRN.cjs.map +1 -0
  38. package/dist/{chunk-5KD3S25X.cjs → chunk-LFGGF7OT.cjs} +139 -2
  39. package/dist/chunk-LFGGF7OT.cjs.map +1 -0
  40. package/dist/{chunk-N45M3TK3.js → chunk-NSPOYUBH.js} +2 -2
  41. package/dist/{chunk-TYZEMPPH.js → chunk-OQ6X7ZOC.js} +138 -1
  42. package/dist/chunk-OQ6X7ZOC.js.map +1 -0
  43. package/dist/{chunk-B2U6INNO.js → chunk-QFG4G62D.js} +4 -4
  44. package/dist/{chunk-IXDTNQF4.js → chunk-RT6DJB3C.js} +4 -4
  45. package/dist/{chunk-MDLWEJAV.cjs → chunk-V6YZGLHZ.cjs} +454 -459
  46. package/dist/chunk-V6YZGLHZ.cjs.map +1 -0
  47. package/dist/{chunk-2BMVBPC7.cjs → chunk-YIGPRLQY.cjs} +9 -9
  48. package/dist/{chunk-2BMVBPC7.cjs.map → chunk-YIGPRLQY.cjs.map} +1 -1
  49. package/dist/{chunk-5FK7X3EE.js → chunk-ZYZWD7LS.js} +9 -14
  50. package/dist/chunk-ZYZWD7LS.js.map +1 -0
  51. package/dist/components/chat/entity-cards/roadmap-card.d.ts +7 -1
  52. package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -1
  53. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +0 -5
  54. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  55. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  56. package/dist/components/chat/index.cjs +7 -7
  57. package/dist/components/chat/index.js +6 -6
  58. package/dist/components/chat/types/api.types.d.ts +0 -6
  59. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  60. package/dist/components/contact/index.cjs +8 -8
  61. package/dist/components/contact/index.js +7 -7
  62. package/dist/components/docs/index.cjs +6 -6
  63. package/dist/components/docs/index.js +5 -5
  64. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  65. package/dist/components/embeds/index.cjs +8 -8
  66. package/dist/components/embeds/index.js +7 -7
  67. package/dist/components/faq/faq-section.d.ts.map +1 -1
  68. package/dist/components/faq/index.cjs +8 -8
  69. package/dist/components/faq/index.js +7 -7
  70. package/dist/components/features/index.cjs +7 -7
  71. package/dist/components/features/index.js +6 -6
  72. package/dist/components/index.cjs +214 -193
  73. package/dist/components/index.cjs.map +1 -1
  74. package/dist/components/index.js +50 -29
  75. package/dist/components/index.js.map +1 -1
  76. package/dist/components/navigation/index.cjs +7 -7
  77. package/dist/components/navigation/index.js +6 -6
  78. package/dist/components/onboarding-guides/index.cjs +24 -24
  79. package/dist/components/onboarding-guides/index.js +4 -4
  80. package/dist/components/related-content/index.cjs +8 -8
  81. package/dist/components/related-content/index.js +7 -7
  82. package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
  83. package/dist/components/shared/delivery/delivery-row.d.ts +8 -1
  84. package/dist/components/shared/delivery/delivery-row.d.ts.map +1 -1
  85. package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -1
  86. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  87. package/dist/components/shared/roadmap/roadmap-view.d.ts.map +1 -1
  88. package/dist/components/tickets/help-center-card.d.ts +7 -1
  89. package/dist/components/tickets/help-center-card.d.ts.map +1 -1
  90. package/dist/components/tickets/help-center-list.d.ts.map +1 -1
  91. package/dist/components/tickets/index.cjs +82 -73
  92. package/dist/components/tickets/index.cjs.map +1 -1
  93. package/dist/components/tickets/index.js +24 -15
  94. package/dist/components/tickets/index.js.map +1 -1
  95. package/dist/components/tickets/ticket-center.d.ts.map +1 -1
  96. package/dist/components/tickets/ticket-row.d.ts +6 -1
  97. package/dist/components/tickets/ticket-row.d.ts.map +1 -1
  98. package/dist/components/ui/index.cjs +7 -7
  99. package/dist/components/ui/index.js +6 -6
  100. package/dist/hooks/index.cjs +5 -3
  101. package/dist/hooks/index.cjs.map +1 -1
  102. package/dist/hooks/index.d.ts +1 -0
  103. package/dist/hooks/index.d.ts.map +1 -1
  104. package/dist/hooks/index.js +4 -2
  105. package/dist/hooks/use-scroll-to-hash.d.ts +17 -0
  106. package/dist/hooks/use-scroll-to-hash.d.ts.map +1 -0
  107. package/dist/index.cjs +19 -7
  108. package/dist/index.cjs.map +1 -1
  109. package/dist/index.js +19 -7
  110. package/dist/utils/dev-sections/dev-section-param-keys.d.ts +10 -0
  111. package/dist/utils/dev-sections/dev-section-param-keys.d.ts.map +1 -1
  112. package/dist/utils/index.cjs +71 -1
  113. package/dist/utils/index.cjs.map +1 -1
  114. package/dist/utils/index.d.ts +2 -1
  115. package/dist/utils/index.d.ts.map +1 -1
  116. package/dist/utils/index.js +67 -2
  117. package/dist/utils/index.js.map +1 -1
  118. package/dist/utils/same-page-hash-nav.d.ts +37 -0
  119. package/dist/utils/same-page-hash-nav.d.ts.map +1 -0
  120. package/dist/utils/source-icons.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/components/chat/entity-cards/roadmap-card.tsx +8 -1
  123. package/src/components/chat/hooks/use-nats-chat-adapter.ts +0 -8
  124. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +1 -12
  125. package/src/components/chat/types/api.types.ts +0 -6
  126. package/src/components/docs/use-document-tree.ts +45 -3
  127. package/src/components/faq/faq-section.tsx +22 -9
  128. package/src/components/shared/delivery/delivery-lists.tsx +9 -0
  129. package/src/components/shared/delivery/delivery-row.tsx +15 -2
  130. package/src/components/shared/delivery/delivery-table.tsx +7 -1
  131. package/src/components/shared/roadmap/roadmap-grid.tsx +7 -0
  132. package/src/components/shared/roadmap/roadmap-view.tsx +11 -0
  133. package/src/components/tickets/help-center-card.tsx +9 -17
  134. package/src/components/tickets/help-center-list.tsx +13 -0
  135. package/src/components/tickets/ticket-center.tsx +2 -0
  136. package/src/components/tickets/ticket-row.tsx +7 -1
  137. package/src/hooks/index.ts +5 -0
  138. package/src/hooks/use-scroll-to-hash.ts +74 -0
  139. package/src/utils/.source-icons.md +1 -1
  140. package/src/utils/dev-sections/dev-section-param-keys.ts +14 -0
  141. package/src/utils/index.ts +17 -1
  142. package/src/utils/same-page-hash-nav.ts +115 -0
  143. package/src/utils/source-icons.ts +7 -1
  144. package/dist/chunk-5FK7X3EE.js.map +0 -1
  145. package/dist/chunk-5KD3S25X.cjs.map +0 -1
  146. package/dist/chunk-5PELVUFT.cjs.map +0 -1
  147. package/dist/chunk-6FHO73AP.js.map +0 -1
  148. package/dist/chunk-CDJOKNCS.cjs.map +0 -1
  149. package/dist/chunk-E2YXRSDG.js.map +0 -1
  150. package/dist/chunk-KBKZYJRI.cjs.map +0 -1
  151. package/dist/chunk-MDLWEJAV.cjs.map +0 -1
  152. package/dist/chunk-N6ZM5PYZ.js.map +0 -1
  153. package/dist/chunk-PZZGDS5I.cjs.map +0 -1
  154. package/dist/chunk-SLP4KXP6.js.map +0 -1
  155. package/dist/chunk-TYZEMPPH.js.map +0 -1
  156. package/dist/chunk-Z6BK4XHH.cjs.map +0 -1
  157. package/dist/chunk-ZHNL2IPK.cjs.map +0 -1
  158. /package/dist/{chunk-CUQH4SHH.js.map → chunk-6GCI7JOE.js.map} +0 -0
  159. /package/dist/{chunk-HTYUZXQP.js.map → chunk-E24HKKIE.js.map} +0 -0
  160. /package/dist/{chunk-DUIWR7RQ.js.map → chunk-EJXHZX2E.js.map} +0 -0
  161. /package/dist/{chunk-N45M3TK3.js.map → chunk-NSPOYUBH.js.map} +0 -0
  162. /package/dist/{chunk-B2U6INNO.js.map → chunk-QFG4G62D.js.map} +0 -0
  163. /package/dist/{chunk-IXDTNQF4.js.map → chunk-RT6DJB3C.js.map} +0 -0
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import {
3
3
  PageLayout
4
- } from "./chunk-N45M3TK3.js";
4
+ } from "./chunk-NSPOYUBH.js";
5
5
  import {
6
6
  formatBioText,
7
7
  getProxiedImageUrl
8
- } from "./chunk-SLP4KXP6.js";
8
+ } from "./chunk-FQJK446R.js";
9
9
  import {
10
10
  useChatRuntime
11
11
  } from "./chunk-LCNMR277.js";
@@ -148,4 +148,4 @@ export {
148
148
  DetailPageSkeleton,
149
149
  ArticleAuthorByline
150
150
  };
151
- //# sourceMappingURL=chunk-DUIWR7RQ.js.map
151
+ //# sourceMappingURL=chunk-EJXHZX2E.js.map
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunk2BMVBPC7cjs = require('./chunk-2BMVBPC7.cjs');
5
+ var _chunkYIGPRLQYcjs = require('./chunk-YIGPRLQY.cjs');
6
6
 
7
7
 
8
8
 
@@ -11,11 +11,15 @@ var _chunk2BMVBPC7cjs = require('./chunk-2BMVBPC7.cjs');
11
11
 
12
12
 
13
13
 
14
+ var _chunkKXCRGTRNcjs = require('./chunk-KXCRGTRN.cjs');
14
15
 
15
- var _chunkZ6BK4XHHcjs = require('./chunk-Z6BK4XHH.cjs');
16
16
 
17
17
 
18
- var _chunkZHNL2IPKcjs = require('./chunk-ZHNL2IPK.cjs');
18
+
19
+ var _chunkLFGGF7OTcjs = require('./chunk-LFGGF7OT.cjs');
20
+
21
+
22
+ var _chunkJ54Z3OCRcjs = require('./chunk-J54Z3OCR.cjs');
19
23
 
20
24
 
21
25
 
@@ -1025,15 +1029,15 @@ function useDocNavigation() {
1025
1029
  function scrollToContent() {
1026
1030
  const article = document.querySelector("article");
1027
1031
  if (article) {
1028
- _chunkZ6BK4XHHcjs.scrollElementIntoView.call(void 0, article, { headerOffset: 80 });
1032
+ _chunkLFGGF7OTcjs.scrollElementIntoView.call(void 0, article, { headerOffset: _chunkLFGGF7OTcjs.HUB_HEADER_OFFSET_PX });
1029
1033
  } else {
1030
1034
  window.scrollTo({ top: 0, behavior: "smooth" });
1031
1035
  }
1032
1036
  }
1033
1037
  function useDocumentTree(config, initialPath) {
1034
1038
  const { structureEndpoint, contentEndpoint, baseRoute } = config;
1035
- const folderIndexFile = _nullishCoalesce(config.folderIndexFile, () => ( _chunkZ6BK4XHHcjs.DEFAULT_FOLDER_INDEX_FILE));
1036
- const cleanInitialPath = _chunkZ6BK4XHHcjs.stripFolderIndexFromPath.call(void 0,
1039
+ const folderIndexFile = _nullishCoalesce(config.folderIndexFile, () => ( _chunkKXCRGTRNcjs.DEFAULT_FOLDER_INDEX_FILE));
1040
+ const cleanInitialPath = _chunkKXCRGTRNcjs.stripFolderIndexFromPath.call(void 0,
1037
1041
  _optionalChain([initialPath, 'optionalAccess', _2 => _2.replace, 'call', _3 => _3(/\/$/, "")]) || "",
1038
1042
  folderIndexFile
1039
1043
  );
@@ -1059,12 +1063,12 @@ function useDocumentTree(config, initialPath) {
1059
1063
  } else if (pathname.startsWith(`${normalizedBaseRoute}/`)) {
1060
1064
  pathFromUrl = pathname.substring(`${normalizedBaseRoute}/`.length);
1061
1065
  }
1062
- pathFromUrl = _chunkZ6BK4XHHcjs.stripFolderIndexFromPath.call(void 0, pathFromUrl, folderIndexFile);
1066
+ pathFromUrl = _chunkKXCRGTRNcjs.stripFolderIndexFromPath.call(void 0, pathFromUrl, folderIndexFile);
1063
1067
  if (pathFromUrl !== selectedPathRef.current) {
1064
1068
  setSelectedPath(pathFromUrl);
1065
1069
  if (pathFromUrl) {
1066
1070
  const parentPath = pathFromUrl.includes("/") ? pathFromUrl.substring(0, pathFromUrl.lastIndexOf("/")) : pathFromUrl;
1067
- setExpandedNodes(new Set(_chunkZ6BK4XHHcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
1071
+ setExpandedNodes(new Set(_chunkKXCRGTRNcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
1068
1072
  }
1069
1073
  setTimeout(() => {
1070
1074
  scrollToContent();
@@ -1090,7 +1094,7 @@ function useDocumentTree(config, initialPath) {
1090
1094
  if (selectedPath === "") {
1091
1095
  pathToFetch = folderIndexFile;
1092
1096
  } else {
1093
- const node = _chunkZ6BK4XHHcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
1097
+ const node = _chunkKXCRGTRNcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
1094
1098
  if (node && node.type === "folder" && !node.hasReadme) {
1095
1099
  return;
1096
1100
  }
@@ -1121,7 +1125,7 @@ function useDocumentTree(config, initialPath) {
1121
1125
  if (cleanInitialPath) {
1122
1126
  const pathForExpansion = cleanInitialPath.includes(".") ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf("/")) : cleanInitialPath;
1123
1127
  if (pathForExpansion) {
1124
- setExpandedNodes(new Set(_chunkZ6BK4XHHcjs.getDocAncestorNodeIds.call(void 0, pathForExpansion)));
1128
+ setExpandedNodes(new Set(_chunkKXCRGTRNcjs.getDocAncestorNodeIds.call(void 0, pathForExpansion)));
1125
1129
  }
1126
1130
  } else if (result.data.length > 0) {
1127
1131
  const hasRootReadme = result.data.some(
@@ -1208,11 +1212,11 @@ function useDocumentTree(config, initialPath) {
1208
1212
  if (node.type === "folder") {
1209
1213
  setExpandedNodes((prev) => {
1210
1214
  if (prev.has(node.id)) {
1211
- const ancestorIds = _chunkZ6BK4XHHcjs.getDocAncestorNodeIds.call(void 0, node.path);
1215
+ const ancestorIds = _chunkKXCRGTRNcjs.getDocAncestorNodeIds.call(void 0, node.path);
1212
1216
  ancestorIds.pop();
1213
1217
  return new Set(ancestorIds);
1214
1218
  } else {
1215
- return new Set(_chunkZ6BK4XHHcjs.getDocAncestorNodeIds.call(void 0, node.path));
1219
+ return new Set(_chunkKXCRGTRNcjs.getDocAncestorNodeIds.call(void 0, node.path));
1216
1220
  }
1217
1221
  });
1218
1222
  if (node.hasReadme) {
@@ -1229,7 +1233,7 @@ function useDocumentTree(config, initialPath) {
1229
1233
  const lastSlash = node.path.lastIndexOf("/");
1230
1234
  if (lastSlash > 0) {
1231
1235
  const parentPath = node.path.substring(0, lastSlash);
1232
- setExpandedNodes(new Set(_chunkZ6BK4XHHcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
1236
+ setExpandedNodes(new Set(_chunkKXCRGTRNcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
1233
1237
  }
1234
1238
  lastFetchedPath.current = null;
1235
1239
  setSelectedPath(node.path);
@@ -1243,12 +1247,17 @@ function useDocumentTree(config, initialPath) {
1243
1247
  const hashIndex = path.indexOf("#");
1244
1248
  const anchor = hashIndex !== -1 ? path.substring(hashIndex) : "";
1245
1249
  const cleanPath = path.replace(/\/$/, "").split("#")[0];
1250
+ const pathForSelection = anchor && _optionalChain([options, 'optionalAccess', _4 => _4.fromInternalLink]) && cleanPath === "" ? selectedPathRef.current : _chunkKXCRGTRNcjs.stripFolderIndexFromPath.call(void 0, cleanPath, folderIndexFile);
1251
+ if (anchor && _optionalChain([options, 'optionalAccess', _5 => _5.fromInternalLink]) && pathForSelection === selectedPathRef.current) {
1252
+ _chunkLFGGF7OTcjs.navigateSamePageHash.call(void 0, anchor, { headerOffset: _chunkLFGGF7OTcjs.HUB_HEADER_OFFSET_PX });
1253
+ return;
1254
+ }
1246
1255
  const scrollAfterNav = () => {
1247
1256
  if (anchor) {
1248
1257
  setTimeout(() => {
1249
1258
  const el = document.getElementById(anchor.substring(1));
1250
1259
  if (el) {
1251
- _chunkZ6BK4XHHcjs.scrollElementIntoView.call(void 0, el, { headerOffset: 80 });
1260
+ _chunkLFGGF7OTcjs.scrollElementIntoView.call(void 0, el, { headerOffset: _chunkLFGGF7OTcjs.HUB_HEADER_OFFSET_PX });
1252
1261
  } else {
1253
1262
  scrollToContent();
1254
1263
  }
@@ -1259,7 +1268,7 @@ function useDocumentTree(config, initialPath) {
1259
1268
  }, 150);
1260
1269
  }
1261
1270
  };
1262
- if (_optionalChain([options, 'optionalAccess', _4 => _4.expandFolder])) {
1271
+ if (_optionalChain([options, 'optionalAccess', _6 => _6.expandFolder])) {
1263
1272
  lastFetchedPath.current = null;
1264
1273
  setSelectedPath(cleanPath);
1265
1274
  window.history.pushState({}, "", `${normalizedBaseRoute}/${cleanPath}${anchor}`);
@@ -1271,18 +1280,17 @@ function useDocumentTree(config, initialPath) {
1271
1280
  nodeIdsToExpand.push(currentPath2.toLowerCase());
1272
1281
  }
1273
1282
  setExpandedNodes(new Set(nodeIdsToExpand));
1274
- if (_optionalChain([options, 'optionalAccess', _5 => _5.fromInternalLink])) {
1283
+ if (_optionalChain([options, 'optionalAccess', _7 => _7.fromInternalLink])) {
1275
1284
  scrollAfterNav();
1276
1285
  }
1277
1286
  setError(null);
1278
1287
  return;
1279
1288
  }
1280
- const pathForSelection = _chunkZ6BK4XHHcjs.stripFolderIndexFromPath.call(void 0, cleanPath, folderIndexFile);
1281
1289
  const urlPath = pathForSelection;
1282
1290
  lastFetchedPath.current = null;
1283
1291
  setSelectedPath(pathForSelection);
1284
1292
  window.history.pushState({}, "", `${normalizedBaseRoute}/${urlPath}${anchor}`);
1285
- if (_optionalChain([options, 'optionalAccess', _6 => _6.fromInternalLink])) {
1293
+ if (_optionalChain([options, 'optionalAccess', _8 => _8.fromInternalLink])) {
1286
1294
  scrollAfterNav();
1287
1295
  }
1288
1296
  const pathParts = cleanPath.split("/");
@@ -1301,8 +1309,8 @@ function useDocumentTree(config, initialPath) {
1301
1309
  return docNavigation.register({
1302
1310
  baseRoute: normalizedBaseRoute,
1303
1311
  findNodeByPath: (path) => {
1304
- const clean = _chunkZ6BK4XHHcjs.stripFolderIndexFromPath.call(void 0, path.replace(/\/$/, "").split("#")[0], folderIndexFile);
1305
- return _nullishCoalesce(_chunkZ6BK4XHHcjs.findDocNodeByPath.call(void 0, clean, structure), () => ( null));
1312
+ const clean = _chunkKXCRGTRNcjs.stripFolderIndexFromPath.call(void 0, path.replace(/\/$/, "").split("#")[0], folderIndexFile);
1313
+ return _nullishCoalesce(_chunkKXCRGTRNcjs.findDocNodeByPath.call(void 0, clean, structure), () => ( null));
1306
1314
  },
1307
1315
  selectNode
1308
1316
  });
@@ -1331,11 +1339,11 @@ function useScrollSpy(sections) {
1331
1339
  const [activeSection, setActiveSection] = _react.useState.call(void 0, "");
1332
1340
  const isScrollingFromClick = _react.useRef.call(void 0, false);
1333
1341
  const sectionIds = _react.useMemo.call(void 0,
1334
- () => _nullishCoalesce(_optionalChain([sections, 'optionalAccess', _7 => _7.map, 'call', _8 => _8((s) => s.id), 'access', _9 => _9.join, 'call', _10 => _10(",")]), () => ( "")),
1342
+ () => _nullishCoalesce(_optionalChain([sections, 'optionalAccess', _9 => _9.map, 'call', _10 => _10((s) => s.id), 'access', _11 => _11.join, 'call', _12 => _12(",")]), () => ( "")),
1335
1343
  [sections]
1336
1344
  );
1337
1345
  const stableSections = _react.useRef.call(void 0, sections);
1338
- if (sectionIds !== (_nullishCoalesce(_optionalChain([stableSections, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.map, 'call', _13 => _13((s) => s.id), 'access', _14 => _14.join, 'call', _15 => _15(",")]), () => ( "")))) {
1346
+ if (sectionIds !== (_nullishCoalesce(_optionalChain([stableSections, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.map, 'call', _15 => _15((s) => s.id), 'access', _16 => _16.join, 'call', _17 => _17(",")]), () => ( "")))) {
1339
1347
  stableSections.current = sections;
1340
1348
  }
1341
1349
  const handleSectionClick = _react.useCallback.call(void 0, (sectionId) => {
@@ -1343,7 +1351,7 @@ function useScrollSpy(sections) {
1343
1351
  if (!targetElement) return;
1344
1352
  isScrollingFromClick.current = true;
1345
1353
  setActiveSection(sectionId);
1346
- _chunkZ6BK4XHHcjs.scrollElementIntoView.call(void 0, targetElement, { headerOffset: SCROLL_OFFSET });
1354
+ _chunkLFGGF7OTcjs.scrollElementIntoView.call(void 0, targetElement, { headerOffset: SCROLL_OFFSET });
1347
1355
  setTimeout(() => {
1348
1356
  isScrollingFromClick.current = false;
1349
1357
  }, 800);
@@ -1354,7 +1362,7 @@ function useScrollSpy(sections) {
1354
1362
  const handleScroll = () => {
1355
1363
  if (isScrollingFromClick.current) return;
1356
1364
  const scrollPosition = window.scrollY + SCROLL_OFFSET;
1357
- let currentSection = _optionalChain([currentSections, 'access', _16 => _16[0], 'optionalAccess', _17 => _17.id]) || "";
1365
+ let currentSection = _optionalChain([currentSections, 'access', _18 => _18[0], 'optionalAccess', _19 => _19.id]) || "";
1358
1366
  for (let i = currentSections.length - 1; i >= 0; i--) {
1359
1367
  const element = document.getElementById(currentSections[i].id);
1360
1368
  if (element && scrollPosition >= element.offsetTop) {
@@ -1433,9 +1441,9 @@ function DocViewerContent({
1433
1441
  },
1434
1442
  docPath
1435
1443
  );
1436
- const { activeSection, handleSectionClick } = useScrollSpy(_optionalChain([content, 'optionalAccess', _18 => _18.sections]));
1444
+ const { activeSection, handleSectionClick } = useScrollSpy(_optionalChain([content, 'optionalAccess', _20 => _20.sections]));
1437
1445
  const docNav = useDocNavigation();
1438
- const docSearch = _chunk2BMVBPC7cjs.useDocSearch.call(void 0, {
1446
+ const docSearch = _chunkYIGPRLQYcjs.useDocSearch.call(void 0, {
1439
1447
  source: chatSource,
1440
1448
  baseRoute,
1441
1449
  onNavigate: (path) => navigateToDoc(path, { fromInternalLink: true }),
@@ -1449,11 +1457,11 @@ function DocViewerContent({
1449
1457
  sourceId
1450
1458
  });
1451
1459
  }, [content, selectedPath, renderContent, navigateToDoc, sourceId]);
1452
- const selectedNodeDocType = selectedPath && structure.length > 0 ? _optionalChain([_chunkZ6BK4XHHcjs.findDocNodeByPath.call(void 0, selectedPath, structure), 'optionalAccess', _19 => _19.documentType]) : void 0;
1453
- const activeDocType = _nullishCoalesce(_optionalChain([content, 'optionalAccess', _20 => _20.documentType]), () => ( selectedNodeDocType));
1460
+ const selectedNodeDocType = selectedPath && structure.length > 0 ? _optionalChain([_chunkKXCRGTRNcjs.findDocNodeByPath.call(void 0, selectedPath, structure), 'optionalAccess', _21 => _21.documentType]) : void 0;
1461
+ const activeDocType = _nullishCoalesce(_optionalChain([content, 'optionalAccess', _22 => _22.documentType]), () => ( selectedNodeDocType));
1454
1462
  const isMarkdownContent = !activeDocType || activeDocType === "markdown";
1455
1463
  const showStickyNav = isMarkdownContent;
1456
- const stickyNavSections = _nullishCoalesce(_optionalChain([content, 'optionalAccess', _21 => _21.sections, 'optionalAccess', _22 => _22.map, 'call', _23 => _23((s) => ({ id: s.id, label: s.title }))]), () => ( []));
1464
+ const stickyNavSections = _nullishCoalesce(_optionalChain([content, 'optionalAccess', _23 => _23.sections, 'optionalAccess', _24 => _24.map, 'call', _25 => _25((s) => ({ id: s.id, label: s.title }))]), () => ( []));
1457
1465
  const isColorValue = colorPalette.background.startsWith("#") || colorPalette.background.startsWith("rgb") || colorPalette.background.startsWith("var(");
1458
1466
  const bgStyle = isColorValue ? { backgroundColor: colorPalette.background } : {};
1459
1467
  const bgClass = !isColorValue ? colorPalette.background : "";
@@ -1466,11 +1474,11 @@ function DocViewerContent({
1466
1474
  className: "max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto",
1467
1475
  style: containerBgStyle,
1468
1476
  children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-6", children: [
1469
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-col gap-4", children: typeof title === "string" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZHNL2IPKcjs.PageHeading, { children: title }) : title }),
1477
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-col gap-4", children: typeof title === "string" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJ54Z3OCRcjs.PageHeading, { children: title }) : title }),
1470
1478
  showAIChat && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1471
- _chunk2BMVBPC7cjs.DocSearchBar,
1479
+ _chunkYIGPRLQYcjs.DocSearchBar,
1472
1480
  {
1473
- placeholder: `Search ${_optionalChain([sidebarLabel, 'optionalAccess', _24 => _24.toLowerCase, 'call', _25 => _25()]) || "documents"}...`,
1481
+ placeholder: `Search ${_optionalChain([sidebarLabel, 'optionalAccess', _26 => _26.toLowerCase, 'call', _27 => _27()]) || "documents"}...`,
1474
1482
  query: docSearch.query,
1475
1483
  onQueryChange: docSearch.setQuery,
1476
1484
  results: docSearch.results,
@@ -1489,7 +1497,7 @@ function DocViewerContent({
1489
1497
  !error && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col lg:flex-row gap-6 lg:gap-10 items-start flex-1", children: [
1490
1498
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full lg:w-[320px] lg:shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "lg:sticky lg:top-20", children: isLoadingStructure ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CategorySidebarSkeleton, {}) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1491
1499
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PersistentMobileDropdown, { isLoading: false, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1492
- _chunkZ6BK4XHHcjs.MobileNavigationDropdown,
1500
+ _chunkKXCRGTRNcjs.MobileNavigationDropdown,
1493
1501
  {
1494
1502
  nodes: structure,
1495
1503
  selectedPath,
@@ -1503,7 +1511,7 @@ function DocViewerContent({
1503
1511
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PersistentSidebar, { isLoading: false, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "hidden lg:block", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
1504
1512
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em]", children: sidebarLabel }),
1505
1513
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1506
- _chunkZ6BK4XHHcjs.MultiLevelNavigation,
1514
+ _chunkKXCRGTRNcjs.MultiLevelNavigation,
1507
1515
  {
1508
1516
  nodes: structure,
1509
1517
  selectedPath,
@@ -1542,7 +1550,7 @@ function DocViewerContent({
1542
1550
  showStickyNav && content && stickyNavSections.length > 0 && !isLoadingContent && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "hidden lg:block", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "sticky top-24", children: [
1543
1551
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em] mb-4", children: "ON THIS PAGE" }),
1544
1552
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1545
- _chunkZ6BK4XHHcjs.StickySectionNav,
1553
+ _chunkKXCRGTRNcjs.StickySectionNav,
1546
1554
  {
1547
1555
  sections: stickyNavSections,
1548
1556
  activeSection,
@@ -1591,4 +1599,4 @@ function DocViewerContent({
1591
1599
 
1592
1600
 
1593
1601
  exports.PersistentFilterControls = PersistentFilterControls; exports.PersistentSearchContainer = PersistentSearchContainer; exports.PersistentSidebar = PersistentSidebar; exports.PersistentMobileDropdown = PersistentMobileDropdown; exports.UnifiedSkeleton = UnifiedSkeleton; exports.TextSkeleton = TextSkeleton; exports.InteractiveSkeleton = InteractiveSkeleton; exports.MediaSkeleton = MediaSkeleton; exports.CardSkeleton = CardSkeleton; exports.CardSkeletonGrid = CardSkeletonGrid; exports.AnnouncementBarSkeleton = AnnouncementBarSkeleton; exports.HeroSkeleton = HeroSkeleton; exports.SearchContainerSkeleton = SearchContainerSkeleton; exports.CategorySidebarSkeleton = CategorySidebarSkeleton; exports.BreadcrumbSkeleton = BreadcrumbSkeleton; exports.ResultsHeaderSkeleton = ResultsHeaderSkeleton; exports.TwoColumnLayoutSkeleton = TwoColumnLayoutSkeleton; exports.ArticleLayoutSkeleton = ArticleLayoutSkeleton; exports.VendorDetailLayoutSkeleton = VendorDetailLayoutSkeleton; exports.StatsSectionSkeleton = StatsSectionSkeleton; exports.BlogCardGridSkeleton = BlogCardGridSkeleton; exports.VendorGridSkeleton = VendorGridSkeleton; exports.SlackCommunitySkeleton = SlackCommunitySkeleton; exports.DocNavigationProvider = DocNavigationProvider; exports.useDocNavigation = useDocNavigation; exports.useDocumentTree = useDocumentTree; exports.useScrollSpy = useScrollSpy; exports.DocViewer = DocViewer;
1594
- //# sourceMappingURL=chunk-5PELVUFT.cjs.map
1602
+ //# sourceMappingURL=chunk-EYEW6PTA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-EYEW6PTA.cjs","../src/components/docs/doc-viewer.tsx","../src/components/persistent-filter-controls.tsx","../src/components/loading/page-layout-skeleton.tsx","../src/components/loading/unified-skeleton.tsx","../src/components/loading/card-skeleton.tsx","../src/components/docs/use-document-tree.ts","../src/components/docs/doc-navigation-context.tsx","../src/components/docs/use-scroll-spy.ts"],"names":["jsx","jsxs","Fragment","useRef","useCallback","pathParts","currentPath","useState","useMemo"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-EYEW6PTA.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useMemo } from \"react\"\nimport { MultiLevelNavigation, MobileNavigationDropdown } from \"../navigation/multi-level-navigation\"\nimport { PageHeading } from \"../layout/page-heading\"\nimport { PersistentSidebar, PersistentMobileDropdown } from \"../persistent-filter-controls\"\nimport { CategorySidebarSkeleton } from \"../loading/page-layout-skeleton\"\nimport { DocSearchBar, useDocSearch } from \"../shared/doc-search\"\nimport { StickySectionNav } from \"../navigation/sticky-section-nav\"\nimport { useDocumentTree } from \"./use-document-tree\"\nimport { useScrollSpy } from \"./use-scroll-spy\"\nimport { useDocNavigation } from \"./doc-navigation-context\"\nimport { findDocNodeByPath } from \"../../utils/doc-tree-nav\"\nimport type { DocContent, DocNode, DocRenderHandlers, DocSourceId } from \"../../types/doc-source\"\n\n/** Color tokens for the doc-viewer chrome. Hub-side `DocViewer` callers share\n * this constant; no need to override per source — the palette is intentionally\n * uniform across knowledge-base + data-room (both use ODS dark tokens). */\nexport const DEFAULT_DOC_VIEWER_PALETTE = {\n background: \"bg-ods-bg\",\n containerBackground: \"transparent\",\n headerText: \"text-ods-text-primary\",\n primaryText: \"text-ods-text-primary\",\n secondaryText: \"text-ods-text-secondary\",\n accent: \"var(--ods-accent)\",\n border: \"border-ods-border\",\n cardBackground: \"bg-ods-card\",\n} as const\n\nexport interface DocViewerProps {\n /**\n * Registry source id (`'openframe-docs'`, `'data-room-docs'`, …). Flowed through\n * `renderContent`'s handlers for `/api/resolve-link` POSTs.\n */\n sourceId: DocSourceId\n\n /**\n * Render the content body. The page shell owns this — it picks the markdown\n * renderer, dispatches PDF/Figma/Sheets/file branches, etc. No renderer\n * interface in between.\n */\n renderContent: (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode\n\n /**\n * Render the loading skeleton. Receives the selected node's `documentType`\n * (undefined while structure is still loading) so the caller can return a\n * markdown-shaped skeleton vs an embed-shaped skeleton.\n */\n renderSkeleton: (documentType: DocNode['documentType']) => React.ReactNode\n\n /**\n * Chat-source identifier — passed in by the page shell from server-side\n * `currentPlatform()`. Lib has no platform context; the page shell is the\n * trusted boundary that wires this. NEVER pass user input here.\n */\n chatSource: string\n\n title: string | React.ReactNode\n /** Override the default ODS palette. Optional — most callers should omit. */\n colorPalette?: typeof DEFAULT_DOC_VIEWER_PALETTE\n className?: string\n\n /** Initial doc path (URL `[...path]`). */\n docPath?: string\n\n /** Sidebar header copy (`'DOCUMENTATION'`, `'DATA ROOM'`). */\n sidebarLabel?: string\n\n /**\n * API endpoint for fetching the document tree structure. Defaults to the\n * dispatcher path `/api/docs/sources/${sourceId}/structure`. Override only\n * if hosting the viewer behind a different route.\n */\n structureEndpoint?: string\n /** Same shape as `structureEndpoint`. Defaults to `/api/docs/sources/${sourceId}/content`. */\n contentEndpoint?: string\n /** Base route path for URL navigation. */\n baseRoute: string\n\n /** Empty state copy when no doc is selected. */\n emptyStateText?: string\n\n /** Whether to render the doc-search bar (bound to chat). */\n showAIChat?: boolean\n\n /** Folder-index filename (default `'README.md'`). */\n folderIndexFile?: string\n}\n\nexport function DocViewer(props: DocViewerProps) {\n return <DocViewerContent {...props} />\n}\n\nfunction DocViewerContent({\n sourceId,\n renderContent,\n renderSkeleton,\n chatSource,\n title,\n colorPalette = DEFAULT_DOC_VIEWER_PALETTE,\n className = \"\",\n docPath,\n sidebarLabel = \"DOCUMENTATION\",\n structureEndpoint,\n contentEndpoint,\n baseRoute,\n emptyStateText,\n showAIChat = false,\n folderIndexFile,\n}: DocViewerProps) {\n // Default endpoints derived from sourceId. Hub callers omit the props in 99%\n // of cases; the override is for embed contexts where the doc-viewer sits\n // behind a non-standard route.\n const resolvedStructureEndpoint =\n structureEndpoint ?? `/api/docs/sources/${sourceId}/structure`\n const resolvedContentEndpoint =\n contentEndpoint ?? `/api/docs/sources/${sourceId}/content`\n const {\n structure,\n selectedPath,\n content,\n isLoadingStructure,\n isLoadingContent,\n error,\n expandedNodes,\n selectNode,\n toggleNode,\n navigateToDoc,\n } = useDocumentTree(\n {\n structureEndpoint: resolvedStructureEndpoint,\n contentEndpoint: resolvedContentEndpoint,\n baseRoute,\n folderIndexFile,\n },\n docPath,\n )\n\n const { activeSection, handleSectionClick } = useScrollSpy(content?.sections)\n\n const docNav = useDocNavigation()\n const docSearch = useDocSearch({\n source: chatSource,\n baseRoute,\n onNavigate: (path) => navigateToDoc(path, { fromInternalLink: true }),\n onInPageSwap: (path) => docNav.navigate(path),\n })\n\n const renderedContent = useMemo(() => {\n if (!content) return null\n return renderContent(content, {\n onInternalLinkClick: navigateToDoc,\n currentPath: selectedPath,\n sourceId,\n })\n }, [content, selectedPath, renderContent, navigateToDoc, sourceId])\n\n // Selected node's documentType drives:\n // - which skeleton the caller renders during fetch (markdown vs embed)\n // - the article max-width + sticky-nav visibility (markdown only)\n // `undefined` documentType is treated as `'markdown'` (per the DocNode\n // discriminator's documented default).\n const selectedNodeDocType =\n selectedPath && structure.length > 0\n ? findDocNodeByPath(selectedPath, structure)?.documentType\n : undefined\n // During loading, the in-flight content's type isn't known yet — fall back\n // to the selected node's type (or markdown if neither is set).\n const activeDocType = content?.documentType ?? selectedNodeDocType\n const isMarkdownContent = !activeDocType || activeDocType === 'markdown'\n const showStickyNav = isMarkdownContent\n\n const stickyNavSections =\n content?.sections?.map((s) => ({ id: s.id, label: s.title })) ?? []\n\n const isColorValue =\n colorPalette.background.startsWith('#') ||\n colorPalette.background.startsWith('rgb') ||\n colorPalette.background.startsWith('var(')\n\n const bgStyle = isColorValue ? { backgroundColor: colorPalette.background } : {}\n const bgClass = !isColorValue ? colorPalette.background : ''\n const containerBgStyle =\n colorPalette.containerBackground !== 'transparent'\n ? { backgroundColor: colorPalette.containerBackground }\n : {}\n\n const defaultEmptyText =\n structure.length > 0\n ? 'Select a document from the sidebar to view'\n : 'No documents yet. Add content from the admin panel.'\n const resolvedEmptyText = emptyStateText || defaultEmptyText\n\n return (\n <section className={`${bgClass} ${className}`} style={bgStyle}>\n <div\n className=\"max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto\"\n style={containerBgStyle}\n >\n <div className=\"flex flex-col gap-6\">\n <div className=\"flex flex-col gap-4\">\n {typeof title === 'string' ? <PageHeading>{title}</PageHeading> : title}\n </div>\n\n {showAIChat && (\n <DocSearchBar\n placeholder={`Search ${sidebarLabel?.toLowerCase() || 'documents'}...`}\n query={docSearch.query}\n onQueryChange={docSearch.setQuery}\n results={docSearch.results}\n isLoading={docSearch.isLoading}\n onResultSelect={docSearch.handleResultSelect}\n showDropdown={docSearch.keepDropdownOpen}\n />\n )}\n\n {error && (\n <div className=\"flex justify-center\">\n <div className=\"rounded-lg border bg-ods-card p-8 text-center max-w-md border-ods-border\">\n <h2 className=\"text-xl font-semibold text-ods-text-primary\">\n Error Loading Documents\n </h2>\n <p className=\"mt-2 text-ods-text-secondary\">{error}. Please try again later.</p>\n </div>\n </div>\n )}\n\n {!error && (\n <div className=\"flex flex-col lg:flex-row gap-6 lg:gap-10 items-start flex-1\">\n <div className=\"w-full lg:w-[320px] lg:shrink-0\">\n <div className=\"lg:sticky lg:top-20\">\n {isLoadingStructure ? (\n <CategorySidebarSkeleton />\n ) : (\n <>\n <PersistentMobileDropdown isLoading={false}>\n <MobileNavigationDropdown\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </PersistentMobileDropdown>\n\n <PersistentSidebar isLoading={false}>\n <div className=\"hidden lg:block\">\n <div className=\"space-y-4\">\n <h3 className=\"text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em]\">\n {sidebarLabel}\n </h3>\n <MultiLevelNavigation\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </div>\n </div>\n </PersistentSidebar>\n </>\n )}\n </div>\n </div>\n\n <div className=\"flex-1 min-w-0 w-full\">\n <div\n className={`grid grid-cols-1 ${\n (showStickyNav && stickyNavSections.length > 0) ||\n isLoadingContent ||\n isLoadingStructure\n ? 'lg:grid-cols-[1fr_280px]'\n : ''\n } gap-8`}\n >\n <div className={`w-full ${isMarkdownContent ? 'max-w-4xl mx-auto' : ''}`}>\n <article className=\"space-y-2\">\n {(isLoadingContent || isLoadingStructure) ? (\n renderSkeleton(selectedNodeDocType)\n ) : !content ? (\n <div className=\"text-center py-16\">\n <p className=\"text-xl text-ods-text-secondary\">{resolvedEmptyText}</p>\n </div>\n ) : (\n renderedContent\n )}\n </article>\n </div>\n\n {(isLoadingContent || isLoadingStructure) && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <div className=\"h-[14px] w-28 bg-ods-border rounded animate-pulse mb-5\" />\n <div className=\"space-y-0\">\n {[130, 170, 190, 220, 110, 200, 80, 100, 120, 140, 90].map((w, i) => (\n <div\n key={i}\n className={`py-[13px] pl-3 border-l-2 ${\n i === 0 ? 'border-ods-accent' : 'border-transparent'\n }`}\n >\n <div\n className=\"h-[13px] bg-ods-border rounded animate-pulse\"\n style={{ width: w }}\n />\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {showStickyNav &&\n content &&\n stickyNavSections.length > 0 &&\n !isLoadingContent && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <h3 className=\"text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em] mb-4\">\n ON THIS PAGE\n </h3>\n <StickySectionNav\n sections={stickyNavSections}\n activeSection={activeSection}\n onSectionClick={handleSectionClick}\n ribbonPosition=\"left\"\n ribbonColor=\"var(--ods-accent)\"\n />\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n </section>\n )\n}\n","\"use client\"\n\nimport { ReactNode } from \"react\"\nimport { cn } from \"../utils/cn\"\n\ninterface PersistentFilterControlsProps {\n /**\n * Whether the filters are currently in a loading state\n */\n isLoading: boolean\n /**\n * The filter control components (search, sidebar, mobile dropdown)\n */\n children: ReactNode\n /**\n * Additional CSS classes\n */\n className?: string\n /**\n * Disabled opacity (0-1)\n */\n disabledOpacity?: number\n /**\n * Transition duration in milliseconds\n */\n transitionDuration?: number\n /**\n * Whether to prevent pointer events during loading\n */\n preventInteraction?: boolean\n}\n\n/**\n * PersistentFilterControls\n * \n * A wrapper component that keeps filter controls visible during loading states\n * but provides visual feedback that they are temporarily disabled.\n * \n * Features:\n * - Reduces opacity and disables pointer events during loading\n * - Maintains layout and accessibility during loading states\n * - Provides smooth transitions between enabled/disabled states\n * - Preserves keyboard navigation and screen reader functionality\n * - Applies consistent disabled styling across all filter types\n * \n * Usage:\n * ```tsx\n * <PersistentFilterControls isLoading={isLoadingVendors}>\n * <SearchContainer />\n * <CategoryFilterSidebar />\n * <MobileDropdown />\n * </PersistentFilterControls>\n * ```\n */\nexport function PersistentFilterControls({\n isLoading,\n children,\n className,\n disabledOpacity = 0.6,\n transitionDuration = 300,\n preventInteraction = true,\n}: PersistentFilterControlsProps) {\n return (\n <div \n className={cn(\n \"relative transition-all ease-in-out\",\n isLoading && preventInteraction && \"pointer-events-none\",\n className\n )}\n style={{\n opacity: isLoading ? disabledOpacity : 1,\n transitionDuration: `${transitionDuration}ms`\n }}\n role=\"region\"\n aria-label=\"Filter controls\"\n aria-busy={isLoading}\n data-loading={isLoading}\n >\n {/* Loading indicator overlay for screen readers */}\n {isLoading && (\n <div \n className=\"sr-only\" \n role=\"status\" \n aria-live=\"polite\"\n >\n Filters temporarily disabled while loading content\n </div>\n )}\n\n {/* Filter controls with disabled state styling */}\n <div \n className={cn(\n \"transition-all ease-in-out\",\n isLoading && \"cursor-not-allowed\"\n )}\n style={{\n transitionDuration: `${transitionDuration}ms`\n }}\n >\n {children}\n </div>\n\n {/* Visual loading indicator */}\n {isLoading && (\n <div \n className=\"absolute top-2 right-2 z-10\"\n role=\"status\"\n aria-label=\"Loading filters\"\n >\n <div className=\"w-4 h-4 border-2 border-ods-border border-t-[#FFC008] rounded-full animate-spin\" />\n </div>\n )}\n </div>\n )\n}\n\n/**\n * Enhanced SearchContainer wrapper that integrates with persistent loading\n */\ninterface PersistentSearchContainerProps {\n isLoading: boolean\n children: ReactNode\n className?: string\n}\n\nexport function PersistentSearchContainer({\n isLoading,\n children,\n className\n}: PersistentSearchContainerProps) {\n return (\n <PersistentFilterControls \n isLoading={isLoading}\n preventInteraction={false} // Keep search interactive during loading\n className={className}\n >\n {children}\n </PersistentFilterControls>\n )\n}\n\n/**\n * Enhanced sidebar wrapper for desktop filter controls\n */\ninterface PersistentSidebarProps {\n isLoading: boolean\n children: ReactNode\n className?: string\n}\n\nexport function PersistentSidebar({\n isLoading,\n children,\n className\n}: PersistentSidebarProps) {\n return (\n <PersistentFilterControls \n isLoading={isLoading}\n disabledOpacity={0.5}\n className={cn(\"lg:sticky lg:top-20\", className)}\n >\n {children}\n </PersistentFilterControls>\n )\n}\n\n/**\n * Enhanced mobile dropdown wrapper\n */\ninterface PersistentMobileDropdownProps {\n isLoading: boolean\n children: ReactNode\n className?: string\n}\n\nexport function PersistentMobileDropdown({\n isLoading,\n children,\n className\n}: PersistentMobileDropdownProps) {\n return (\n <PersistentFilterControls \n isLoading={isLoading}\n disabledOpacity={0.7}\n className={cn(\"lg:hidden\", className)}\n >\n {children}\n </PersistentFilterControls>\n )\n} ","import type React from \"react\"\nimport { cn } from \"../../utils/cn\"\nimport { UnifiedSkeleton, TextSkeleton, MediaSkeleton, InteractiveSkeleton } from \"./unified-skeleton\"\nimport { CardSkeletonGrid } from \"./card-skeleton\"\n\ninterface PageLayoutSkeletonProps {\n className?: string\n}\n\n/**\n * Announcement bar skeleton that matches the AnnouncementBar component\n */\nexport function AnnouncementBarSkeleton() {\n return (\n <div className=\"bg-[#2A2A2A] relative w-full animate-pulse\">\n <div className=\"flex flex-row items-center relative w-full\">\n <div className=\"box-border flex flex-row gap-4 md:gap-6 items-center justify-start pl-4 md:pl-6 pr-12 md:pr-16 py-3 md:py-4 relative w-full\">\n {/* Logo skeleton */}\n <div className=\"relative shrink-0 w-6 h-6 md:w-8 md:h-8 bg-ods-border rounded\"></div>\n \n {/* Text content skeleton */}\n <div className=\"flex-1 min-w-0 space-y-1 md:space-y-2\">\n <div className=\"h-[14px] md:h-[18px] bg-ods-border rounded w-3/4 max-w-md\"></div>\n <div className=\"h-[12px] md:h-[18px] bg-ods-border rounded w-full max-w-lg hidden md:block\"></div>\n </div>\n \n {/* Close button skeleton */}\n <div className=\"absolute right-2 top-2 w-6 h-6 bg-ods-border rounded\"></div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Header skeleton that matches the ClientOnlyHeader placeholder but with proper animations\n */\nexport function HeaderSkeleton() {\n return (\n <header className=\"sticky top-0 z-40 w-full flex items-center justify-between border-b border-ods-border bg-ods-card backdrop-blur-sm bg-ods-card/95 px-4 md:px-[80px] py-3 md:py-[12px] animate-pulse\">\n {/* Left: Logo skeleton */}\n <div className=\"flex items-center justify-start flex-1 min-w-0\">\n <div className=\"w-[110px] h-[26px] md:w-[137px] md:h-8 bg-[#2A2A2A] rounded\"></div>\n </div>\n\n {/* Center: Navigation skeleton - hidden on mobile, visible on desktop */}\n <nav className=\"hidden md:flex flex-1 basis-1/3 justify-center items-center gap-2 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <InteractiveSkeleton.Button className=\"w-24 h-10\" />\n <InteractiveSkeleton.Button className=\"w-24 h-10\" />\n <InteractiveSkeleton.Button className=\"w-24 h-10\" />\n </div>\n </nav>\n\n {/* Right: Actions skeleton */}\n <div className=\"flex items-center justify-end gap-4 flex-1 min-w-0\">\n {/* Mobile: Show hamburger skeleton */}\n <div className=\"md:hidden\">\n <InteractiveSkeleton.Button className=\"h-10 w-10\" />\n </div>\n \n {/* Desktop: Show action buttons skeletons */}\n <div className=\"hidden md:flex items-center gap-4\">\n <InteractiveSkeleton.Button className=\"w-10 h-10\" />\n <InteractiveSkeleton.Button className=\"w-32 h-10\" />\n <InteractiveSkeleton.Button className=\"w-20 h-10\" />\n </div>\n </div>\n </header>\n )\n}\n\n/**\n * Hero section skeleton for static content areas\n */\nexport function HeroSkeleton() {\n return (\n <section\n className=\"w-full flex flex-col items-center justify-center py-12 px-4 md:py-20 md:px-8 text-center animate-pulse\"\n style={{\n background: 'radial-gradient(circle at 50% 0%, #242323 0%, #1A1A1A 100%)'\n }}\n >\n {/* Title skeleton */}\n <TextSkeleton.Heading className=\"h-12 md:h-20 lg:h-24 mb-4 md:mb-6 max-w-4xl w-full\" />\n \n {/* Subtitle skeleton */}\n <div className=\"space-y-3 mb-8 md:mb-10 max-w-4xl w-full px-2\">\n <TextSkeleton.Body className=\"h-5 md:h-7\" />\n <TextSkeleton.Body className=\"h-5 md:h-7 w-3/4 mx-auto\" />\n </div>\n \n {/* CTA Button skeleton */}\n <InteractiveSkeleton.Button className=\"w-full md:w-64 h-12\" />\n </section>\n )\n}\n\n/**\n * Search container skeleton with filters\n */\nexport function SearchContainerSkeleton({ \n className,\n showFilters = true \n}: PageLayoutSkeletonProps & { showFilters?: boolean }) {\n return (\n <div className={cn(\"space-y-4\", className || \"\")}>\n {/* Search input and button */}\n <div className=\"flex gap-2 md:gap-4\">\n <InteractiveSkeleton.Input className=\"flex-1\" />\n <InteractiveSkeleton.Button className=\"w-32 md:w-40\" />\n </div>\n\n {/* Filter chips */}\n {showFilters && (\n <div className=\"flex gap-2 flex-wrap\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-24\" />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-20\" />\n </div>\n )}\n </div>\n )\n}\n\n/**\n * Category sidebar skeleton for filtering pages\n */\nexport function CategorySidebarSkeleton({ className }: PageLayoutSkeletonProps) {\n // Matches the actual MultiLevelNavigation sidebar with folder/file icons, README badges, and chevrons\n const items = [\n { type: 'file', width: 'w-20', hasBadge: false },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-28', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'file', width: 'w-28' },\n { type: 'file', width: 'w-36' },\n ]\n\n return (\n <div className={cn(\"w-full lg:w-[320px]\", className)}>\n {/* DATA ROOM label */}\n <UnifiedSkeleton className=\"h-[14px] w-24 rounded mb-4\" />\n\n {/* Navigation items — each has card background matching actual MultiLevelNavigation */}\n <div className=\"space-y-1.5\">\n {items.map((item, index) => (\n <div\n key={index}\n className={cn(\n \"flex items-center justify-between py-4 px-4 rounded-lg border border-ods-border min-h-[50px]\",\n index === 0 ? \"bg-ods-accent/20\" : \"bg-ods-card\"\n )}\n >\n <div className=\"flex items-center gap-2.5\">\n <UnifiedSkeleton className=\"w-4 h-4 shrink-0 rounded\" />\n <UnifiedSkeleton className={`h-[14px] ${item.width} rounded`} />\n </div>\n <div className=\"flex items-center gap-2 h-[18px]\">\n {item.hasBadge && <UnifiedSkeleton className=\"h-[18px] w-14 rounded\" />}\n {item.type === 'folder' && <UnifiedSkeleton className=\"w-4 h-4 rounded\" />}\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n/**\n * Breadcrumb navigation skeleton\n */\nexport function BreadcrumbSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn(\"flex items-center space-x-1 mb-6\", className)}>\n <TextSkeleton.Caption className=\"w-16\" />\n <UnifiedSkeleton variant=\"default\" className=\"w-4 h-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n <UnifiedSkeleton variant=\"default\" className=\"w-4 h-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-32\" />\n <UnifiedSkeleton variant=\"default\" className=\"w-4 h-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n )\n}\n\n/**\n * Results header skeleton with count and sorting\n */\nexport function ResultsHeaderSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn(\"flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6\", className)}>\n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-48\" />\n {/* <TextSkeleton.Caption className=\"w-32\" /> */}\n </div>\n </div>\n )\n}\n\n/**\n * Two-column layout skeleton (sidebar + main content)\n */\nexport function TwoColumnLayoutSkeleton({\n className,\n sidebarContent,\n mainContent,\n sidebarPosition = 'left'\n}: PageLayoutSkeletonProps & {\n sidebarContent?: React.ReactNode\n mainContent?: React.ReactNode\n sidebarPosition?: 'left' | 'right'\n}) {\n const sidebar = sidebarContent || <CategorySidebarSkeleton />\n const main = mainContent || <CardSkeletonGrid count={6} />\n\n return (\n <div className={cn(\n \"grid grid-cols-1 lg:grid-cols-[320px_1fr] gap-6 lg:gap-8\",\n sidebarPosition === 'right' && \"lg:grid-cols-[1fr_320px]\",\n className\n )}>\n {sidebarPosition === 'left' ? (\n <>\n <aside className=\"order-2 lg:order-1\">{sidebar}</aside>\n <main className=\"order-1 lg:order-2\">{main}</main>\n </>\n ) : (\n <>\n <main className=\"order-1\">{main}</main>\n <aside className=\"order-2\">{sidebar}</aside>\n </>\n )}\n </div>\n )\n}\n\n/**\n * Article/blog post layout skeleton\n */\nexport function ArticleLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <article className={cn(\"max-w-4xl mx-auto\", className)}>\n {/* Article header */}\n <header className=\"mb-8 md:mb-12 space-y-4 md:space-y-6\">\n {/* Category/tags */}\n <div className=\"flex gap-2\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n </div>\n \n {/* Title */}\n <div className=\"space-y-3\">\n <TextSkeleton.Heading className=\"w-full\" />\n <TextSkeleton.Heading className=\"w-3/4\" />\n </div>\n \n {/* Metadata */}\n <div className=\"flex items-center gap-4 pt-4 border-t border-ods-divider\">\n <div className=\"flex items-center gap-2\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n <TextSkeleton.Caption className=\"w-20\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </header>\n\n {/* Featured image */}\n <div className=\"mb-8 md:mb-12\">\n <MediaSkeleton.CardImage className=\"w-full h-64 md:h-96\" />\n </div>\n\n {/* Article content */}\n <div className=\"prose prose-invert max-w-none space-y-6\">\n {/* Paragraphs */}\n {Array.from({ length: 12 }).map((_, index) => (\n <div key={index} className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n ))}\n \n {/* Subheading in content */}\n <div className=\"py-4\">\n <TextSkeleton.Subheading className=\"w-2/3 mb-4\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-4/5\" />\n </div>\n </div>\n </div>\n </article>\n )\n}\n\n/**\n * Vendor Detail Layout Skeleton - Complete vendor detail page structure\n * Matches the refactored vendor detail page with proper platform colors and responsive layout\n */\nexport function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <main className={cn(\"bg-ods-bg min-h-screen\", className)}>\n <div className=\"max-w-[1920px] mx-auto px-6 md:px-20 py-6 md:py-10\">\n {/* Breadcrumb */}\n <BreadcrumbSkeleton className=\"mb-6\" />\n\n {/* Main Layout Container */}\n <div className=\"flex flex-col lg:flex-row lg:gap-10\">\n {/* Left Content Area */}\n <div className=\"flex-1 min-w-0\">\n \n {/* Vendor Hero Section */}\n <div className=\"mb-10\">\n {/* Header - Logo and Title Side by Side */}\n <div className=\"flex gap-6 mb-6\">\n <div className=\"w-20 h-20 bg-ods-card border border-ods-border rounded-lg animate-pulse flex-shrink-0\"></div>\n \n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-col gap-2\">\n {/* Large title skeleton */}\n <div className=\"h-12 md:h-16 lg:h-20 bg-[#2A2A2A] rounded animate-pulse w-80 max-w-full\"></div>\n {/* Category text */}\n <div className=\"h-5 md:h-6 bg-[#2A2A2A] rounded animate-pulse w-32\"></div>\n </div>\n \n {/* Pricing tags */}\n <div className=\"flex items-center gap-2\">\n <div className=\"h-8 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n <div className=\"h-8 bg-[#2A2A2A] rounded animate-pulse w-16\"></div>\n </div>\n </div>\n </div>\n\n {/* Vendor Image Display Skeleton */}\n <div className=\"w-full h-[300px] md:h-[400px] lg:h-[500px] bg-ods-card border border-ods-border rounded-lg animate-pulse mb-2\"></div>\n <div className=\"text-center\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-24 mx-auto\"></div>\n </div>\n </div>\n\n {/* Mobile Sidebar - Show on mobile only, positioned after title */}\n <div className=\"lg:hidden mb-10\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n \n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n </div>\n \n {/* GitHub Score Section */}\n <div className=\"border border-ods-border rounded-lg overflow-hidden\">\n {/* Header */}\n <div className=\"bg-ods-card p-4 flex items-center gap-3\">\n <div className=\"w-8 h-8 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-12\"></div>\n </div>\n </div>\n \n {/* Stats */}\n <div className=\"p-4 space-y-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"w-6 h-6 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-12\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded animate-pulse w-8\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n \n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n </div>\n </div>\n </div>\n\n {/* Alternatives Container */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-48\"></div>\n \n {/* Open Source Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-48\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-10 bg-ods-card border border-ods-border rounded-lg animate-pulse w-24\"></div>\n ))}\n </div>\n </div>\n\n {/* Commercial Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-52\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-10 bg-ods-card border border-ods-border rounded-lg animate-pulse w-28\"></div>\n ))}\n </div>\n </div>\n </div>\n\n {/* About Section */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-64\"></div>\n \n <div className=\"bg-ods-card border border-ods-border rounded-lg p-6 md:p-8\">\n <div className=\"space-y-4\">\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-5/6\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-4/5\"></div>\n </div>\n </div>\n </div>\n\n {/* Key Features Section */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-72\"></div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-4 flex gap-4\">\n <div className=\"w-12 h-12 bg-[#2A2A2A] rounded flex-shrink-0 animate-pulse\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-3/4\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Pros and Cons Section */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-80\"></div>\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Pros Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-16\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3\">\n <div className=\"w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Cons Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-16\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3\">\n <div className=\"w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Alternatives Section */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-72\"></div>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-6 space-y-4\">\n {/* Header */}\n <div className=\"flex items-start gap-4\">\n <div className=\"w-16 h-16 bg-ods-bg border border-ods-border rounded-lg animate-pulse flex-shrink-0\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-3/4\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-1/2\"></div>\n </div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n </div>\n \n {/* Description */}\n <div className=\"space-y-2\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-5/6\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-4/5\"></div>\n </div>\n \n {/* Footer */}\n <div className=\"flex items-center justify-between pt-2 border-t border-ods-border\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-16\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Show All Button */}\n <div className=\"h-12 bg-[#2A2A2A] rounded-md animate-pulse\"></div>\n </div>\n\n {/* Comments Section */}\n <div className=\"flex flex-col gap-6 mb-20\">\n <div className=\"h-8 md:h-10 bg-[#2A2A2A] rounded animate-pulse w-64\"></div>\n \n {/* Comment Form Skeleton */}\n <div className=\"space-y-3\">\n <div className=\"flex gap-3\">\n <div className=\"flex-1 bg-ods-bg border border-ods-border rounded-lg overflow-hidden\">\n {/* Title Section */}\n <div className=\"border-b border-ods-border p-3\">\n <div className=\"h-3 bg-[#2A2A2A] rounded animate-pulse w-8 mb-2\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-full\"></div>\n </div>\n \n {/* Description Section */}\n <div className=\"p-3\">\n <div className=\"h-3 bg-[#2A2A2A] rounded animate-pulse w-16 mb-2\"></div>\n <div className=\"space-y-2\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-full\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-3/4\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-1/2\"></div>\n </div>\n </div>\n </div>\n \n {/* Send Button */}\n <div className=\"flex items-stretch\">\n <div className=\"h-[120px] w-20 bg-[#2A2A2A] rounded-lg animate-pulse\"></div>\n </div>\n </div>\n </div>\n \n {/* Sample Comment Cards */}\n <div className=\"space-y-4\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-4\">\n {/* Comment Header */}\n <div className=\"flex flex-col gap-3 mb-3 min-[420px]:flex-row min-[420px]:items-center min-[420px]:justify-between min-[420px]:gap-2\">\n <div className=\"flex items-center gap-2\">\n {/* User Info */}\n <div className=\"flex items-center gap-2 bg-ods-card border border-ods-border rounded-lg py-2 px-3\">\n <div className=\"w-8 h-8 bg-[#2A2A2A] rounded-lg animate-pulse\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n </div>\n \n {/* Timestamp */}\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-12\"></div>\n </div>\n </div>\n \n {/* Comment Content */}\n <div className=\"space-y-2\">\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-2/3\"></div>\n <div className=\"space-y-1\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-full\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-4/5\"></div>\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-3/5\"></div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Right Sidebar - Desktop Only */}\n <div className=\"hidden lg:block w-[290px] flex-shrink-0\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n \n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n </div>\n \n {/* GitHub Score Section */}\n <div className=\"border border-ods-border rounded-lg overflow-hidden\">\n {/* Header */}\n <div className=\"bg-ods-card p-4 flex items-center gap-3\">\n <div className=\"w-8 h-8 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-20\"></div>\n <div className=\"h-6 bg-[#2A2A2A] rounded animate-pulse w-12\"></div>\n </div>\n </div>\n \n {/* Stats */}\n <div className=\"p-4 space-y-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"w-6 h-6 bg-[#2A2A2A] rounded animate-pulse\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 bg-[#2A2A2A] rounded animate-pulse w-12\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded animate-pulse w-8\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n \n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n <div className=\"h-12 bg-ods-card border border-ods-border rounded-lg animate-pulse\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </main>\n )\n}\n\n/**\n * Stats/features section skeleton for homepage\n */\nexport function StatsSectionSkeleton({ \n className,\n columns = 3 \n}: PageLayoutSkeletonProps & { columns?: number }) {\n return (\n <div className={cn(\n \"grid gap-6 mb-12 md:mb-16\",\n columns === 3 && \"grid-cols-1 md:grid-cols-3\",\n columns === 4 && \"grid-cols-1 md:grid-cols-2 lg:grid-cols-4\",\n className\n )}>\n {Array.from({ length: columns }).map((_, i) => (\n <div key={i} className=\"bg-ods-card border border-ods-border rounded-lg p-6\">\n <div className=\"space-y-4\">\n <MediaSkeleton.Icon size=\"lg\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n}\n\n\n/**\n * Blog Card Grid Skeleton - Always displays exactly 4 blog card skeletons\n * Used for consistent blog page layout with predictable height\n */\nexport function BlogCardGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn(\"space-y-6\", className)}>\n <CardSkeletonGrid \n count={4}\n variant=\"blog\"\n className=\"grid-cols-1 md:grid-cols-2\"\n />\n </div>\n )\n}\n\n/**\n * Vendor Grid Skeleton - Always displays exactly 12 vendor card skeletons\n * Used for consistent vendor page layout with predictable height\n */\nexport function VendorGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn(\"space-y-6\", className)}>\n <CardSkeletonGrid \n count={12}\n variant=\"vendor\"\n className=\"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\"\n />\n </div>\n )\n}\n\n/**\n * Slack Community Section Skeleton\n * Matches SlackCommunity component structure with title, channel list, and chat interface\n */\nexport function SlackCommunitySkeleton() {\n return (\n <section \n className=\"w-full bg-ods-bg px-4 md:px-20 lg:px-20 py-12 md:py-20\"\n aria-label=\"Slack Community Loading\"\n >\n {/* Frame 651 Container */}\n <div className=\"w-full flex flex-col gap-4 md:gap-6\">\n \n {/* Title Skeleton */}\n <div className=\"w-full\">\n <div className=\"h-8 md:h-12 lg:h-14 bg-[#2A2A2A] rounded-lg animate-pulse max-w-md\"></div>\n </div>\n\n {/* Content Area - Channel List + Chat Interface */}\n <div className=\"w-full flex flex-col lg:flex-row lg:justify-end lg:items-start gap-4 md:gap-6 min-w-0\">\n \n {/* Channel List Skeleton */}\n <div className=\"w-full lg:w-[290px] lg:max-w-[290px] bg-ods-bg border border-ods-border rounded flex flex-col flex-shrink-0 overflow-hidden animate-pulse\">\n <div className=\"p-4 space-y-4\">\n {/* Header */}\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"h-4 bg-[#2A2A2A] rounded w-24\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-16\"></div>\n </div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-32\"></div>\n </div>\n\n {/* Channels */}\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-3 bg-[#2A2A2A] rounded w-16 mb-2\"></div>\n {Array.from({ length: 5 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-2 px-2 py-3 min-h-[48px]\">\n <div className=\"w-4 h-4 bg-[#2A2A2A] rounded\"></div>\n <div className=\"flex-1\">\n <div className=\"h-3 bg-[#2A2A2A] rounded w-24 mb-1\"></div>\n <div className=\"h-2 bg-[#2A2A2A] rounded w-16\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Stats */}\n <div className=\"border-t border-ods-border pt-3 mt-2\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between\">\n <div className=\"h-3 bg-[#2A2A2A] rounded w-16\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-8\"></div>\n </div>\n <div className=\"flex justify-between\">\n <div className=\"h-3 bg-[#2A2A2A] rounded w-20\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-8\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Chat Interface Skeleton */}\n <div className=\"flex-1 min-h-[450px] h-[450px] md:min-h-[500px] md:h-[500px] lg:min-h-[600px] lg:h-[600px] bg-ods-card border border-ods-border rounded-lg flex flex-col min-w-0 animate-pulse\">\n \n {/* Header */}\n <div className=\"flex items-center justify-between p-4 md:p-6 border-b border-ods-border bg-ods-bg h-[56px] md:h-[60px] flex-shrink-0\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-3 md:h-4 bg-[#2A2A2A] rounded w-3 md:w-4\"></div>\n <div className=\"h-3 md:h-4 bg-[#2A2A2A] rounded w-24 md:w-32\"></div>\n </div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-12 md:w-16\"></div>\n </div>\n\n {/* Messages */}\n <div className=\"flex-1 bg-ods-bg p-4 md:p-6 space-y-3 md:space-y-4 overflow-hidden min-h-[280px] md:min-h-[320px] lg:min-h-[420px]\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"flex gap-2 md:gap-3\">\n <div className=\"w-10 h-10 md:w-12 md:h-12 bg-[#2A2A2A] rounded-lg flex-shrink-0\"></div>\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2 mb-1\">\n <div className=\"h-3 bg-[#2A2A2A] rounded w-16 md:w-20\"></div>\n <div className=\"h-3 bg-[#2A2A2A] rounded w-12 md:w-16\"></div>\n </div>\n <div className=\"h-3 md:h-4 bg-[#2A2A2A] rounded w-full\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Input Area */}\n <div className=\"p-4 md:p-6 border-t border-ods-border bg-ods-card h-[72px] md:h-[80px] flex-shrink-0\">\n <div className=\"flex items-end gap-3 justify-center md:justify-end\">\n <div className=\"h-10 md:h-12 bg-[#2A2A2A] rounded-lg w-28 md:w-32\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n} ","import type React from \"react\"\nimport { cn } from \"../../utils/cn\"\n\ninterface UnifiedSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n className?: string\n variant?: 'default' | 'text' | 'circular' | 'rectangular'\n width?: string | number\n height?: string | number\n /**\n * Disable animation for users who prefer reduced motion\n */\n animate?: boolean\n /**\n * Accessibility label for screen readers\n */\n 'aria-label'?: string\n}\n\n/**\n * UnifiedSkeleton Component\n * \n * Base skeleton component with consistent styling across the application.\n * \n * Design Specifications:\n * - Color: #2A2A2A base with #3A3A3A pulse highlight\n * - Animation: 1.5s pulse duration, ease-in-out timing\n * - Responsive: Mobile-first approach with proper scaling\n * - Accessibility: ARIA labels and reduced motion support\n * \n * @param variant - Shape variant: default (rounded), text (rounded-sm), circular, rectangular\n * @param width - Custom width (defaults to full width)\n * @param height - Custom height (defaults to auto based on variant)\n * @param animate - Enable/disable animation (respects prefers-reduced-motion)\n * @param aria-label - Accessibility label for screen readers\n */\nexport function UnifiedSkeleton({\n className,\n variant = 'default',\n width,\n height,\n animate = true,\n 'aria-label': ariaLabel,\n ...props\n}: UnifiedSkeletonProps) {\n const baseClasses = \"bg-[#2A2A2A]\"\n \n const variantClasses = {\n default: \"rounded-md\",\n text: \"rounded-sm\",\n circular: \"rounded-full\",\n rectangular: \"rounded-none\"\n }\n \n const defaultHeights = {\n default: \"h-4\",\n text: \"h-4\", \n circular: \"h-12 w-12\",\n rectangular: \"h-4\"\n }\n \n const animationClasses = animate \n ? \"animate-pulse motion-reduce:animate-none\" \n : \"\"\n \n const style: React.CSSProperties = {\n width: width,\n height: height,\n }\n \n return (\n <div\n className={cn(\n baseClasses,\n variantClasses[variant],\n !height && !style.height && defaultHeights[variant],\n !width && !style.width && variant !== 'circular' && \"w-full\",\n animationClasses,\n className\n )}\n style={style}\n role=\"status\"\n aria-label={ariaLabel || \"Loading content\"}\n {...props}\n />\n )\n}\n\n/**\n * Text skeleton variants for consistent typography loading\n */\nexport const TextSkeleton = {\n /**\n * Large heading skeleton (h1, h2)\n */\n Heading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"text\" \n className={cn(\"h-8 md:h-10 lg:h-12\", className)} \n aria-label=\"Loading heading\"\n {...props} \n />\n ),\n \n /**\n * Medium heading skeleton (h3, h4)\n */\n Subheading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"text\" \n className={cn(\"h-6 md:h-7 lg:h-8\", className)} \n aria-label=\"Loading subheading\"\n {...props} \n />\n ),\n \n /**\n * Body text skeleton\n */\n Body: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"text\" \n className={cn(\"h-4 md:h-5\", className)} \n aria-label=\"Loading text\"\n {...props} \n />\n ),\n \n /**\n * Small text skeleton (captions, metadata)\n */\n Caption: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"text\" \n className={cn(\"h-3 md:h-4\", className)} \n aria-label=\"Loading caption\"\n {...props} \n />\n ),\n}\n\n/**\n * Interactive element skeletons\n */\nexport const InteractiveSkeleton = {\n /**\n * Button skeleton with proper touch targets\n */\n Button: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"default\" \n className={cn(\"h-10 md:h-12 w-32 md:w-40\", className)} \n aria-label=\"Loading button\"\n {...props} \n />\n ),\n \n /**\n * Input field skeleton\n */\n Input: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"default\" \n className={cn(\"h-10 md:h-12 w-full\", className)} \n aria-label=\"Loading input field\"\n {...props} \n />\n ),\n \n /**\n * Filter chip skeleton\n */\n Chip: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton \n variant=\"default\" \n className={cn(\"h-8 w-20 rounded-full\", className)} \n aria-label=\"Loading filter\"\n {...props} \n />\n ),\n}\n\n/**\n * Media skeletons for images and icons\n */\nexport const MediaSkeleton = {\n /**\n * Avatar/profile image skeleton\n */\n Avatar: ({ size = 'md', className, ...props }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: \"h-8 w-8\",\n md: \"h-12 w-12\", \n lg: \"h-16 w-16\"\n }\n \n return (\n <UnifiedSkeleton \n variant=\"circular\" \n className={cn(sizeClasses[size], className)} \n aria-label=\"Loading profile image\"\n {...props} \n />\n )\n },\n \n /**\n * Card image skeleton (1200×630 OG aspect — matches entity-cards default\n * so loading state never causes a height shift when card data arrives)\n */\n CardImage: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn(\"aspect-[1200/630] w-full\", className)}\n aria-label=\"Loading image\"\n {...props}\n />\n ),\n \n /**\n * Icon skeleton\n */\n Icon: ({ size = 'md', className, ...props }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: \"h-4 w-4\",\n md: \"h-6 w-6\",\n lg: \"h-8 w-8\"\n }\n \n return (\n <UnifiedSkeleton \n variant=\"default\" \n className={cn(sizeClasses[size], className)} \n aria-label=\"Loading icon\"\n {...props} \n />\n )\n },\n} ","import type React from \"react\"\nimport { cn } from \"../../utils/cn\"\nimport { UnifiedSkeleton, TextSkeleton, MediaSkeleton, InteractiveSkeleton } from \"./unified-skeleton\"\n\ninterface CardSkeletonProps {\n className?: string\n /**\n * Card layout variant\n */\n variant?: 'vendor' | 'blog' | 'category' | 'alternative'\n /**\n * Show action buttons area\n */\n showActions?: boolean\n /**\n * Show metadata footer\n */\n showMetadata?: boolean\n /** Optional tailwind classes to override the card container background & border */\n containerClassName?: string\n}\n \n/**\n * Unified card skeleton component for consistent card loading states\n * \n * Supports different card types used across the application:\n * - vendor: Vendor cards with logo, title, description, and actions\n * - blog: Blog post cards with image, title, summary, and metadata \n * - category: Category cards with icon, title, and description\n * - alternative: Alternative vendor cards in comparison lists\n */\nexport function CardSkeleton({\n className,\n containerClassName,\n variant = 'vendor',\n showActions = true,\n showMetadata = true,\n ...props\n}: CardSkeletonProps) {\n const cardContent = {\n vendor: <VendorCardContent showActions={showActions} showMetadata={showMetadata} />,\n blog: <BlogCardContent showActions={showActions} showMetadata={showMetadata} />,\n category: <CategoryCardContent />,\n alternative: <AlternativeCardContent showActions={showActions} />\n }\n\n return (\n <div \n className={cn(\n containerClassName || \"bg-ods-card border border-ods-border\",\n \"rounded-lg overflow-hidden\",\n // Flex layouts for certain variants\n variant === 'blog' && \"h-full flex flex-col\",\n variant === 'vendor' && \"h-full flex flex-col\",\n className\n )}\n role=\"status\"\n aria-label={`Loading ${variant} card`}\n {...props}\n >\n {cardContent[variant]}\n </div>\n )\n}\n\n/**\n * Vendor card skeleton content - matches exact VendorCard structure\n */\nfunction VendorCardContent({ showActions, showMetadata }: { showActions: boolean; showMetadata: boolean }) {\n return (\n <div className=\"p-4 gap-3 flex flex-col\">\n {/* Header Section - Row layout matching actual VendorCard */}\n <div className=\"flex items-start gap-3 w-full\">\n {/* Logo Frame - 60px width fixed, matching actual structure */}\n <div className=\"w-[60px] h-[60px] bg-ods-bg border border-ods-border rounded-lg p-2 flex items-center justify-center flex-shrink-0\">\n <MediaSkeleton.Avatar size=\"sm\" className=\"w-11 h-11\" />\n </div>\n \n {/* Text Container - Column layout, matching actual structure */}\n <div className=\"flex-1 flex flex-col justify-center py-2 min-w-0 space-y-1\">\n {/* Title - Single line with proper width */}\n <TextSkeleton.Subheading className=\"w-3/4\" />\n {/* Category - Single line, shorter */}\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Description Section - Fixed 48px height matching actual VendorCard */}\n <div className=\"w-full h-12 overflow-hidden flex items-center\">\n <div className=\"space-y-1 w-full\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n </div>\n\n {/* Footer Section - Responsive layout matching actual structure */}\n <div className=\"flex items-center justify-between gap-2 w-full min-w-0\">\n {/* Stats Container - Flexible width, no overflow */}\n <div className=\"flex items-center gap-3 md:gap-4 min-w-0 flex-shrink\">\n {/* OpenMSP Score skeleton */}\n <div className=\"flex items-center gap-1 flex-shrink-0\">\n <MediaSkeleton.Icon size=\"sm\" className=\"w-5 h-5\" />\n <TextSkeleton.Caption className=\"w-8\" />\n </div>\n \n {/* GitHub Stats skeleton */}\n <div className=\"flex items-center gap-1 flex-shrink-0\">\n <MediaSkeleton.Icon size=\"sm\" className=\"w-5 h-5\" />\n <TextSkeleton.Caption className=\"w-10\" />\n </div>\n </div>\n\n {/* Tag Section - Contained within card boundaries */}\n <div className=\"flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 bg-ods-bg border border-ods-border rounded px-2.5 py-1.5\">\n <div className=\"w-4 h-4 bg-ods-border rounded-sm flex items-center justify-center flex-shrink-0\">\n <MediaSkeleton.Icon size=\"sm\" className=\"w-2.5 h-2.5\" />\n </div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Blog card skeleton content - matches fixed height structure\n */\nfunction BlogCardContent({ showActions, showMetadata }: { showActions: boolean; showMetadata: boolean }) {\n return (\n <>\n {/* Image Section — OG 1200×630 aspect (matches blog-card.tsx loaded state) */}\n <div className=\"blog-card-image-container relative w-full aspect-[1200/630] overflow-hidden bg-ods-bg\">\n <MediaSkeleton.CardImage />\n </div>\n \n {/* Content - Fixed height structure to match BlogCard */}\n <div className=\"p-4 flex flex-col flex-grow\">\n {/* Title Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex items-center min-h-[50.4px] md:min-h-[56px] lg:min-h-[61.6px]\">\n <div className=\"space-y-1 w-full\">\n <TextSkeleton.Subheading className=\"w-full\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n </div>\n </div>\n \n {/* Chips Section - Fixed single line height */}\n <div className=\"mb-3 flex gap-2 h-[28px] items-center\">\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-12\" />\n </div>\n \n {/* Description Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex items-center min-h-[42px] md:min-h-[45px] lg:min-h-[48px]\">\n <div className=\"space-y-1 w-full\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Actions - only if requested */}\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"w-24 h-8\" />\n </div>\n )}\n \n {/* Metadata footer - Matches BlogMeta horizontal layout */}\n {showMetadata && (\n <div className=\"mt-auto\">\n <div className=\"flex items-center justify-between gap-4 pt-4 border-t border-ods-border\">\n {/* Author section - matches AuthorMeta */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n \n {/* Date and reading time section - matches BlogMeta right side */}\n <div className=\"flex items-center gap-3 text-[#767676] shrink-0\">\n <TextSkeleton.Caption className=\"w-12\" />\n {/* Separator dot */}\n <div className=\"w-1 h-1 bg-[#767676] rounded-full\"></div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n )}\n </div>\n </>\n )\n}\n\n/**\n * Category card skeleton content\n */\nfunction CategoryCardContent() {\n return (\n <div className=\"p-4 md:p-6 space-y-4\">\n {/* Icon grid */}\n <div className=\"flex gap-2 md:gap-3\">\n {Array.from({ length: 6 }).map((_, index) => (\n <MediaSkeleton.Icon \n key={index}\n size=\"lg\"\n className=\"flex-shrink-0\"\n />\n ))}\n </div>\n\n {/* Content */}\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n \n <div className=\"flex items-start md:items-end justify-between gap-4 md:gap-6\">\n <div className=\"flex-1 space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n \n <InteractiveSkeleton.Button className=\"w-10 h-10 md:w-12 md:h-12 flex-shrink-0\" />\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Alternative card skeleton content (for vendor alternatives/comparisons)\n */\nfunction AlternativeCardContent({ showActions }: { showActions: boolean }) {\n return (\n <div className=\"flex items-start gap-4 p-4\">\n <MediaSkeleton.Avatar size=\"md\" className=\"flex-shrink-0\" />\n \n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <TextSkeleton.Subheading className=\"w-1/3\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n \n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n </div>\n\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"w-20 h-8\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/**\n * Grid of card skeletons for loading lists\n */\nexport function CardSkeletonGrid({\n count = 6,\n variant = 'vendor',\n className,\n containerClassName,\n ...props\n}: {\n count?: number\n variant?: CardSkeletonProps['variant']\n className?: string\n containerClassName?: string\n} & Omit<CardSkeletonProps, 'variant'>) {\n return (\n <div \n className={cn(\n \"grid gap-4 md:gap-6\",\n // Responsive grid based on card type\n variant === 'vendor' && \"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\",\n variant === 'blog' && \"grid-cols-1 md:grid-cols-2\",\n variant === 'category' && \"grid-cols-1 md:grid-cols-2 lg:grid-cols-4\",\n variant === 'alternative' && \"grid-cols-1\",\n className\n )}\n role=\"status\"\n aria-label={`Loading ${count} ${variant} cards`}\n >\n {Array.from({ length: count }, (_, index) => (\n <CardSkeleton \n key={index} \n variant={variant}\n containerClassName={containerClassName}\n {...props}\n />\n ))}\n </div>\n )\n} ","\"use client\"\n\nimport { useState, useEffect, useCallback, useRef } from 'react'\nimport type { DocNode, DocContent } from '../../types/doc-source'\nimport {\n stripFolderIndexFromPath,\n findDocNodeByPath,\n getDocAncestorNodeIds,\n DEFAULT_FOLDER_INDEX_FILE,\n} from '../../utils/doc-tree-nav'\nimport { useDocNavigation } from './doc-navigation-context'\nimport { scrollElementIntoView } from '../../utils/scroll-into-view'\nimport { navigateSamePageHash, HUB_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav'\n\nfunction scrollToContent() {\n const article = document.querySelector('article') as HTMLElement | null\n if (article) {\n scrollElementIntoView(article, { headerOffset: HUB_HEADER_OFFSET_PX })\n } else {\n window.scrollTo({ top: 0, behavior: 'smooth' })\n }\n}\n\nexport interface UseDocumentTreeConfig {\n /** API endpoint for fetching the document tree structure */\n structureEndpoint: string\n /** API endpoint for fetching document content */\n contentEndpoint: string\n /** Base route path for URL navigation (e.g., '/knowledge-base', '/data-room') */\n baseRoute: string\n /** Folder-index filename (defaults to 'README.md'). */\n folderIndexFile?: string\n}\n\n/**\n * Generic hook for document tree navigation and content fetching.\n * Drives DocViewer across all doc-source consumers.\n *\n * Client-only: structure + content fetches run in parallel on first mount.\n * No SSR pre-population — the previous SSR path required a Supabase admin\n * client (service role key) and silently fell back to client fetches on\n * envs where the key wasn't set; the parallel client fetches keep behavior\n * uniform across local + prod (latency ~= max(structure, content), not sum).\n */\nexport function useDocumentTree(\n config: UseDocumentTreeConfig,\n initialPath?: string,\n) {\n const { structureEndpoint, contentEndpoint, baseRoute } = config\n const folderIndexFile = config.folderIndexFile ?? DEFAULT_FOLDER_INDEX_FILE\n\n const cleanInitialPath = stripFolderIndexFromPath(\n initialPath?.replace(/\\/$/, '') || '',\n folderIndexFile,\n )\n\n const [structure, setStructure] = useState<DocNode[]>([])\n const [selectedPath, setSelectedPath] = useState<string>(cleanInitialPath)\n const [content, setContent] = useState<DocContent | null>(null)\n const [isLoadingStructure, setIsLoadingStructure] = useState(true)\n const [isLoadingContent, setIsLoadingContent] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set())\n const [isInitialized, setIsInitialized] = useState(false)\n const lastFetchedPath = useRef<string | null>(null)\n\n const normalizedBaseRoute = baseRoute.replace(/\\/$/, '')\n\n const selectedPathRef = useRef(selectedPath)\n selectedPathRef.current = selectedPath\n\n const docNavigation = useDocNavigation()\n\n useEffect(() => {\n const handlePopState = () => {\n const pathname = window.location.pathname\n let pathFromUrl = ''\n\n if (pathname === normalizedBaseRoute || pathname === `${normalizedBaseRoute}/`) {\n pathFromUrl = ''\n } else if (pathname.startsWith(`${normalizedBaseRoute}/`)) {\n pathFromUrl = pathname.substring(`${normalizedBaseRoute}/`.length)\n }\n\n pathFromUrl = stripFolderIndexFromPath(pathFromUrl, folderIndexFile)\n\n if (pathFromUrl !== selectedPathRef.current) {\n setSelectedPath(pathFromUrl)\n if (pathFromUrl) {\n const parentPath = pathFromUrl.includes('/')\n ? pathFromUrl.substring(0, pathFromUrl.lastIndexOf('/'))\n : pathFromUrl\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)))\n }\n setTimeout(() => {\n scrollToContent()\n }, 150)\n }\n }\n\n window.addEventListener('popstate', handlePopState)\n return () => window.removeEventListener('popstate', handlePopState)\n }, [normalizedBaseRoute, folderIndexFile])\n\n useEffect(() => {\n if (!isInitialized) {\n // Kick off the speculative content fetch IN PARALLEL with the structure\n // fetch — the two endpoints are independent and most landing pages have\n // a root README (the default folder-index). If the structure ends up\n // pointing at a different path (e.g. knowledge-base falls back to the\n // first-folder README because there's no root README), the content\n // useEffect issues the correct fetch after structure arrives — the\n // speculative result silently no-ops (the content state update gets\n // overwritten by the targeted fetch).\n const speculativeContentPath = cleanInitialPath || folderIndexFile\n lastFetchedPath.current = speculativeContentPath\n fetchContent(speculativeContentPath)\n fetchStructure()\n setIsInitialized(true)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isInitialized])\n\n useEffect(() => {\n if (selectedPath === null || selectedPath === undefined) return\n if (!structure || structure.length === 0) return\n\n let pathToFetch: string | null = null\n\n if (selectedPath === '') {\n pathToFetch = folderIndexFile\n } else {\n const node = findDocNodeByPath(selectedPath, structure)\n\n if (node && node.type === 'folder' && !node.hasReadme) {\n return\n }\n\n pathToFetch = selectedPath\n if (node && node.type === 'folder' && node.hasReadme) {\n pathToFetch = `${selectedPath}/${folderIndexFile}`\n }\n }\n\n if (pathToFetch === lastFetchedPath.current) {\n return\n }\n\n if (pathToFetch) {\n lastFetchedPath.current = pathToFetch\n fetchContent(pathToFetch)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedPath, structure, folderIndexFile])\n\n const fetchStructure = async () => {\n try {\n setIsLoadingStructure(true)\n setError(null)\n\n const response = await fetch(structureEndpoint)\n\n if (!response.ok) {\n throw new Error('Failed to load documentation structure')\n }\n\n const result = await response.json()\n\n if (result.success && result.data) {\n setStructure(result.data)\n\n if (cleanInitialPath) {\n const pathForExpansion = cleanInitialPath.includes('.')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath\n if (pathForExpansion) {\n setExpandedNodes(new Set(getDocAncestorNodeIds(pathForExpansion)))\n }\n } else if (result.data.length > 0) {\n const hasRootReadme = result.data.some(\n (node: DocNode) => node.type === 'file' && node.path === folderIndexFile\n )\n\n if (!hasRootReadme) {\n const firstNode = result.data[0]\n if (firstNode.type === 'folder') {\n setExpandedNodes(new Set([firstNode.id]))\n if (firstNode.hasReadme) {\n setSelectedPath(firstNode.path)\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${firstNode.path}`)\n }\n }\n }\n }\n } else {\n setError('Failed to load documentation structure')\n }\n } catch (err) {\n console.error('Error fetching documentation structure:', err)\n setError(err instanceof Error ? err.message : 'Failed to load documentation')\n } finally {\n setIsLoadingStructure(false)\n }\n }\n\n const fetchContent = async (path: string) => {\n try {\n setIsLoadingContent(true)\n // Don't clear `error` here — if a previous fetch set an error and this\n // is a stale/speculative call that gets superseded, the guard below\n // returns early without writing to state. Clearing error here would\n // briefly flicker the user-visible error message.\n\n const response = await fetch(`${contentEndpoint}?path=${encodeURIComponent(path)}`)\n\n // Request-id guard: between awaits, `lastFetchedPath.current` may have\n // been bumped by a newer fetch (the structure-arrives auto-select issues\n // a more-targeted fetch while the speculative one is in flight). Bail\n // BEFORE writing to state — otherwise the late 404 of the speculative\n // fetch overwrites the targeted fetch's good content with null.\n if (path !== lastFetchedPath.current) return\n\n if (!response.ok) {\n if (response.status === 404) {\n const result = await response.json().catch(() => ({}))\n if (path !== lastFetchedPath.current) return\n // Landing-page silent fallback: when the user lands on the source's\n // root URL and there's no root `README.md` (knowledge-base case),\n // the speculative fetch 404s — surface an empty state instead of\n // an error banner. The structure-arrives auto-select will fire\n // a targeted fetch for the first-folder README on the next render.\n if (path === folderIndexFile && selectedPath === '') {\n setError(null)\n setContent(null)\n return\n }\n setError(result.error || 'Documentation file not found')\n setContent(null)\n return\n }\n throw new Error('Failed to load documentation content')\n }\n\n const result = await response.json()\n if (path !== lastFetchedPath.current) return\n setError(null)\n\n if (result.success && result.data) {\n if (result.redirect && result.correctPath !== undefined) {\n const correctedPath = result.correctPath\n setSelectedPath(correctedPath)\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${correctedPath}`)\n setContent(result.data)\n } else {\n setContent(result.data)\n }\n } else {\n setError(result.error || 'Failed to load content')\n setContent(null)\n }\n } catch (err) {\n if (path !== lastFetchedPath.current) return\n console.error('Error fetching documentation content:', err)\n setError(err instanceof Error ? err.message : 'Failed to load content')\n setContent(null)\n } finally {\n // Only clear loading state if THIS fetch is still the active one — a\n // superseded speculative shouldn't flip the spinner off while the\n // targeted fetch is still in flight.\n if (path === lastFetchedPath.current) setIsLoadingContent(false)\n }\n }\n\n const toggleNode = useCallback((nodeId: string) => {\n setExpandedNodes(prev => {\n const next = new Set(prev)\n if (next.has(nodeId)) {\n next.delete(nodeId)\n } else {\n next.add(nodeId)\n }\n return next\n })\n }, [])\n\n // Structural minimum the body uses — `id`, `path`, `type`, `hasReadme`.\n // Widening from `DocNode` lets the navigation components (which carry the\n // narrower `NavigationNode` row shape) pass their own node back without the\n // cross-type `as` cast. Both DocNode and NavigationNode satisfy this.\n const selectNode = useCallback((node: Pick<DocNode, 'id' | 'path' | 'type' | 'hasReadme'>) => {\n if (node.type === 'folder') {\n setExpandedNodes(prev => {\n if (prev.has(node.id)) {\n const ancestorIds = getDocAncestorNodeIds(node.path)\n ancestorIds.pop()\n return new Set(ancestorIds)\n } else {\n return new Set(getDocAncestorNodeIds(node.path))\n }\n })\n\n if (node.hasReadme) {\n lastFetchedPath.current = null\n setSelectedPath(node.path)\n window.history.pushState({}, '', `${normalizedBaseRoute}/${node.path}`)\n setTimeout(() => {\n scrollToContent()\n }, 150)\n } else {\n setSelectedPath(node.path)\n }\n } else {\n const lastSlash = node.path.lastIndexOf('/')\n if (lastSlash > 0) {\n const parentPath = node.path.substring(0, lastSlash)\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)))\n }\n\n lastFetchedPath.current = null\n setSelectedPath(node.path)\n window.history.pushState({}, '', `${normalizedBaseRoute}/${node.path}`)\n setTimeout(() => {\n scrollToContent()\n }, 150)\n }\n }, [normalizedBaseRoute])\n\n const navigateToDoc = useCallback((path: string, options?: { expandFolder?: boolean; fromInternalLink?: boolean }) => {\n const hashIndex = path.indexOf('#')\n const anchor = hashIndex !== -1 ? path.substring(hashIndex) : ''\n const cleanPath = path.replace(/\\/$/, '').split('#')[0]\n\n // Same-doc-different-anchor shortcut. Content is already mounted, so we\n // don't need the 300ms \"wait-for-fetch\" bandaid — the canonical helper\n // owns pushState + synthetic `hashchange` (so any in-doc TOC / accordion\n // bound to the URL hash re-renders) + the anchoring-proof tween in one\n // sync call. `headerOffset: HUB_HEADER_OFFSET_PX` matches the cross-doc path below so\n // anchors land BELOW the docs sticky header on every same-doc internal\n // link click. Cross-doc nav (different cleanPath) falls through to the\n // existing fetch-then-scroll path below.\n //\n // We pass the BARE-hash form to the helper rather than reconstructing\n // a full `${normalizedBaseRoute}/${cleanPath}${anchor}` path: the\n // helper's pathname check compares against `window.location.pathname`,\n // which carries the FOLDER-INDEX-STRIPPED form (`/docs/foo` for\n // `foo/README.md`, `/docs` for the root index). Handing it `cleanPath`\n // — the raw resolved path — produces e.g. `/docs/foo/README.md` and\n // the compare fails → helper returns false → silent dead-click. The\n // bare-hash form sidesteps that entirely: the helper reconstructs\n // `pathname + search + hash` from `window.location`, so the compare\n // is trivially equal. Covers bare `#anchor` links (resolve to\n // `cleanPath=''`) AND folder-index links (`foo/README.md` resolving\n // to the current `/docs/foo`).\n // Bare-hash internal links (`[Click](#section)`) come in as\n // `path === '#section'`, so `cleanPath` becomes `''` and the naive\n // strip-then-compare misses the same-doc shortcut on every NON-root\n // doc (selectedPath is e.g. `'foo/bar'`, not `''`). For that case the\n // current doc IS the same-doc target by definition — short-circuit\n // pathForSelection to the current selection so the shortcut fires.\n const pathForSelection =\n anchor && options?.fromInternalLink && cleanPath === ''\n ? selectedPathRef.current\n : stripFolderIndexFromPath(cleanPath, folderIndexFile)\n if (\n anchor &&\n options?.fromInternalLink &&\n pathForSelection === selectedPathRef.current\n ) {\n navigateSamePageHash(anchor, { headerOffset: HUB_HEADER_OFFSET_PX })\n return\n }\n\n const scrollAfterNav = () => {\n if (anchor) {\n setTimeout(() => {\n const el = document.getElementById(anchor.substring(1))\n if (el) {\n scrollElementIntoView(el, { headerOffset: HUB_HEADER_OFFSET_PX })\n } else {\n scrollToContent()\n }\n }, 300)\n } else {\n setTimeout(() => {\n scrollToContent()\n }, 150)\n }\n }\n\n if (options?.expandFolder) {\n lastFetchedPath.current = null\n setSelectedPath(cleanPath)\n window.history.pushState({}, '', `${normalizedBaseRoute}/${cleanPath}${anchor}`)\n\n const pathParts = cleanPath.split('/')\n const nodeIdsToExpand: string[] = []\n let currentPath = ''\n\n for (const part of pathParts) {\n currentPath = currentPath ? `${currentPath}-${part}` : part\n nodeIdsToExpand.push(currentPath.toLowerCase())\n }\n\n setExpandedNodes(new Set(nodeIdsToExpand))\n\n if (options?.fromInternalLink) {\n scrollAfterNav()\n }\n\n setError(null)\n return\n }\n\n // `pathForSelection` was already computed above (inside the\n // same-doc-anchor shortcut check); reuse it here for cross-doc nav.\n const urlPath = pathForSelection\n\n lastFetchedPath.current = null\n setSelectedPath(pathForSelection)\n window.history.pushState({}, '', `${normalizedBaseRoute}/${urlPath}${anchor}`)\n\n if (options?.fromInternalLink) {\n scrollAfterNav()\n }\n\n const pathParts = cleanPath.split('/')\n const parentIds: string[] = []\n let currentPath = ''\n const partsToProcess = cleanPath.includes('.') ? pathParts.slice(0, -1) : pathParts\n\n for (const part of partsToProcess) {\n currentPath = currentPath ? `${currentPath}-${part}` : part\n parentIds.push(currentPath.toLowerCase())\n }\n\n if (parentIds.length > 0) {\n setExpandedNodes(new Set(parentIds))\n }\n }, [structure, normalizedBaseRoute, folderIndexFile])\n\n useEffect(() => {\n return docNavigation.register({\n baseRoute: normalizedBaseRoute,\n findNodeByPath: (path) => {\n const clean = stripFolderIndexFromPath(path.replace(/\\/$/, '').split('#')[0], folderIndexFile)\n return findDocNodeByPath(clean, structure) ?? null\n },\n selectNode,\n })\n }, [docNavigation, normalizedBaseRoute, structure, selectNode, folderIndexFile])\n\n return {\n structure,\n selectedPath,\n setSelectedPath,\n content,\n isLoadingStructure,\n isLoadingContent,\n error,\n expandedNodes,\n toggleNode,\n selectNode,\n navigateToDoc,\n refetch: fetchStructure,\n setExpandedNodes,\n }\n}\n\nexport type { DocNode, DocContent }\n","\"use client\"\n\nimport React, { createContext, useCallback, useContext, useRef, type ReactNode } from 'react'\nimport type { DocNode } from '../../types/doc-source'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DocNavigator {\n /** Base route this navigator owns (e.g., '/knowledge-base', '/data-room'). */\n baseRoute: string\n /** Look up a node in the tree by its storage path (e.g., 'openframe-cli/README.md'). */\n findNodeByPath: (path: string) => DocNode | null\n /** Navigate to a node using the same flow as a sidebar menu click. */\n selectNode: (node: DocNode) => void\n}\n\ninterface DocNavigationContextValue {\n /**\n * Register a navigator for a `baseRoute`. Returns a cleanup function that\n * removes the navigator from the map IF the slot still owns it (StrictMode-safe).\n * Duplicate registration for the same baseRoute logs a console.warn and replaces.\n */\n register: (nav: DocNavigator) => () => void\n /**\n * Navigate to a path inside whichever registered navigator owns its baseRoute prefix.\n * Returns true if handled, false otherwise (caller falls back to opening in a new tab).\n */\n navigate: (path: string) => boolean\n /** Whether ANY navigator is currently mounted. */\n isAvailable: () => boolean\n}\n\n// =============================================================================\n// Context\n// =============================================================================\n\nconst DocNavigationContext = createContext<DocNavigationContextValue | null>(null)\n\n/**\n * Bridges `useDocumentTree` instances (deep in the page tree) to `GlobalAskAI`\n * (high up near the root) without global events or URL parsing.\n *\n * Multi-navigator design: a `Map<baseRoute, DocNavigator>` lets multiple\n * viewers coexist (nested viewers, dual-pane scenarios). Today only one\n * viewer mounts at a time, so behavior is unchanged.\n */\nexport function DocNavigationProvider({ children }: { children: ReactNode }) {\n const navMap = useRef<Map<string, DocNavigator>>(new Map())\n\n const register = useCallback((nav: DocNavigator) => {\n const existing = navMap.current.get(nav.baseRoute)\n if (existing && existing !== nav) {\n // eslint-disable-next-line no-console\n console.warn(\n `[DocNavigationContext] duplicate registration for ${nav.baseRoute}; replacing`,\n )\n }\n navMap.current.set(nav.baseRoute, nav)\n return () => {\n // StrictMode-safe identity check: only delete if THIS nav still owns the slot.\n if (navMap.current.get(nav.baseRoute) === nav) {\n navMap.current.delete(nav.baseRoute)\n }\n }\n }, [])\n\n const findOwningNavigator = useCallback((path: string): DocNavigator | null => {\n // First try: a navigator whose baseRoute is a prefix of `path`. Walks the\n // longest match first so nested routes win over a parent.\n const baseRoutes = Array.from(navMap.current.keys()).sort((a, b) => b.length - a.length)\n for (const baseRoute of baseRoutes) {\n if (path === baseRoute || path.startsWith(`${baseRoute}/`)) {\n return navMap.current.get(baseRoute) ?? null\n }\n }\n // Fallback: a single navigator handles any path (existing behavior — chat\n // chips emit storage paths, not absolute URLs, so prefix matching misses).\n if (navMap.current.size === 1) {\n return navMap.current.values().next().value ?? null\n }\n return null\n }, [])\n\n const navigate = useCallback(\n (path: string): boolean => {\n const nav = findOwningNavigator(path)\n if (!nav) return false\n const node = nav.findNodeByPath(path)\n if (!node) return false\n nav.selectNode(node)\n return true\n },\n [findOwningNavigator],\n )\n\n const isAvailable = useCallback(() => navMap.current.size > 0, [])\n\n const value = React.useMemo(\n () => ({ register, navigate, isAvailable }),\n [register, navigate, isAvailable],\n )\n\n return <DocNavigationContext.Provider value={value}>{children}</DocNavigationContext.Provider>\n}\n\n// =============================================================================\n// Hooks\n// =============================================================================\n\nexport function useDocNavigation(): DocNavigationContextValue {\n const ctx = useContext(DocNavigationContext)\n if (!ctx) {\n // Safe no-op fallback when used outside the provider.\n return {\n register: () => () => {},\n navigate: () => false,\n isAvailable: () => false,\n }\n }\n return ctx\n}\n","\"use client\"\n\nimport { useState, useRef, useCallback, useEffect, useMemo } from \"react\"\nimport { scrollElementIntoView } from \"../../utils/scroll-into-view\"\n\n// Single source for the sticky-chrome height. Used for BOTH the scroll target\n// offset (where a clicked section lands) AND the active-section detection\n// threshold (where the scroll listener flips highlight). They must match —\n// previously 100 vs 150 caused a 50px window where the indicator jumped to\n// the next section even though that section's top was still below the\n// clicked one's resting offset.\nconst SCROLL_OFFSET = 100\n\ninterface ScrollSpySection {\n id: string\n title?: string\n level?: number\n}\n\ninterface UseScrollSpyReturn {\n activeSection: string\n handleSectionClick: (sectionId: string) => void\n}\n\n/**\n * Shared scroll spy hook for tracking active section based on scroll position.\n * Used by DocViewer for sticky section navigation.\n */\nexport function useScrollSpy(sections: ScrollSpySection[] | undefined): UseScrollSpyReturn {\n const [activeSection, setActiveSection] = useState(\"\")\n const isScrollingFromClick = useRef(false)\n\n const sectionIds = useMemo(\n () => sections?.map(s => s.id).join(',') ?? '',\n [sections]\n )\n const stableSections = useRef(sections)\n if (sectionIds !== (stableSections.current?.map(s => s.id).join(',') ?? '')) {\n stableSections.current = sections\n }\n\n const handleSectionClick = useCallback((sectionId: string) => {\n const targetElement = document.getElementById(sectionId)\n if (!targetElement) return\n\n isScrollingFromClick.current = true\n setActiveSection(sectionId)\n\n scrollElementIntoView(targetElement, { headerOffset: SCROLL_OFFSET })\n\n setTimeout(() => {\n isScrollingFromClick.current = false\n }, 800)\n }, [])\n\n useEffect(() => {\n const currentSections = stableSections.current\n if (!currentSections || currentSections.length === 0) return\n\n const handleScroll = () => {\n if (isScrollingFromClick.current) return\n\n const scrollPosition = window.scrollY + SCROLL_OFFSET\n let currentSection = currentSections[0]?.id || \"\"\n\n for (let i = currentSections.length - 1; i >= 0; i--) {\n const element = document.getElementById(currentSections[i].id)\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = currentSections[i].id\n break\n }\n }\n\n setActiveSection((prev) => (prev !== currentSection ? currentSection : prev))\n }\n\n let scrollTimer: ReturnType<typeof setTimeout>\n const throttledScroll = () => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(handleScroll, 100)\n }\n\n window.addEventListener(\"scroll\", throttledScroll)\n handleScroll()\n\n return () => {\n window.removeEventListener(\"scroll\", throttledScroll)\n clearTimeout(scrollTimer)\n }\n }, [sectionIds])\n\n return { activeSection, handleSectionClick }\n}\n"]}