@flamingo-stack/openframe-frontend-core 0.0.671 → 0.0.672-snapshot.20260928034232

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 (179) hide show
  1. package/dist/chat-protocol/card-marker.d.ts +12 -0
  2. package/dist/chat-protocol/card-marker.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +17 -0
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.d.ts +1 -0
  6. package/dist/chat-protocol/index.d.ts.map +1 -1
  7. package/dist/chat-protocol/index.js +15 -1
  8. package/dist/chat-protocol/index.js.map +1 -1
  9. package/dist/{chunk-DAGKBFZN.cjs → chunk-26NSIJMT.cjs} +33 -33
  10. package/dist/{chunk-DAGKBFZN.cjs.map → chunk-26NSIJMT.cjs.map} +1 -1
  11. package/dist/{chunk-FUXUJSPP.cjs → chunk-3UDBK2YG.cjs} +1 -1
  12. package/dist/chunk-3UDBK2YG.cjs.map +1 -0
  13. package/dist/{chunk-XGXHHSDU.cjs → chunk-6QUKYN4K.cjs} +22 -22
  14. package/dist/{chunk-XGXHHSDU.cjs.map → chunk-6QUKYN4K.cjs.map} +1 -1
  15. package/dist/{chunk-SNNE34OP.js → chunk-BF7GZ6BS.js} +5 -5
  16. package/dist/{chunk-SJZPKIW6.cjs → chunk-DRURJCEY.cjs} +12 -12
  17. package/dist/{chunk-SJZPKIW6.cjs.map → chunk-DRURJCEY.cjs.map} +1 -1
  18. package/dist/{chunk-YIDNSRPL.cjs → chunk-G262S3Q5.cjs} +31 -31
  19. package/dist/{chunk-YIDNSRPL.cjs.map → chunk-G262S3Q5.cjs.map} +1 -1
  20. package/dist/{chunk-OG2BNJ2Q.cjs → chunk-HJ532KQR.cjs} +390 -375
  21. package/dist/chunk-HJ532KQR.cjs.map +1 -0
  22. package/dist/{chunk-K3O4D7LW.js → chunk-HRM6242Y.js} +4 -4
  23. package/dist/{chunk-BP542DGQ.cjs → chunk-IFQMIYFO.cjs} +55 -28
  24. package/dist/chunk-IFQMIYFO.cjs.map +1 -0
  25. package/dist/{chunk-IJIIL6WU.js → chunk-IG7M3XKX.js} +6 -6
  26. package/dist/{chunk-PQ7YC5Y6.js → chunk-II6ABVLK.js} +3 -3
  27. package/dist/{chunk-TLLTL6LB.js → chunk-JD4U7QMG.js} +1 -1
  28. package/dist/chunk-JD4U7QMG.js.map +1 -0
  29. package/dist/{chunk-ZRIVCLPR.js → chunk-JD5TKRCU.js} +1 -1
  30. package/dist/chunk-JD5TKRCU.js.map +1 -0
  31. package/dist/{chunk-XH4MRTZM.cjs → chunk-K365MY6G.cjs} +5 -5
  32. package/dist/{chunk-XH4MRTZM.cjs.map → chunk-K365MY6G.cjs.map} +1 -1
  33. package/dist/{chunk-EIAVE6SE.cjs → chunk-KV7W5T6U.cjs} +10 -10
  34. package/dist/{chunk-EIAVE6SE.cjs.map → chunk-KV7W5T6U.cjs.map} +1 -1
  35. package/dist/{chunk-II66XWU5.cjs → chunk-KYF5EONP.cjs} +3 -3
  36. package/dist/{chunk-II66XWU5.cjs.map → chunk-KYF5EONP.cjs.map} +1 -1
  37. package/dist/{chunk-D55IENZJ.cjs → chunk-LCMT2GZK.cjs} +1 -1
  38. package/dist/chunk-LCMT2GZK.cjs.map +1 -0
  39. package/dist/{chunk-STLBB6PF.js → chunk-LMUZZUYC.js} +35 -8
  40. package/dist/chunk-LMUZZUYC.js.map +1 -0
  41. package/dist/{chunk-FAJUQKWQ.cjs → chunk-N6TC6IPV.cjs} +76 -75
  42. package/dist/chunk-N6TC6IPV.cjs.map +1 -0
  43. package/dist/{chunk-RKUCK6MN.js → chunk-OGOMAK7B.js} +2 -2
  44. package/dist/{chunk-G64BNLDO.js → chunk-P4GUIOLP.js} +2 -2
  45. package/dist/{chunk-DUZEFACP.js → chunk-RRU46J3N.js} +3 -3
  46. package/dist/{chunk-DAWYS25M.cjs → chunk-THSIFQGS.cjs} +17 -17
  47. package/dist/{chunk-DAWYS25M.cjs.map → chunk-THSIFQGS.cjs.map} +1 -1
  48. package/dist/{chunk-6C6UUOH4.js → chunk-TTHBFT7Q.js} +8 -7
  49. package/dist/{chunk-6C6UUOH4.js.map → chunk-TTHBFT7Q.js.map} +1 -1
  50. package/dist/{chunk-YNGAJGYB.cjs → chunk-TZP2OXHG.cjs} +346 -6
  51. package/dist/chunk-TZP2OXHG.cjs.map +1 -0
  52. package/dist/{chunk-T3ACDOVD.cjs → chunk-U2H3C4NT.cjs} +7 -7
  53. package/dist/{chunk-T3ACDOVD.cjs.map → chunk-U2H3C4NT.cjs.map} +1 -1
  54. package/dist/{chunk-OYMOG544.js → chunk-UFMXQKVT.js} +5 -5
  55. package/dist/{chunk-NIYIK32F.js → chunk-UMTXGEPU.js} +19 -4
  56. package/dist/{chunk-NIYIK32F.js.map → chunk-UMTXGEPU.js.map} +1 -1
  57. package/dist/{chunk-3PVOF3IB.js → chunk-UTHZ7DLN.js} +343 -3
  58. package/dist/{chunk-3PVOF3IB.js.map → chunk-UTHZ7DLN.js.map} +1 -1
  59. package/dist/{chunk-YJWR7D6O.cjs → chunk-UXT5EK27.cjs} +3 -3
  60. package/dist/{chunk-YJWR7D6O.cjs.map → chunk-UXT5EK27.cjs.map} +1 -1
  61. package/dist/{chunk-UH3BL2OT.js → chunk-VK5737GE.js} +2 -2
  62. package/dist/{chunk-R22IA7MP.js → chunk-VKLM5UKG.js} +4 -4
  63. package/dist/{chunk-FHTNCWIP.js → chunk-X2DR3XNN.js} +5 -5
  64. package/dist/{chunk-GF5TSQ6C.cjs → chunk-YCYOZAB5.cjs} +10 -10
  65. package/dist/{chunk-GF5TSQ6C.cjs.map → chunk-YCYOZAB5.cjs.map} +1 -1
  66. package/dist/{chunk-4PVY4JJN.js → chunk-YWQVL5VJ.js} +3 -3
  67. package/dist/{chunk-PLG6LP7E.cjs → chunk-Z5K4IQZU.cjs} +49 -49
  68. package/dist/{chunk-PLG6LP7E.cjs.map → chunk-Z5K4IQZU.cjs.map} +1 -1
  69. package/dist/components/case-studies/index.cjs +18 -17
  70. package/dist/components/case-studies/index.cjs.map +1 -1
  71. package/dist/components/case-studies/index.js +7 -6
  72. package/dist/components/case-studies/index.js.map +1 -1
  73. package/dist/components/case-studies/share-experience-section.d.ts.map +1 -1
  74. package/dist/components/chat/index.cjs +2 -2
  75. package/dist/components/chat/index.js +1 -1
  76. package/dist/components/contact/contact-form.d.ts +6 -1
  77. package/dist/components/contact/contact-form.d.ts.map +1 -1
  78. package/dist/components/contact/index.cjs +6 -6
  79. package/dist/components/contact/index.js +5 -5
  80. package/dist/components/docs/index.cjs +9 -9
  81. package/dist/components/docs/index.js +8 -8
  82. package/dist/components/embeds/index.cjs +6 -6
  83. package/dist/components/embeds/index.js +5 -5
  84. package/dist/components/faq/index.cjs +6 -6
  85. package/dist/components/faq/index.js +5 -5
  86. package/dist/components/features/index.cjs +5 -5
  87. package/dist/components/features/index.js +4 -4
  88. package/dist/components/features/waitlist-form.d.ts +7 -2
  89. package/dist/components/features/waitlist-form.d.ts.map +1 -1
  90. package/dist/components/help-center-pages/index.cjs +77 -76
  91. package/dist/components/help-center-pages/index.cjs.map +1 -1
  92. package/dist/components/help-center-pages/index.js +17 -16
  93. package/dist/components/help-center-pages/index.js.map +1 -1
  94. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  95. package/dist/components/index.cjs +43 -43
  96. package/dist/components/index.js +15 -15
  97. package/dist/components/meeting-scheduler/booking-form.d.ts +4 -1
  98. package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
  99. package/dist/components/meeting-scheduler/index.cjs +75 -40
  100. package/dist/components/meeting-scheduler/index.cjs.map +1 -1
  101. package/dist/components/meeting-scheduler/index.d.ts +5 -1
  102. package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
  103. package/dist/components/meeting-scheduler/index.js +46 -11
  104. package/dist/components/meeting-scheduler/index.js.map +1 -1
  105. package/dist/components/navigation/index.cjs +8 -8
  106. package/dist/components/navigation/index.js +7 -7
  107. package/dist/components/onboarding-guides/index.cjs +8 -8
  108. package/dist/components/onboarding-guides/index.js +7 -7
  109. package/dist/components/tickets/help-center-create-form.d.ts.map +1 -1
  110. package/dist/components/tickets/index.cjs +9 -9
  111. package/dist/components/tickets/index.js +8 -8
  112. package/dist/components/ui/index.cjs +5 -5
  113. package/dist/components/ui/index.js +4 -4
  114. package/dist/contexts/endpoints-runtime-context.d.ts +5 -0
  115. package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -1
  116. package/dist/contexts/index.cjs +2 -2
  117. package/dist/contexts/index.js +1 -1
  118. package/dist/hooks/index.cjs +5 -3
  119. package/dist/hooks/index.cjs.map +1 -1
  120. package/dist/hooks/index.d.ts +1 -0
  121. package/dist/hooks/index.d.ts.map +1 -1
  122. package/dist/hooks/index.js +4 -2
  123. package/dist/hooks/use-form-rescue.d.ts +37 -0
  124. package/dist/hooks/use-form-rescue.d.ts.map +1 -0
  125. package/dist/index.cjs +53 -9
  126. package/dist/index.cjs.map +1 -1
  127. package/dist/index.js +52 -8
  128. package/dist/index.js.map +1 -1
  129. package/dist/utils/form-rescue.cjs +123 -0
  130. package/dist/utils/form-rescue.cjs.map +1 -0
  131. package/dist/utils/form-rescue.d.ts +105 -0
  132. package/dist/utils/form-rescue.d.ts.map +1 -0
  133. package/dist/utils/form-rescue.js +101 -0
  134. package/dist/utils/form-rescue.js.map +1 -0
  135. package/dist/utils/index.cjs +119 -0
  136. package/dist/utils/index.cjs.map +1 -1
  137. package/dist/utils/index.d.ts +1 -0
  138. package/dist/utils/index.d.ts.map +1 -1
  139. package/dist/utils/index.js +99 -1
  140. package/dist/utils/index.js.map +1 -1
  141. package/package.json +7 -1
  142. package/src/chat-protocol/__tests__/card-marker.test.ts +27 -1
  143. package/src/chat-protocol/card-marker.ts +24 -0
  144. package/src/chat-protocol/index.ts +3 -0
  145. package/src/components/case-studies/share-experience-section.tsx +1 -0
  146. package/src/components/contact/contact-form.tsx +38 -2
  147. package/src/components/features/waitlist-form.tsx +27 -2
  148. package/src/components/help-center-pages/trust-center-sections.tsx +1 -0
  149. package/src/components/meeting-scheduler/booking-form.tsx +14 -0
  150. package/src/components/meeting-scheduler/index.tsx +38 -3
  151. package/src/components/tickets/help-center-create-form.tsx +2 -0
  152. package/src/contexts/endpoints-runtime-context.tsx +5 -0
  153. package/src/hooks/__tests__/use-form-rescue.test.tsx +189 -0
  154. package/src/hooks/index.ts +3 -0
  155. package/src/hooks/use-form-rescue.ts +315 -0
  156. package/src/utils/__tests__/form-rescue.test.ts +106 -0
  157. package/src/utils/form-rescue.ts +198 -0
  158. package/src/utils/index.ts +5 -0
  159. package/dist/chunk-BP542DGQ.cjs.map +0 -1
  160. package/dist/chunk-D55IENZJ.cjs.map +0 -1
  161. package/dist/chunk-FAJUQKWQ.cjs.map +0 -1
  162. package/dist/chunk-FUXUJSPP.cjs.map +0 -1
  163. package/dist/chunk-OG2BNJ2Q.cjs.map +0 -1
  164. package/dist/chunk-STLBB6PF.js.map +0 -1
  165. package/dist/chunk-TLLTL6LB.js.map +0 -1
  166. package/dist/chunk-YNGAJGYB.cjs.map +0 -1
  167. package/dist/chunk-ZRIVCLPR.js.map +0 -1
  168. /package/dist/{chunk-SNNE34OP.js.map → chunk-BF7GZ6BS.js.map} +0 -0
  169. /package/dist/{chunk-K3O4D7LW.js.map → chunk-HRM6242Y.js.map} +0 -0
  170. /package/dist/{chunk-IJIIL6WU.js.map → chunk-IG7M3XKX.js.map} +0 -0
  171. /package/dist/{chunk-PQ7YC5Y6.js.map → chunk-II6ABVLK.js.map} +0 -0
  172. /package/dist/{chunk-RKUCK6MN.js.map → chunk-OGOMAK7B.js.map} +0 -0
  173. /package/dist/{chunk-G64BNLDO.js.map → chunk-P4GUIOLP.js.map} +0 -0
  174. /package/dist/{chunk-DUZEFACP.js.map → chunk-RRU46J3N.js.map} +0 -0
  175. /package/dist/{chunk-OYMOG544.js.map → chunk-UFMXQKVT.js.map} +0 -0
  176. /package/dist/{chunk-UH3BL2OT.js.map → chunk-VK5737GE.js.map} +0 -0
  177. /package/dist/{chunk-R22IA7MP.js.map → chunk-VKLM5UKG.js.map} +0 -0
  178. /package/dist/{chunk-FHTNCWIP.js.map → chunk-X2DR3XNN.js.map} +0 -0
  179. /package/dist/{chunk-4PVY4JJN.js.map → chunk-YWQVL5VJ.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/components/help-center-pages/index.cjs","../../../src/components/help-center-pages/roadmap-page.tsx","../../../src/components/help-center-pages/product-releases-list-page.tsx","../../../src/components/help-center-pages/delivery-page.tsx","../../../src/components/help-center-pages/onboarding-guides-catalog-page.tsx","../../../src/components/help-center-pages/trust-center-page.tsx","../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":["jsxs","jsx","Fragment"],"mappings":"AAAA,6rBAAY;AACZ,YAAY;AACZ;AACE;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACA;ACpEI,+CAAA;AAdG,SAAS,WAAA,CAAY;AAAA,EAC1B,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,uBACE,8BAAA,gCA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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/components/help-center-pages/index.cjs","sourcesContent":[null,"'use client';\n\n/**\n * `<RoadmapPage>` — the full `/roadmap` page: `DevSectionPage sectionKey=\"roadmap\"`\n * (PageShell + PageLayout + hero + search/status chrome) wrapping the\n * self-contained `<RoadmapView>` list. Hosts configure only the api routes\n * (+ optional SSR `initialItems`); both openframe-frontend and the hub mount\n * this instead of hand-composing `DevSectionPage` + `RoadmapView`.\n */\n\nimport type { ReactNode } from 'react';\nimport type { RoadmapItem } from '../chat/types/entities/roadmap-item';\nimport { DevSectionPage } from '../shared/dev-section';\nimport { RoadmapView } from '../shared/roadmap';\nimport type { UseRoadmapVotingOptions } from '../shared/roadmap';\n\nexport interface RoadmapPageProps {\n /** GET list endpoint (the api route). Default `/api/roadmap`. */\n roadmapEndpoint?: string;\n /** Per-task refresh URL builder (after a vote). Default `/api/roadmap/<id>`. */\n buildRefreshUrl?: (taskId: string) => string;\n /** Vote POST endpoint, forwarded to `RoadmapView` via `votingOptions`. */\n voteApiEndpoint?: string;\n /** Full voting options override — takes precedence over `voteApiEndpoint`. */\n votingOptions?: UseRoadmapVotingOptions;\n /** Optional SSR hydrate (hub server-fetch) — skips the initial client fetch. */\n initialItems?: RoadmapItem[];\n /** Indent the grid (the hub's full-page roadmap look). Default off. */\n showLeftMargin?: boolean;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Override the hero title/subtitle (defaults from `OPENFRAME_DEV_SECTIONS`). */\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the list, inside the page chrome — e.g. the\n * hub's related-content rail. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function RoadmapPage({\n roadmapEndpoint,\n buildRefreshUrl,\n voteApiEndpoint,\n votingOptions,\n initialItems,\n showLeftMargin,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: RoadmapPageProps) {\n return (\n <DevSectionPage sectionKey=\"roadmap\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <RoadmapView\n endpoint={roadmapEndpoint}\n initialItems={initialItems}\n showLeftMargin={showLeftMargin}\n buildRefreshUrl={buildRefreshUrl}\n votingOptions={votingOptions ?? (voteApiEndpoint ? { voteApiEndpoint } : undefined)}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<ProductReleasesListPage>` — the full `/releases` list page: `DevSectionPage\n * sectionKey=\"releases\"` chrome wrapping the self-contained `<ProductReleasesView>`.\n * Hosts configure the api route + detail `basePath` (+ optional SSR `initialData`\n * and a custom card-prop builder). Card → detail nav flows through\n * `runtime.composeContentUrl`.\n */\n\nimport type { ReactNode } from 'react';\nimport type { ProductRelease, ProductReleaseListResponse } from '../../types/product-release';\nimport { DevSectionPage } from '../shared/dev-section';\nimport { ProductReleasesView, type ProductReleaseCardExtras } from '../shared/product-release';\n\nexport interface ProductReleasesListPageProps {\n /** GET list endpoint (the api route). Default `/api/releases`. */\n releasesEndpoint?: string;\n /** Fallback detail-href prefix when `composeContentUrl` is not wired. Default `/releases`. */\n basePath?: string;\n /** Derive the per-card prop bundle (defaults to the lib's rich builder). */\n buildCardProps?: (release: ProductRelease) => ProductReleaseCardExtras;\n /** Optional SSR hydrate for the first page (hub server-fetch). */\n initialData?: ProductReleaseListResponse;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the list, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function ProductReleasesListPage({\n releasesEndpoint,\n basePath,\n buildCardProps,\n initialData,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: ProductReleasesListPageProps) {\n return (\n <DevSectionPage sectionKey=\"releases\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <ProductReleasesView\n endpoint={releasesEndpoint}\n basePath={basePath}\n buildCardProps={buildCardProps}\n initialData={initialData}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<DeliveryPage>` — the full bug-fixes & enhancements (`delivery`) page:\n * `DevSectionPage sectionKey=\"delivery\"` chrome wrapping the self-contained\n * `<DeliveryLists>` (Recently Completed + Active Tasks tables). Hosts configure\n * only the two bucket api routes.\n */\n\nimport type { ReactNode } from 'react';\nimport { DeliveryLists } from '../shared/delivery';\nimport { DevSectionPage } from '../shared/dev-section';\n\nexport interface DeliveryPageProps {\n /** GET endpoint for the \"Recently Completed\" bucket. Default `/api/delivery/completed`. */\n completedEndpoint?: string;\n /** GET endpoint for the \"Active Tasks\" bucket. Default `/api/delivery/in-progress`. */\n inProgressEndpoint?: string;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the lists, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function DeliveryPage({\n completedEndpoint,\n inProgressEndpoint,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: DeliveryPageProps) {\n return (\n <DevSectionPage sectionKey=\"delivery\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <DeliveryLists completedApiEndpoint={completedEndpoint} inProgressApiEndpoint={inProgressEndpoint} />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<OnboardingGuidesCatalogPage>` — the full `/onboarding-guides` catalog page:\n * `DevSectionPage sectionKey=\"onboarding\"` chrome wrapping the self-contained\n * `<OnboardingGuidesCatalogView>` (RAG search + section pills + guide grid).\n * Supports both SSR (`initialGuides`/`initialSections`) and self-fetch\n * (`guidesEndpoint`/`sectionsEndpoint`) modes — forwarded to the view verbatim.\n */\n\nimport type { ReactNode } from 'react';\nimport type { OnboardingGuide } from '../chat/types/entities/onboarding-guide';\nimport { OnboardingGuidesCatalogView } from '../onboarding-guides';\nimport { DevSectionPage } from '../shared/dev-section';\n\ntype SectionSummary = { section: string; section_order: number; count: number };\n\nexport interface OnboardingGuidesCatalogPageProps {\n /** Self-fetch: GET list endpoint (the api route). Appends `?section=`. */\n guidesEndpoint?: string;\n /** Self-fetch: GET section-summary endpoint (the api route). */\n sectionsEndpoint?: string;\n /** Controlled / SSR: server-fetched guides + sections + active section. */\n initialGuides?: OnboardingGuide[];\n initialSections?: SectionSummary[];\n initialSection?: string;\n /** Base path the catalog is mounted under (card href prefix + `?section=` push). */\n basePath?: string;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the catalog, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function OnboardingGuidesCatalogPage({\n guidesEndpoint,\n sectionsEndpoint,\n initialGuides,\n initialSections,\n initialSection,\n basePath,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: OnboardingGuidesCatalogPageProps) {\n return (\n <DevSectionPage sectionKey=\"onboarding\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <OnboardingGuidesCatalogView\n guidesEndpoint={guidesEndpoint}\n sectionsEndpoint={sectionsEndpoint}\n initialGuides={initialGuides}\n initialSections={initialSections}\n initialSection={initialSection}\n basePath={basePath}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<TrustCenterPage>` — the full public trust center (Vanta-fed, hub-projected).\n *\n * ONE page, not tabs (2026 trust-center practice: buyers skim and Ctrl-F, and a\n * questionnaire needs a deep link per section): hero with the monitoring status\n * and the two actions, then anchored sections in reading order\n * (`TRUST_CENTER_SECTIONS`: AI & data use → compliance → controls → documents →\n * subprocessors → FAQ → questions) with a sticky section rail on desktop\n * (`StickySectionNav` + `useScrollSpy`, the vendor-page / DocViewer pattern).\n * Built ONLY from existing lib components (see `trust-center-sections.tsx`); the\n * chrome is the canonical `PageShell` + frozen `PageLayout`.\n *\n * DATA: `useSelfFetch` against `endpoint` (default `TRUST_CENTER_API_PATH`;\n * embedders pass their `/content` proxy path). `initialData` (hub SSR) skips the\n * first fetch; a visible tab re-validates after `TRUST_CENTER_CACHE_SECONDS`.\n * CONTROLS are answered by the server (`endpoint/controls?domain=&q=`) per tab\n * and per search; the page never filters, it only highlights what came back.\n *\n * MONITORING is derived on the client AFTER HYDRATION from `syncedAt` +\n * `monitoredWindowMs` (`isTrustCenterMonitored`), against a clock re-read every\n * time the data changes — so a long-open tab flips to \"Monitoring paused\" on\n * revalidation. Before hydration the status is NEUTRAL (never a claim that\n * flips). The subtitle is the brand-neutral `TRUST_CENTER_TAGLINE` unless the\n * host overrides it.\n */\n\nimport { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { useIsHydrated } from '../../hooks/ui/use-is-hydrated';\nimport { useScrollToHash } from '../../hooks/use-scroll-to-hash';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport {\n TRUST_CENTER_API_PATH,\n TRUST_CENTER_CACHE_SECONDS,\n TRUST_CENTER_SECTIONS,\n TRUST_CENTER_TAGLINE,\n TRUST_CENTER_TITLE,\n isTrustCenterMonitored,\n trustCenterDocumentUrl,\n type TrustCenterControlsPage,\n type TrustCenterPublic,\n type TrustCenterSectionId,\n} from '../../types/trust-center';\nimport { navigateSamePageHash, STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { useScrollSpy } from '../docs/use-scroll-spy';\nimport { FaqSection } from '../faq/faq-section';\nimport { PageShell } from '../layout/article-detail-layout';\nimport { PageLayout } from '../layout/page-layout';\nimport { StickySectionNav } from '../navigation/sticky-section-nav';\nimport { DataAttribution } from '../ui/data-attribution';\nimport { EntityImage } from '../ui/entity-image';\nimport { LoadError } from '../ui/error-state';\nimport { StatusIndicator } from '../ui/status-indicator';\nimport {\n AiSection,\n ComplianceSection,\n ContactSection,\n ControlsSection,\n DocumentRequestModal,\n DocumentsSection,\n SubprocessorsSection,\n TRUST_BODY_GRID_CLASS,\n TRUST_SECTIONS_COLUMN_CLASS,\n TRUST_SECTION_LEADS,\n TRUST_STATUS_ROW_CLASS,\n TrustCenterSkeleton,\n TrustSection,\n} from './trust-center-sections';\n\nexport interface TrustCenterPageProps {\n /** GET endpoint for the public projection. Default `TRUST_CENTER_API_PATH`. */\n endpoint?: string;\n /** Optional SSR hydrate (hub server-read) — skips the initial client fetch. */\n initialData?: TrustCenterPublic;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (e.g. openframe-frontend). */\n shell?: boolean;\n /** Back-button config. Default: none (a trust center is usually a top-level page). */\n backButton?: { label?: string; href?: string } | false;\n /** Page title. Default `TRUST_CENTER_TITLE`. */\n title?: string;\n /** Page subtitle. Default the brand-neutral `TRUST_CENTER_TAGLINE` (a host adds its brand here). */\n subtitle?: string;\n}\n\n/** One section's page content. Its `h2` is the `TRUST_CENTER_SECTIONS` label — never a literal here. */\ninterface TrustSectionView {\n lead?: string;\n render: (data: TrustCenterPublic) => ReactNode;\n}\n\n/** Sections that have content, in page order. */\nfunction visibleSections(data: TrustCenterPublic): Array<(typeof TRUST_CENTER_SECTIONS)[number]> {\n const hasContent: Record<TrustCenterSectionId, boolean> = {\n ai: data.aiPractices.length > 0,\n compliance: data.frameworks.length > 0,\n controls: data.controlDomains.length > 0,\n documents: data.documents.length > 0,\n subprocessors: data.subprocessors.length > 0,\n faq: data.faqs.length > 0,\n contact: true,\n };\n return TRUST_CENTER_SECTIONS.filter(section => hasContent[section.id]);\n}\n\n/**\n * The Controls section's first answer, from the page's own server data: the\n * first category's controls with no query — exactly what the controls endpoint\n * answers for it, so the first paint needs no request.\n */\nfunction controlsSeed(data: TrustCenterPublic): TrustCenterControlsPage {\n const first = data.controlDomains[0] ?? null;\n return {\n domain: first?.domain ?? null,\n query: '',\n categories: data.controlDomains.map(domain => ({ domain: domain.domain, count: domain.controls.length })),\n controls: first?.controls ?? [],\n total: data.controlDomains.reduce((sum, domain) => sum + domain.controls.length, 0),\n };\n}\n\n/** How often an open page re-judges \"monitored\" against the clock. */\nconst MONITORING_CLOCK_TICK_MS = 60_000;\n\n/**\n * The monitoring claim: neutral before mount, then monitored / paused / not\n * enabled. WHEN the data was last synced is the shared `DataAttribution` line\n * beside it, not repeated here.\n */\nfunction monitoringStatus(\n data: TrustCenterPublic,\n hydrated: boolean,\n nowMs: number,\n): { status: 'success' | 'pending' | 'missing'; label: string } {\n if (!data.connected) return { status: 'missing', label: 'Live control monitoring is not enabled yet' };\n // Before mount the clock is the server's, so the text claims nothing either way.\n if (!hydrated) return { status: 'missing', label: 'Checking monitoring status' };\n return isTrustCenterMonitored(data, nowMs)\n ? { status: 'success', label: 'Controls continuously monitored' }\n : { status: 'pending', label: 'Monitoring paused' };\n}\n\nexport function TrustCenterPage({\n endpoint = TRUST_CENTER_API_PATH,\n initialData,\n shell = true,\n backButton = false,\n title = TRUST_CENTER_TITLE,\n subtitle = TRUST_CENTER_TAGLINE,\n}: TrustCenterPageProps) {\n const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {\n initialData,\n revalidateOnVisibleAfterMs: TRUST_CENTER_CACHE_SECONDS * 1000,\n });\n // `false` during SSR + hydration, `true` after mount — gates everything clock-dependent.\n const hydrated = useIsHydrated();\n // The clock is re-read whenever the DATA changes (React's \"adjust state while\n // rendering\" pattern, keyed on the data identity): a revalidated copy is judged\n // against the time it arrived, not the time the tab was opened. Only read\n // after hydration (`monitoringStatus`), so the SSR value never reaches the DOM.\n const [clock, setClock] = useState(() => ({ data, nowMs: Date.now() }));\n if (clock.data !== data) {\n setClock(() => ({ data, nowMs: Date.now() }));\n }\n const nowMs = clock.nowMs;\n // A tab left open keeps judging the same copy against the passing time, so it\n // flips to \"Monitoring paused\" once `syncedAt` leaves the window.\n useEffect(() => {\n if (!hydrated) return undefined;\n const timer = setInterval(() => setClock(current => ({ ...current, nowMs: Date.now() })), MONITORING_CLOCK_TICK_MS);\n return () => clearInterval(timer);\n }, [hydrated]);\n const [request, setRequest] = useState<{ open: boolean; documentTitle: string | null }>({\n open: false,\n documentTitle: null,\n });\n\n const sections = useMemo(() => (data ? visibleSections(data) : []), [data]);\n // Anchors: the shared same-page hash navigation (the FAQ section's pattern).\n // A rail click puts `#section` in the URL (`replace`: the rail is a table of\n // contents, not a navigation step) and scrolls below the sticky header; a\n // visit that ARRIVES with `#section`, and back/forward, scroll there once the\n // sections have rendered. The scroll spy lights the rail and, as the reader\n // scrolls, keeps the URL's hash on the section in view (`syncHash`).\n const { activeSection } = useScrollSpy(sections, { syncHash: true });\n const handleSectionClick = useCallback((sectionId: string) => {\n navigateSamePageHash(`#${sectionId}`, { headerOffset: STICKY_HEADER_OFFSET_PX, history: 'replace' });\n }, []);\n useScrollToHash(sections.length > 0 ? sections.map(section => section.id).join('|') : null, {\n headerOffset: STICKY_HEADER_OFFSET_PX,\n });\n const openRequest = useCallback((documentTitle: string | null) => setRequest({ open: true, documentTitle }), []);\n const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []);\n\n const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false;\n const actions = hasGatedDocuments\n ? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }]\n : undefined;\n\n // Section id → its content. Titles come from `TRUST_CENTER_SECTIONS`; which\n // sections show comes from `visibleSections` — this map only says what each renders.\n const views: Record<TrustCenterSectionId, TrustSectionView> = {\n ai: {\n lead: TRUST_SECTION_LEADS.ai,\n render: d => <AiSection practices={d.aiPractices} />,\n },\n compliance: {\n lead: TRUST_SECTION_LEADS.compliance,\n render: d => <ComplianceSection frameworks={d.frameworks} />,\n },\n controls: {\n lead: TRUST_SECTION_LEADS.controls,\n render: d => <ControlsSection endpoint={endpoint} seed={controlsSeed(d)} />,\n },\n documents: {\n lead: TRUST_SECTION_LEADS.documents,\n render: d => (\n <DocumentsSection\n documents={d.documents}\n documentUrl={document => trustCenterDocumentUrl(endpoint, document)}\n onRequest={openRequest}\n />\n ),\n },\n subprocessors: {\n lead: TRUST_SECTION_LEADS.subprocessors,\n render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,\n },\n faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },\n contact: { render: () => <ContactSection onContact={() => openRequest(null)} /> },\n };\n\n let body: ReactNode;\n if (error && !data) {\n body = <LoadError message=\"Could not load the trust center\" onRetry={reload} />;\n } else if (!data) {\n body = isLoading ? <TrustCenterSkeleton /> : null;\n } else {\n const status = monitoringStatus(data, hydrated, nowMs);\n body = (\n <>\n <div className={TRUST_STATUS_ROW_CLASS}>\n <StatusIndicator status={status.status} label={status.label} />\n {data.connected ? (\n <DataAttribution\n icon={\n data.dataSource.logoUrl ? (\n <EntityImage\n src={data.dataSource.logoUrl}\n alt={data.dataSource.name}\n fallbackText={data.dataSource.name}\n sizeClassName=\"size-4\"\n />\n ) : undefined\n }\n source={data.dataSource.name}\n lastUpdated={data.syncedAt}\n />\n ) : null}\n </div>\n\n <div className={TRUST_BODY_GRID_CLASS}>\n <div className={TRUST_SECTIONS_COLUMN_CLASS}>\n {sections.map(section => {\n const view = views[section.id];\n return (\n <TrustSection key={section.id} id={section.id} title={section.label} lead={view.lead}>\n {view.render(data)}\n </TrustSection>\n );\n })}\n </div>\n\n <aside className=\"hidden lg:block\" aria-label=\"Trust center sections\">\n <div className=\"sticky\" style={{ top: STICKY_HEADER_OFFSET_PX }}>\n <StickySectionNav\n sections={sections.map(section => ({ id: section.id, label: section.label }))}\n activeSection={activeSection}\n onSectionClick={handleSectionClick}\n />\n </div>\n </aside>\n </div>\n\n <DocumentRequestModal\n open={request.open}\n documentTitle={request.documentTitle}\n category={data.documentRequestCategory}\n onClose={closeRequest}\n />\n </>\n );\n }\n\n return (\n <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton} actions={actions}>\n {body}\n </TrustCenterChrome>\n );\n}\n\n/**\n * The page's chrome, ONE copy for the page and its skeleton: the standalone\n * `PageShell` or the host's padded box (`shell`), and the frozen `PageLayout`\n * header (title, subtitle, back button, actions).\n */\nfunction TrustCenterChrome({\n shell,\n title,\n subtitle,\n backButton,\n actions,\n children,\n}: Required<Pick<TrustCenterPageProps, 'shell' | 'title' | 'subtitle' | 'backButton'>> & {\n actions?: Array<{ label: string; variant: 'accent'; onClick: () => void }>;\n children: ReactNode;\n}) {\n const router = useRouter();\n const backCfg =\n backButton === false\n ? undefined\n : { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };\n const inner = (\n <PageLayout\n title={title}\n subtitle={subtitle}\n backButton={backCfg}\n titleSize=\"h1\"\n titleWrap\n actions={actions}\n actionsVariant=\"primary-buttons\"\n >\n {children}\n </PageLayout>\n );\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n\n/**\n * The whole page while a host loads the data itself (the hub's admin preview):\n * the SAME chrome and the same body skeleton `TrustCenterPage` shows while it\n * self-fetches. Pass the chrome props the page will get.\n */\nexport function TrustCenterPageSkeleton({\n shell = true,\n backButton = false,\n title = TRUST_CENTER_TITLE,\n subtitle = TRUST_CENTER_TAGLINE,\n}: Pick<TrustCenterPageProps, 'shell' | 'backButton' | 'title' | 'subtitle'>) {\n return (\n <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton}>\n <TrustCenterSkeleton />\n </TrustCenterChrome>\n );\n}\n","'use client';\n\n/**\n * Section compositions for `<TrustCenterPage>`. Every visual element here is an\n * EXISTING shared lib component — no new primitive, no styled wrapper and no\n * intrinsic interactive/semantic HTML (`__tests__/trust-center-page.test.tsx`\n * statically enforces that). The only own markup is layout `div`/`section`s with\n * ODS spacing tokens, the same as `RoadmapPage` / `DeliveryPage`.\n *\n * The layout follows 2026 trust-center practice (Anthropic, OpenAI, Vanta,\n * Linear, Cursor, ElevenLabs): ONE page with a section rail, AI & data use first,\n * certified proof above roadmap items, every control of every category always\n * shown (no drawers, no \"view all\", no collapsing), and a controls SEARCH the\n * server answers as one flat, grouped, counted list.\n */\n\nimport { createElement, useState, type ReactNode } from 'react';\nimport { useDebounce } from '../../hooks/ui/use-debounce';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport {\n TRUST_CENTER_SEARCH_DEBOUNCE_MS,\n TRUST_CENTER_SECTIONS,\n normalizeTrustControlQuery,\n trustCenterControlsUrl,\n trustFrameworkBadge,\n trustFrameworkMonitoringEntry,\n type TrustCenterAiPractice,\n type TrustCenterControl,\n type TrustCenterControlsPage,\n type TrustCenterDocument,\n type TrustCenterFramework,\n type TrustCenterSectionId,\n type TrustCenterSubprocessor,\n type TrustFrameworkMonitoringEntry,\n} from '../../types/trust-center';\nimport { getFlagFromCountryName } from '../../utils/country-phone-utils';\nimport { STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { brandLogoForName } from '../chat/utils/icon-library';\nimport { ContactForm } from '../contact/contact-form';\nimport { FileIcon } from '../icons-v2-generated/documents/file-icon';\nimport { FileShieldIcon } from '../icons-v2-generated/documents/file-shield-icon';\nimport { BrainAIIcon } from '../icons-v2-generated/health/brain-ai-icon';\nimport { LockIcon } from '../icons-v2-generated/security/lock-icon';\nimport { ShieldCheckIcon } from '../icons-v2-generated/security/shield-check-icon';\nimport { CheckIcon } from '../icons-v2-generated/signs-and-symbols/check-icon';\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading';\nimport { ListEmptyState } from '../list-empty-state';\nimport { TextSkeleton, UnifiedSkeleton } from '../loading/unified-skeleton';\nimport { Button } from '../ui/button/button';\nimport { ComplianceLogo } from '../ui/compliance-badge';\nimport { EntityImage } from '../ui/entity-image';\nimport { LoadError } from '../ui/error-state';\nimport { ModalV2, ModalV2Content, ModalV2Header, ModalV2Title } from '../ui/modal-v2';\nimport { ScrollShadow } from '../ui/scroll-fade';\nimport { SearchInput } from '../ui/search-input';\nimport { StackedRowsPanel, type PanelRow } from '../ui/stacked-rows-panel';\nimport { StatusBadge } from '../ui/status-badge';\nimport { TabNavigation, type TabItem } from '../ui/tab-navigation';\n\n/** Monitoring `icon` vocabulary (owned by `TRUST_FRAMEWORK_MONITORING`) → icon component, ONE lookup. */\nconst FRAMEWORK_ICONS: Record<TrustFrameworkMonitoringEntry['icon'], typeof ShieldCheckIcon> = {\n 'shield-check': ShieldCheckIcon,\n 'file-shield': FileShieldIcon,\n};\n\nconst BODY_TEXT = 'text-h6 text-ods-text-secondary';\n\n/**\n * The page body's layout, ONE copy for the loaded page and its skeleton, so the\n * skeleton cannot drift from what replaces it: the status row, then the sections\n * beside the section rail.\n */\nexport const TRUST_STATUS_ROW_CLASS =\n 'flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between';\nexport const TRUST_BODY_GRID_CLASS =\n 'grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]';\nexport const TRUST_SECTIONS_COLUMN_CLASS = 'flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]';\n\n/** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */\nexport const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>> = {\n compliance: 'Frameworks we are audited against, and what is next.',\n controls: 'Security controls currently passing in continuous monitoring (via Vanta).',\n documents: 'Public documents open directly. Gated documents are shared under NDA after one short request.',\n subprocessors: 'Third parties that process customer data on our behalf.',\n ai: 'How customer data is handled by the AI in our products.',\n};\n/**\n * The right-hand action / status column: ONE width from tablet up, so every\n * panel's rows line up; on phones it hugs its content so the title keeps the room.\n */\nconst ACTION_COLUMN = 'shrink-0 md:w-36';\n\n// ---------------------------------------------------------------------------\n// Section frame\n// ---------------------------------------------------------------------------\n\n/** One anchored page section: an `h2` + optional lead line + content. */\nexport function TrustSection({\n id,\n title,\n lead,\n children,\n}: {\n id: string;\n title: string;\n lead?: ReactNode;\n children: ReactNode;\n}) {\n const headingId = `${id}-heading`;\n return (\n <section\n id={id}\n aria-labelledby={headingId}\n className=\"flex flex-col gap-[var(--spacing-system-m)]\"\n style={{ scrollMarginTop: STICKY_HEADER_OFFSET_PX }}\n >\n <div className=\"flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]\">\n <h2 id={headingId} className={SECTION_HEADING_CLASS}>\n {title}\n </h2>\n {lead ? <p className={BODY_TEXT}>{lead}</p> : null}\n </div>\n {children}\n </section>\n );\n}\n\n// ---------------------------------------------------------------------------\n// AI & data use\n// ---------------------------------------------------------------------------\n\n/**\n * The AI statements — the company's Vanta Trust Center FAQs in its AI category,\n * in Vanta's order: the question as the row title, the answer beneath. The\n * first is the commitment (success shield). The page's ONE row style.\n */\nexport function AiSection({ practices }: { practices: TrustCenterAiPractice[] }) {\n const rows: PanelRow[] = practices.map(practice => ({\n id: practice.label,\n columns: [\n {\n key: 'practice',\n leadingIcon: practice.commitment ? (\n <ShieldCheckIcon className=\"text-ods-success\" role=\"img\" aria-label=\"Commitment\" />\n ) : (\n <BrainAIIcon aria-hidden=\"true\" />\n ),\n value: practice.label,\n label: practice.value,\n wrap: true,\n },\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Compliance\n// ---------------------------------------------------------------------------\n\n/**\n * One row per framework, in the Vanta Trust Center's order: its name, its\n * Vanta description, and ONE badge: Vanta's completion (\"16% complete\") or\n * \"Planned\" — never a certification claim, which Vanta's API does\n * not hold.\n */\nexport function ComplianceSection({ frameworks }: { frameworks: TrustCenterFramework[] }) {\n const rows: PanelRow[] = frameworks.map(framework => {\n const entry = trustFrameworkMonitoringEntry(framework.monitoring);\n const Icon = FRAMEWORK_ICONS[entry.icon];\n return {\n id: framework.id,\n columns: [\n {\n key: 'framework',\n leadingIcon: (\n <ComplianceLogo names={[framework.standard, framework.label]} fallback={<Icon aria-hidden=\"true\" />} />\n ),\n value: framework.label,\n label: framework.description ?? undefined,\n wrap: true,\n },\n {\n key: 'monitoring',\n width: ACTION_COLUMN,\n align: 'right',\n content: <StatusBadge text={trustFrameworkBadge(framework)} colorScheme={entry.color} singleLine />,\n },\n ],\n };\n });\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Every control row is ONE fixed height (one line of name, one of description),\n * so the list box shows exactly 5 of them and scrolls for\n * the rest.\n */\nconst CONTROL_ROW_CLASS = 'h-14 md:h-20 overflow-hidden';\n\n/** ONE row renderer for the controls list: fixed-height rows, one line each for name and description. */\nfunction controlRows(controls: TrustCenterControl[]): PanelRow[] {\n return controls.map(control => ({\n id: control.id,\n className: CONTROL_ROW_CLASS,\n columns: [\n {\n key: 'control',\n leadingIcon: <CheckIcon className=\"text-ods-success\" role=\"img\" aria-label=\"Passing\" />,\n value: control.name,\n label: control.description ?? undefined,\n },\n ],\n }));\n}\n\nfunction countLabel(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? '' : 's'}`;\n}\n\n/**\n * The list box: exactly 5 control rows (`CONTROL_ROW_CLASS`: 3.5rem, 5rem from\n * md) plus their 1px separators and the panel's border. Controls, loading,\n * empty and error all fill this same box, so nothing below it moves.\n */\nconst CONTROLS_LIST_HEIGHT = 'h-[calc(5*3.5rem+7px)] md:h-[calc(5*5rem+7px)]';\n\n/** Skeleton rows shown in the list box while the server's answer is on its way: exactly the box's visible rows. */\nconst CONTROLS_SKELETON_ROWS = 5;\n\nfunction controlsTabId(domain: string): string {\n return `domain-${domain.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;\n}\n\nfunction ControlsListSkeleton() {\n const rows: PanelRow[] = Array.from({ length: CONTROLS_SKELETON_ROWS }, (_, index) => ({\n id: `skeleton-${index}`,\n className: CONTROL_ROW_CLASS,\n columns: [\n {\n key: 'control',\n content: (\n <div className=\"flex w-full flex-col gap-[var(--spacing-system-xs)]\">\n <TextSkeleton.Body className=\"w-2/3\" />\n <TextSkeleton.Caption className=\"w-full\" />\n </div>\n ),\n },\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n/**\n * Controls — 100% SERVER-driven. Every tab switch and every (debounced) search\n * is ONE request to the controls endpoint (`trustCenterControlsUrl`), which\n * answers the categories with their counts, the category shown and its\n * controls; the section only renders that answer. One tab per category (no\n * \"All\"), the search and the summary line ABOVE a fixed-height list box that\n * scrolls on its own — loading (skeleton rows), empty and error states fill the\n * same box, so the page never jumps.\n *\n * `seed` is the server-rendered answer for the first category with no query\n * (the page's own data), so the first paint needs no request.\n */\nexport function ControlsSection({ endpoint, seed }: { endpoint: string; seed: TrustCenterControlsPage }) {\n const [domain, setDomain] = useState<string | null>(seed.domain);\n const [typed, setTyped] = useState('');\n const query = useDebounce(normalizeTrustControlQuery(typed), TRUST_CENTER_SEARCH_DEBOUNCE_MS);\n const url = trustCenterControlsUrl(endpoint, { domain, query });\n const seedUrl = trustCenterControlsUrl(endpoint, { domain: seed.domain, query: '' });\n const answer = useSelfFetch<TrustCenterControlsPage>(url, url === seedUrl ? { initialData: seed } : undefined);\n\n // The answer counts only when it answers what is on screen NOW (the typed\n // query, the selected tab); otherwise the box shows its loading state.\n const typedQuery = normalizeTrustControlQuery(typed);\n const current =\n answer.data && !answer.isLoading && typedQuery === query && answer.data.query === query ? answer.data : null;\n // Tabs keep the last categories the server sent while a new answer loads.\n const [categories, setCategories] = useState(seed.categories);\n if (current && current.categories !== categories) setCategories(current.categories);\n const total = current?.total ?? seed.total;\n\n const tabs: TabItem[] = categories.map(category => ({\n id: controlsTabId(category.domain),\n label: `${category.domain} · ${category.count}`,\n }));\n const activeDomain = current?.domain ?? domain;\n const activeTab = activeDomain ? controlsTabId(activeDomain) : '';\n\n let summary: string;\n if (answer.error && !current) summary = 'Controls are unavailable';\n else if (!current) summary = typedQuery ? `Searching for “${typedQuery}”…` : 'Loading…';\n else if (query)\n summary = `${current.controls.length} in ${current.domain} match “${query}” · ${countLabel(total, 'control')} in total`;\n else summary = `${countLabel(current.controls.length, 'passing control')} in ${current.domain}`;\n\n let body: ReactNode;\n if (answer.error && !current) {\n body = <LoadError message=\"Could not load the controls\" onRetry={answer.reload} />;\n } else if (!current) {\n body = <ControlsListSkeleton />;\n } else if (current.controls.length === 0) {\n body = (\n <ListEmptyState\n isFiltered\n filtered={{\n title: 'No matching controls',\n description: 'Try another word, or another category.',\n clearText: 'Clear search',\n }}\n onClearFilters={() => setTyped('')}\n empty={{ title: 'No controls', description: '' }}\n />\n );\n } else {\n body = <StackedRowsPanel rows={controlRows(current.controls)} />;\n }\n\n return (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <TabNavigation\n tabs={tabs}\n activeTab={activeTab}\n onTabChange={tabId =>\n setDomain(categories.find(category => controlsTabId(category.domain) === tabId)?.domain ?? null)\n }\n />\n <div className=\"flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between\">\n <p className={BODY_TEXT} aria-live=\"polite\">\n {summary}\n </p>\n <div className=\"w-full md:w-80\">\n <SearchInput\n placeholder=\"Search controls\"\n value={typed}\n onChange={setTyped}\n debounceMs={0}\n showDropdown={false}\n />\n </div>\n </div>\n <ScrollShadow className={CONTROLS_LIST_HEIGHT} scrollClassName=\"h-full\" data-testid=\"controls-list\">\n {body}\n </ScrollShadow>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Documents\n// ---------------------------------------------------------------------------\n\nexport function DocumentsSection({\n documents,\n documentUrl,\n onRequest,\n}: {\n documents: TrustCenterDocument[];\n /** Where a public document opens (`trustCenterDocumentUrl`); `null` → offer a request instead. */\n documentUrl: (document: TrustCenterDocument) => string | null;\n /** Open the ONE access request, optionally for a named document. */\n onRequest: (documentTitle: string | null) => void;\n}) {\n const rows: PanelRow[] = documents.map(document => {\n const gated = document.access === 'request';\n const href = gated ? null : documentUrl(document);\n return {\n id: document.id,\n columns: [\n {\n key: 'document',\n leadingIcon: gated ? (\n <LockIcon role=\"img\" aria-label=\"Available on request\" />\n ) : (\n <FileIcon role=\"img\" aria-label=\"Public\" />\n ),\n value: document.title,\n label: document.description ?? (gated ? 'Available on request' : 'Public'),\n wrap: true,\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content:\n href === null ? (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() => onRequest(document.title)}\n aria-label={`Request ${document.title}`}\n >\n Request\n </Button>\n ) : (\n <Button variant=\"outline\" size=\"small\" href={href} openInNewTab aria-label={`View ${document.title}`}>\n View\n </Button>\n ),\n },\n ],\n };\n });\n return <StackedRowsPanel rows={rows} />;\n}\n\n/**\n * The ONE access request (the existing contact flow, `/api/contact`, kept on the\n * page). Only name, email and a pre-filled message: no marketing questions.\n */\nexport function DocumentRequestModal({\n open,\n documentTitle,\n category,\n onClose,\n}: {\n open: boolean;\n /** The serving platform's help category for these requests (`TrustCenterPublic.documentRequestCategory`). */\n category: string;\n /** The document the request started from, or null for \"all gated documents\". */\n documentTitle: string | null;\n onClose: () => void;\n}) {\n const message = documentTitle\n ? `I'd like access to: ${documentTitle}.`\n : \"I'd like access to your gated security documentation.\";\n return (\n <ModalV2 isOpen={open} onClose={onClose} size=\"medium\">\n <ModalV2Header>\n <ModalV2Title>Request access</ModalV2Title>\n </ModalV2Header>\n <ModalV2Content>\n {open ? (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <p className={BODY_TEXT}>\n We share gated security documents under NDA. Leave your work email and our security team will follow up\n with access, usually within one business day.\n </p>\n <ContactForm\n title=\"\"\n noBorder\n noPadding\n hideFields={['companySize', 'referralSource', 'helpCategory']}\n defaultValues={{ helpCategory: category, message }}\n submitLabel=\"Request access\"\n successRedirectUrl=\"\"\n successToastMessage=\"Thanks — our security team will follow up by email.\"\n onSubmitSuccess={onClose}\n />\n </div>\n ) : null}\n </ModalV2Content>\n </ModalV2>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Subprocessors\n// ---------------------------------------------------------------------------\n\n/**\n * A subprocessor's mark, best source first: its own site's icon (the hub looks\n * it up from the website Vanta holds), else the brand logo our icon set has for\n * its name (`brandLogoForName`), else initials — which are also what a site\n * icon that fails to load falls back to.\n */\nfunction subprocessorMark({ name, logoUrl }: TrustCenterSubprocessor): ReactNode {\n if (logoUrl) return <EntityImage src={logoUrl} alt={name} fallbackText={name} sizeClassName=\"size-6\" />;\n const logo = brandLogoForName(name);\n return logo ? (\n <span role=\"img\" aria-label={name} className=\"flex\">\n {createElement(logo, { className: 'size-6' })}\n </span>\n ) : (\n <EntityImage alt={name} fallbackText={name} sizeClassName=\"size-6\" />\n );\n}\n\n/** \"🇺🇸 United States\": the flag from the shared country lookup beside the name; the name alone when unknown. */\nfunction countryWithFlag(country: string): string {\n const flag = getFlagFromCountryName(country);\n return flag ? `${flag} ${country}` : country;\n}\n\nexport function SubprocessorsSection({ subprocessors }: { subprocessors: TrustCenterSubprocessor[] }) {\n // The location column exists only when Vanta holds a location for at least one of them.\n const anyLocation = subprocessors.some(subprocessor => subprocessor.location);\n const rows: PanelRow[] = subprocessors.map(subprocessor => ({\n id: subprocessor.name,\n columns: [\n {\n key: 'name',\n leadingIcon: subprocessorMark(subprocessor),\n value: subprocessor.name,\n label: subprocessor.purpose ?? subprocessor.description ?? undefined,\n href: subprocessor.url ?? undefined,\n wrap: true,\n },\n ...(anyLocation\n ? [\n {\n key: 'location',\n value: subprocessor.location ? countryWithFlag(subprocessor.location) : '—',\n label: 'Location',\n hideAt: 'md' as const,\n width: 'w-48 shrink-0',\n },\n ]\n : []),\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Contact + loading\n// ---------------------------------------------------------------------------\n\n/**\n * Security & compliance questions, routed through the page's ONE request flow\n * (`/api/contact`) — no address is published and no disclosure programme is\n * advertised (industry practice for trust portals: questions go through the\n * account relationship, not an open inbox).\n */\nexport function ContactSection({ onContact }: { onContact: () => void }) {\n return (\n <StackedRowsPanel\n rows={[\n {\n id: 'questions',\n columns: [\n {\n key: 'questions',\n leadingIcon: <FileShieldIcon aria-hidden=\"true\" />,\n value: 'Security questions',\n label:\n 'Customers and prospective customers can reach our security team for questionnaires, documentation or a review call.',\n wrap: true,\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content: (\n <Button variant=\"outline\" size=\"small\" onClick={onContact}>\n Get in touch\n </Button>\n ),\n },\n ],\n },\n ]}\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\n/** A section's heading and lead, exactly as `TrustSection` renders them (real text: it does not load). */\nfunction SkeletonSection({ id, children }: { id: TrustCenterSectionId; children: ReactNode }) {\n const label = TRUST_CENTER_SECTIONS.find(section => section.id === id)?.label ?? id;\n const lead = TRUST_SECTION_LEADS[id];\n return (\n <section aria-hidden=\"true\" className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <div className=\"flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]\">\n <h2 className={SECTION_HEADING_CLASS}>{label}</h2>\n {lead ? <p className={BODY_TEXT}>{lead}</p> : null}\n </div>\n {children}\n </section>\n );\n}\n\n/**\n * Rows of the REAL `StackedRowsPanel` (its own row heights and borders) whose\n * cells hold bars: a mark (a 40px compliance logo or a 24px glyph), a value over\n * a label, and the right-hand badge or button in the page's `ACTION_COLUMN`.\n */\nfunction skeletonRows(count: number, shape: { mark: 'logo' | 'glyph'; action: 'badge' | 'button' }): PanelRow[] {\n return Array.from({ length: count }, (_, index) => ({\n id: `skeleton-${index}`,\n columns: [\n {\n key: 'item',\n content: (\n <div className=\"flex w-full items-center gap-[var(--spacing-system-s)]\">\n <UnifiedSkeleton\n className={shape.mark === 'logo' ? 'size-10 shrink-0 rounded-full' : 'size-6 shrink-0 rounded'}\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]\">\n <UnifiedSkeleton variant=\"text\" className=\"h-5 w-1/3 md:h-6\" />\n <TextSkeleton.Body className=\"w-1/2\" />\n </div>\n </div>\n ),\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content:\n shape.action === 'badge' ? (\n <UnifiedSkeleton className=\"h-4 w-28 rounded\" />\n ) : (\n <UnifiedSkeleton className=\"h-6 w-16 rounded md:h-8\" />\n ),\n },\n ],\n }));\n}\n\n/** The Controls section while loading: its tab strip, its summary and search, and the list box's own skeleton. */\nfunction ControlsSectionSkeleton() {\n return (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <div className=\"flex items-center gap-[var(--spacing-system-xxs)] overflow-hidden\">\n {[0, 1, 2, 3].map(index => (\n <div key={index} className=\"shrink-0 p-[var(--spacing-system-m)]\">\n <UnifiedSkeleton variant=\"text\" className=\"h-5 w-36 md:h-6\" />\n </div>\n ))}\n </div>\n <div className=\"flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between\">\n <TextSkeleton.Body className=\"w-48\" />\n <UnifiedSkeleton className=\"h-11 w-full rounded-[6px] md:h-12 md:w-80\" />\n </div>\n <div className={CONTROLS_LIST_HEIGHT}>\n <ControlsListSkeleton />\n </div>\n </div>\n );\n}\n\n/** The section rail while loading: `StickySectionNav`'s line and its 40px items. */\nfunction SectionRailSkeleton() {\n return (\n <div className=\"sticky\" style={{ top: STICKY_HEADER_OFFSET_PX }}>\n <div className=\"relative\">\n <div\n className=\"absolute -left-0.5 top-0 w-px bg-ods-border\"\n style={{ height: TRUST_CENTER_SECTIONS.length * 40 }}\n />\n {TRUST_CENTER_SECTIONS.map(section => (\n <div key={section.id} className=\"flex h-10 items-center px-3 py-2\">\n <TextSkeleton.Body className=\"w-24\" />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * The page BODY while the trust center loads: built on the loaded body's own\n * layout classes and the real section headings and panels, so nothing moves when\n * the content replaces it. The status row, then Compliance, Controls and\n * Documents (what fills the first screens), beside the section rail.\n */\nexport function TrustCenterSkeleton() {\n return (\n <>\n <div className={TRUST_STATUS_ROW_CLASS} aria-hidden=\"true\">\n <div className=\"flex items-center gap-1.5\">\n <UnifiedSkeleton className=\"size-3 rounded-full\" />\n <TextSkeleton.Body className=\"w-44\" />\n </div>\n {/* `DataAttribution`'s own layout: two lines below `sm`, one row above it. */}\n <div className=\"flex shrink-0 flex-col gap-[var(--spacing-system-xxs)] sm:flex-row sm:items-center sm:gap-[var(--spacing-system-sf)]\">\n <div className=\"flex items-center gap-[var(--spacing-system-xsf)]\">\n <UnifiedSkeleton className=\"size-4 rounded\" />\n <TextSkeleton.Body className=\"w-36\" />\n </div>\n <TextSkeleton.Body className=\"w-44\" />\n </div>\n </div>\n <div className={TRUST_BODY_GRID_CLASS} aria-busy=\"true\" aria-label=\"Loading the trust center\">\n <div className={TRUST_SECTIONS_COLUMN_CLASS}>\n <SkeletonSection id=\"compliance\">\n <StackedRowsPanel rows={skeletonRows(3, { mark: 'logo', action: 'badge' })} />\n </SkeletonSection>\n <SkeletonSection id=\"controls\">\n <ControlsSectionSkeleton />\n </SkeletonSection>\n <SkeletonSection id=\"documents\">\n <StackedRowsPanel rows={skeletonRows(2, { mark: 'glyph', action: 'button' })} />\n </SkeletonSection>\n </div>\n <aside className=\"hidden lg:block\" aria-hidden=\"true\">\n <SectionRailSkeleton />\n </aside>\n </div>\n </>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/components/help-center-pages/index.cjs","../../../src/components/help-center-pages/roadmap-page.tsx","../../../src/components/help-center-pages/product-releases-list-page.tsx","../../../src/components/help-center-pages/delivery-page.tsx","../../../src/components/help-center-pages/onboarding-guides-catalog-page.tsx","../../../src/components/help-center-pages/trust-center-page.tsx","../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":["jsxs","jsx","Fragment"],"mappings":"AAAA,6rBAAY;AACZ,YAAY;AACZ;AACE;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACA;AACA;AACF,4DAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACE;AACF,4DAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC,oCAAiC;AACjC;AACA;ACpEI,+CAAA;AAdG,SAAS,WAAA,CAAY;AAAA,EAC1B,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,uBACE,8BAAA,gCA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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/components/help-center-pages/index.cjs","sourcesContent":[null,"'use client';\n\n/**\n * `<RoadmapPage>` — the full `/roadmap` page: `DevSectionPage sectionKey=\"roadmap\"`\n * (PageShell + PageLayout + hero + search/status chrome) wrapping the\n * self-contained `<RoadmapView>` list. Hosts configure only the api routes\n * (+ optional SSR `initialItems`); both openframe-frontend and the hub mount\n * this instead of hand-composing `DevSectionPage` + `RoadmapView`.\n */\n\nimport type { ReactNode } from 'react';\nimport type { RoadmapItem } from '../chat/types/entities/roadmap-item';\nimport { DevSectionPage } from '../shared/dev-section';\nimport { RoadmapView } from '../shared/roadmap';\nimport type { UseRoadmapVotingOptions } from '../shared/roadmap';\n\nexport interface RoadmapPageProps {\n /** GET list endpoint (the api route). Default `/api/roadmap`. */\n roadmapEndpoint?: string;\n /** Per-task refresh URL builder (after a vote). Default `/api/roadmap/<id>`. */\n buildRefreshUrl?: (taskId: string) => string;\n /** Vote POST endpoint, forwarded to `RoadmapView` via `votingOptions`. */\n voteApiEndpoint?: string;\n /** Full voting options override — takes precedence over `voteApiEndpoint`. */\n votingOptions?: UseRoadmapVotingOptions;\n /** Optional SSR hydrate (hub server-fetch) — skips the initial client fetch. */\n initialItems?: RoadmapItem[];\n /** Indent the grid (the hub's full-page roadmap look). Default off. */\n showLeftMargin?: boolean;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Override the hero title/subtitle (defaults from `OPENFRAME_DEV_SECTIONS`). */\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the list, inside the page chrome — e.g. the\n * hub's related-content rail. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function RoadmapPage({\n roadmapEndpoint,\n buildRefreshUrl,\n voteApiEndpoint,\n votingOptions,\n initialItems,\n showLeftMargin,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: RoadmapPageProps) {\n return (\n <DevSectionPage sectionKey=\"roadmap\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <RoadmapView\n endpoint={roadmapEndpoint}\n initialItems={initialItems}\n showLeftMargin={showLeftMargin}\n buildRefreshUrl={buildRefreshUrl}\n votingOptions={votingOptions ?? (voteApiEndpoint ? { voteApiEndpoint } : undefined)}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<ProductReleasesListPage>` — the full `/releases` list page: `DevSectionPage\n * sectionKey=\"releases\"` chrome wrapping the self-contained `<ProductReleasesView>`.\n * Hosts configure the api route + detail `basePath` (+ optional SSR `initialData`\n * and a custom card-prop builder). Card → detail nav flows through\n * `runtime.composeContentUrl`.\n */\n\nimport type { ReactNode } from 'react';\nimport type { ProductRelease, ProductReleaseListResponse } from '../../types/product-release';\nimport { DevSectionPage } from '../shared/dev-section';\nimport { ProductReleasesView, type ProductReleaseCardExtras } from '../shared/product-release';\n\nexport interface ProductReleasesListPageProps {\n /** GET list endpoint (the api route). Default `/api/releases`. */\n releasesEndpoint?: string;\n /** Fallback detail-href prefix when `composeContentUrl` is not wired. Default `/releases`. */\n basePath?: string;\n /** Derive the per-card prop bundle (defaults to the lib's rich builder). */\n buildCardProps?: (release: ProductRelease) => ProductReleaseCardExtras;\n /** Optional SSR hydrate for the first page (hub server-fetch). */\n initialData?: ProductReleaseListResponse;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the list, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function ProductReleasesListPage({\n releasesEndpoint,\n basePath,\n buildCardProps,\n initialData,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: ProductReleasesListPageProps) {\n return (\n <DevSectionPage sectionKey=\"releases\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <ProductReleasesView\n endpoint={releasesEndpoint}\n basePath={basePath}\n buildCardProps={buildCardProps}\n initialData={initialData}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<DeliveryPage>` — the full bug-fixes & enhancements (`delivery`) page:\n * `DevSectionPage sectionKey=\"delivery\"` chrome wrapping the self-contained\n * `<DeliveryLists>` (Recently Completed + Active Tasks tables). Hosts configure\n * only the two bucket api routes.\n */\n\nimport type { ReactNode } from 'react';\nimport { DeliveryLists } from '../shared/delivery';\nimport { DevSectionPage } from '../shared/dev-section';\n\nexport interface DeliveryPageProps {\n /** GET endpoint for the \"Recently Completed\" bucket. Default `/api/delivery/completed`. */\n completedEndpoint?: string;\n /** GET endpoint for the \"Active Tasks\" bucket. Default `/api/delivery/in-progress`. */\n inProgressEndpoint?: string;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the lists, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function DeliveryPage({\n completedEndpoint,\n inProgressEndpoint,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: DeliveryPageProps) {\n return (\n <DevSectionPage sectionKey=\"delivery\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <DeliveryLists completedApiEndpoint={completedEndpoint} inProgressApiEndpoint={inProgressEndpoint} />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<OnboardingGuidesCatalogPage>` — the full `/onboarding-guides` catalog page:\n * `DevSectionPage sectionKey=\"onboarding\"` chrome wrapping the self-contained\n * `<OnboardingGuidesCatalogView>` (RAG search + section pills + guide grid).\n * Supports both SSR (`initialGuides`/`initialSections`) and self-fetch\n * (`guidesEndpoint`/`sectionsEndpoint`) modes — forwarded to the view verbatim.\n */\n\nimport type { ReactNode } from 'react';\nimport type { OnboardingGuide } from '../chat/types/entities/onboarding-guide';\nimport { OnboardingGuidesCatalogView } from '../onboarding-guides';\nimport { DevSectionPage } from '../shared/dev-section';\n\ntype SectionSummary = { section: string; section_order: number; count: number };\n\nexport interface OnboardingGuidesCatalogPageProps {\n /** Self-fetch: GET list endpoint (the api route). Appends `?section=`. */\n guidesEndpoint?: string;\n /** Self-fetch: GET section-summary endpoint (the api route). */\n sectionsEndpoint?: string;\n /** Controlled / SSR: server-fetched guides + sections + active section. */\n initialGuides?: OnboardingGuide[];\n initialSections?: SectionSummary[];\n initialSection?: string;\n /** Base path the catalog is mounted under (card href prefix + `?section=` push). */\n basePath?: string;\n /** Back-button config. Pass `false` to hide. Default `{ href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n title?: string;\n subtitle?: string;\n /** Optional slot rendered below the catalog, inside the page chrome. */\n belowContent?: ReactNode;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (forwarded to `DevSectionPage`). */\n shell?: boolean;\n}\n\nexport function OnboardingGuidesCatalogPage({\n guidesEndpoint,\n sectionsEndpoint,\n initialGuides,\n initialSections,\n initialSection,\n basePath,\n backButton,\n title,\n subtitle,\n belowContent,\n shell,\n}: OnboardingGuidesCatalogPageProps) {\n return (\n <DevSectionPage sectionKey=\"onboarding\" backButton={backButton} title={title} subtitle={subtitle} shell={shell}>\n <OnboardingGuidesCatalogView\n guidesEndpoint={guidesEndpoint}\n sectionsEndpoint={sectionsEndpoint}\n initialGuides={initialGuides}\n initialSections={initialSections}\n initialSection={initialSection}\n basePath={basePath}\n />\n {belowContent}\n </DevSectionPage>\n );\n}\n","'use client';\n\n/**\n * `<TrustCenterPage>` — the full public trust center (Vanta-fed, hub-projected).\n *\n * ONE page, not tabs (2026 trust-center practice: buyers skim and Ctrl-F, and a\n * questionnaire needs a deep link per section): hero with the monitoring status\n * and the two actions, then anchored sections in reading order\n * (`TRUST_CENTER_SECTIONS`: AI & data use → compliance → controls → documents →\n * subprocessors → FAQ → questions) with a sticky section rail on desktop\n * (`StickySectionNav` + `useScrollSpy`, the vendor-page / DocViewer pattern).\n * Built ONLY from existing lib components (see `trust-center-sections.tsx`); the\n * chrome is the canonical `PageShell` + frozen `PageLayout`.\n *\n * DATA: `useSelfFetch` against `endpoint` (default `TRUST_CENTER_API_PATH`;\n * embedders pass their `/content` proxy path). `initialData` (hub SSR) skips the\n * first fetch; a visible tab re-validates after `TRUST_CENTER_CACHE_SECONDS`.\n * CONTROLS are answered by the server (`endpoint/controls?domain=&q=`) per tab\n * and per search; the page never filters, it only highlights what came back.\n *\n * MONITORING is derived on the client AFTER HYDRATION from `syncedAt` +\n * `monitoredWindowMs` (`isTrustCenterMonitored`), against a clock re-read every\n * time the data changes — so a long-open tab flips to \"Monitoring paused\" on\n * revalidation. Before hydration the status is NEUTRAL (never a claim that\n * flips). The subtitle is the brand-neutral `TRUST_CENTER_TAGLINE` unless the\n * host overrides it.\n */\n\nimport { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { useIsHydrated } from '../../hooks/ui/use-is-hydrated';\nimport { useScrollToHash } from '../../hooks/use-scroll-to-hash';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport {\n TRUST_CENTER_API_PATH,\n TRUST_CENTER_CACHE_SECONDS,\n TRUST_CENTER_SECTIONS,\n TRUST_CENTER_TAGLINE,\n TRUST_CENTER_TITLE,\n isTrustCenterMonitored,\n trustCenterDocumentUrl,\n type TrustCenterControlsPage,\n type TrustCenterPublic,\n type TrustCenterSectionId,\n} from '../../types/trust-center';\nimport { navigateSamePageHash, STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { useScrollSpy } from '../docs/use-scroll-spy';\nimport { FaqSection } from '../faq/faq-section';\nimport { PageShell } from '../layout/article-detail-layout';\nimport { PageLayout } from '../layout/page-layout';\nimport { StickySectionNav } from '../navigation/sticky-section-nav';\nimport { DataAttribution } from '../ui/data-attribution';\nimport { EntityImage } from '../ui/entity-image';\nimport { LoadError } from '../ui/error-state';\nimport { StatusIndicator } from '../ui/status-indicator';\nimport {\n AiSection,\n ComplianceSection,\n ContactSection,\n ControlsSection,\n DocumentRequestModal,\n DocumentsSection,\n SubprocessorsSection,\n TRUST_BODY_GRID_CLASS,\n TRUST_SECTIONS_COLUMN_CLASS,\n TRUST_SECTION_LEADS,\n TRUST_STATUS_ROW_CLASS,\n TrustCenterSkeleton,\n TrustSection,\n} from './trust-center-sections';\n\nexport interface TrustCenterPageProps {\n /** GET endpoint for the public projection. Default `TRUST_CENTER_API_PATH`. */\n endpoint?: string;\n /** Optional SSR hydrate (hub server-read) — skips the initial client fetch. */\n initialData?: TrustCenterPublic;\n /** Render the standalone `<PageShell>`. Default true. Pass false when the host\n * layout already provides the page container (e.g. openframe-frontend). */\n shell?: boolean;\n /** Back-button config. Default: none (a trust center is usually a top-level page). */\n backButton?: { label?: string; href?: string } | false;\n /** Page title. Default `TRUST_CENTER_TITLE`. */\n title?: string;\n /** Page subtitle. Default the brand-neutral `TRUST_CENTER_TAGLINE` (a host adds its brand here). */\n subtitle?: string;\n}\n\n/** One section's page content. Its `h2` is the `TRUST_CENTER_SECTIONS` label — never a literal here. */\ninterface TrustSectionView {\n lead?: string;\n render: (data: TrustCenterPublic) => ReactNode;\n}\n\n/** Sections that have content, in page order. */\nfunction visibleSections(data: TrustCenterPublic): Array<(typeof TRUST_CENTER_SECTIONS)[number]> {\n const hasContent: Record<TrustCenterSectionId, boolean> = {\n ai: data.aiPractices.length > 0,\n compliance: data.frameworks.length > 0,\n controls: data.controlDomains.length > 0,\n documents: data.documents.length > 0,\n subprocessors: data.subprocessors.length > 0,\n faq: data.faqs.length > 0,\n contact: true,\n };\n return TRUST_CENTER_SECTIONS.filter(section => hasContent[section.id]);\n}\n\n/**\n * The Controls section's first answer, from the page's own server data: the\n * first category's controls with no query — exactly what the controls endpoint\n * answers for it, so the first paint needs no request.\n */\nfunction controlsSeed(data: TrustCenterPublic): TrustCenterControlsPage {\n const first = data.controlDomains[0] ?? null;\n return {\n domain: first?.domain ?? null,\n query: '',\n categories: data.controlDomains.map(domain => ({ domain: domain.domain, count: domain.controls.length })),\n controls: first?.controls ?? [],\n total: data.controlDomains.reduce((sum, domain) => sum + domain.controls.length, 0),\n };\n}\n\n/** How often an open page re-judges \"monitored\" against the clock. */\nconst MONITORING_CLOCK_TICK_MS = 60_000;\n\n/**\n * The monitoring claim: neutral before mount, then monitored / paused / not\n * enabled. WHEN the data was last synced is the shared `DataAttribution` line\n * beside it, not repeated here.\n */\nfunction monitoringStatus(\n data: TrustCenterPublic,\n hydrated: boolean,\n nowMs: number,\n): { status: 'success' | 'pending' | 'missing'; label: string } {\n if (!data.connected) return { status: 'missing', label: 'Live control monitoring is not enabled yet' };\n // Before mount the clock is the server's, so the text claims nothing either way.\n if (!hydrated) return { status: 'missing', label: 'Checking monitoring status' };\n return isTrustCenterMonitored(data, nowMs)\n ? { status: 'success', label: 'Controls continuously monitored' }\n : { status: 'pending', label: 'Monitoring paused' };\n}\n\nexport function TrustCenterPage({\n endpoint = TRUST_CENTER_API_PATH,\n initialData,\n shell = true,\n backButton = false,\n title = TRUST_CENTER_TITLE,\n subtitle = TRUST_CENTER_TAGLINE,\n}: TrustCenterPageProps) {\n const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {\n initialData,\n revalidateOnVisibleAfterMs: TRUST_CENTER_CACHE_SECONDS * 1000,\n });\n // `false` during SSR + hydration, `true` after mount — gates everything clock-dependent.\n const hydrated = useIsHydrated();\n // The clock is re-read whenever the DATA changes (React's \"adjust state while\n // rendering\" pattern, keyed on the data identity): a revalidated copy is judged\n // against the time it arrived, not the time the tab was opened. Only read\n // after hydration (`monitoringStatus`), so the SSR value never reaches the DOM.\n const [clock, setClock] = useState(() => ({ data, nowMs: Date.now() }));\n if (clock.data !== data) {\n setClock(() => ({ data, nowMs: Date.now() }));\n }\n const nowMs = clock.nowMs;\n // A tab left open keeps judging the same copy against the passing time, so it\n // flips to \"Monitoring paused\" once `syncedAt` leaves the window.\n useEffect(() => {\n if (!hydrated) return undefined;\n const timer = setInterval(() => setClock(current => ({ ...current, nowMs: Date.now() })), MONITORING_CLOCK_TICK_MS);\n return () => clearInterval(timer);\n }, [hydrated]);\n const [request, setRequest] = useState<{ open: boolean; documentTitle: string | null }>({\n open: false,\n documentTitle: null,\n });\n\n const sections = useMemo(() => (data ? visibleSections(data) : []), [data]);\n // Anchors: the shared same-page hash navigation (the FAQ section's pattern).\n // A rail click puts `#section` in the URL (`replace`: the rail is a table of\n // contents, not a navigation step) and scrolls below the sticky header; a\n // visit that ARRIVES with `#section`, and back/forward, scroll there once the\n // sections have rendered. The scroll spy lights the rail and, as the reader\n // scrolls, keeps the URL's hash on the section in view (`syncHash`).\n const { activeSection } = useScrollSpy(sections, { syncHash: true });\n const handleSectionClick = useCallback((sectionId: string) => {\n navigateSamePageHash(`#${sectionId}`, { headerOffset: STICKY_HEADER_OFFSET_PX, history: 'replace' });\n }, []);\n useScrollToHash(sections.length > 0 ? sections.map(section => section.id).join('|') : null, {\n headerOffset: STICKY_HEADER_OFFSET_PX,\n });\n const openRequest = useCallback((documentTitle: string | null) => setRequest({ open: true, documentTitle }), []);\n const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []);\n\n const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false;\n const actions = hasGatedDocuments\n ? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }]\n : undefined;\n\n // Section id → its content. Titles come from `TRUST_CENTER_SECTIONS`; which\n // sections show comes from `visibleSections` — this map only says what each renders.\n const views: Record<TrustCenterSectionId, TrustSectionView> = {\n ai: {\n lead: TRUST_SECTION_LEADS.ai,\n render: d => <AiSection practices={d.aiPractices} />,\n },\n compliance: {\n lead: TRUST_SECTION_LEADS.compliance,\n render: d => <ComplianceSection frameworks={d.frameworks} />,\n },\n controls: {\n lead: TRUST_SECTION_LEADS.controls,\n render: d => <ControlsSection endpoint={endpoint} seed={controlsSeed(d)} />,\n },\n documents: {\n lead: TRUST_SECTION_LEADS.documents,\n render: d => (\n <DocumentsSection\n documents={d.documents}\n documentUrl={document => trustCenterDocumentUrl(endpoint, document)}\n onRequest={openRequest}\n />\n ),\n },\n subprocessors: {\n lead: TRUST_SECTION_LEADS.subprocessors,\n render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,\n },\n faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },\n contact: { render: () => <ContactSection onContact={() => openRequest(null)} /> },\n };\n\n let body: ReactNode;\n if (error && !data) {\n body = <LoadError message=\"Could not load the trust center\" onRetry={reload} />;\n } else if (!data) {\n body = isLoading ? <TrustCenterSkeleton /> : null;\n } else {\n const status = monitoringStatus(data, hydrated, nowMs);\n body = (\n <>\n <div className={TRUST_STATUS_ROW_CLASS}>\n <StatusIndicator status={status.status} label={status.label} />\n {data.connected ? (\n <DataAttribution\n icon={\n data.dataSource.logoUrl ? (\n <EntityImage\n src={data.dataSource.logoUrl}\n alt={data.dataSource.name}\n fallbackText={data.dataSource.name}\n sizeClassName=\"size-4\"\n />\n ) : undefined\n }\n source={data.dataSource.name}\n lastUpdated={data.syncedAt}\n />\n ) : null}\n </div>\n\n <div className={TRUST_BODY_GRID_CLASS}>\n <div className={TRUST_SECTIONS_COLUMN_CLASS}>\n {sections.map(section => {\n const view = views[section.id];\n return (\n <TrustSection key={section.id} id={section.id} title={section.label} lead={view.lead}>\n {view.render(data)}\n </TrustSection>\n );\n })}\n </div>\n\n <aside className=\"hidden lg:block\" aria-label=\"Trust center sections\">\n <div className=\"sticky\" style={{ top: STICKY_HEADER_OFFSET_PX }}>\n <StickySectionNav\n sections={sections.map(section => ({ id: section.id, label: section.label }))}\n activeSection={activeSection}\n onSectionClick={handleSectionClick}\n />\n </div>\n </aside>\n </div>\n\n <DocumentRequestModal\n open={request.open}\n documentTitle={request.documentTitle}\n category={data.documentRequestCategory}\n onClose={closeRequest}\n />\n </>\n );\n }\n\n return (\n <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton} actions={actions}>\n {body}\n </TrustCenterChrome>\n );\n}\n\n/**\n * The page's chrome, ONE copy for the page and its skeleton: the standalone\n * `PageShell` or the host's padded box (`shell`), and the frozen `PageLayout`\n * header (title, subtitle, back button, actions).\n */\nfunction TrustCenterChrome({\n shell,\n title,\n subtitle,\n backButton,\n actions,\n children,\n}: Required<Pick<TrustCenterPageProps, 'shell' | 'title' | 'subtitle' | 'backButton'>> & {\n actions?: Array<{ label: string; variant: 'accent'; onClick: () => void }>;\n children: ReactNode;\n}) {\n const router = useRouter();\n const backCfg =\n backButton === false\n ? undefined\n : { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };\n const inner = (\n <PageLayout\n title={title}\n subtitle={subtitle}\n backButton={backCfg}\n titleSize=\"h1\"\n titleWrap\n actions={actions}\n actionsVariant=\"primary-buttons\"\n >\n {children}\n </PageLayout>\n );\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n\n/**\n * The whole page while a host loads the data itself (the hub's admin preview):\n * the SAME chrome and the same body skeleton `TrustCenterPage` shows while it\n * self-fetches. Pass the chrome props the page will get.\n */\nexport function TrustCenterPageSkeleton({\n shell = true,\n backButton = false,\n title = TRUST_CENTER_TITLE,\n subtitle = TRUST_CENTER_TAGLINE,\n}: Pick<TrustCenterPageProps, 'shell' | 'backButton' | 'title' | 'subtitle'>) {\n return (\n <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton}>\n <TrustCenterSkeleton />\n </TrustCenterChrome>\n );\n}\n","'use client';\n\n/**\n * Section compositions for `<TrustCenterPage>`. Every visual element here is an\n * EXISTING shared lib component — no new primitive, no styled wrapper and no\n * intrinsic interactive/semantic HTML (`__tests__/trust-center-page.test.tsx`\n * statically enforces that). The only own markup is layout `div`/`section`s with\n * ODS spacing tokens, the same as `RoadmapPage` / `DeliveryPage`.\n *\n * The layout follows 2026 trust-center practice (Anthropic, OpenAI, Vanta,\n * Linear, Cursor, ElevenLabs): ONE page with a section rail, AI & data use first,\n * certified proof above roadmap items, every control of every category always\n * shown (no drawers, no \"view all\", no collapsing), and a controls SEARCH the\n * server answers as one flat, grouped, counted list.\n */\n\nimport { createElement, useState, type ReactNode } from 'react';\nimport { useDebounce } from '../../hooks/ui/use-debounce';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport {\n TRUST_CENTER_SEARCH_DEBOUNCE_MS,\n TRUST_CENTER_SECTIONS,\n normalizeTrustControlQuery,\n trustCenterControlsUrl,\n trustFrameworkBadge,\n trustFrameworkMonitoringEntry,\n type TrustCenterAiPractice,\n type TrustCenterControl,\n type TrustCenterControlsPage,\n type TrustCenterDocument,\n type TrustCenterFramework,\n type TrustCenterSectionId,\n type TrustCenterSubprocessor,\n type TrustFrameworkMonitoringEntry,\n} from '../../types/trust-center';\nimport { getFlagFromCountryName } from '../../utils/country-phone-utils';\nimport { STICKY_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { brandLogoForName } from '../chat/utils/icon-library';\nimport { ContactForm } from '../contact/contact-form';\nimport { FileIcon } from '../icons-v2-generated/documents/file-icon';\nimport { FileShieldIcon } from '../icons-v2-generated/documents/file-shield-icon';\nimport { BrainAIIcon } from '../icons-v2-generated/health/brain-ai-icon';\nimport { LockIcon } from '../icons-v2-generated/security/lock-icon';\nimport { ShieldCheckIcon } from '../icons-v2-generated/security/shield-check-icon';\nimport { CheckIcon } from '../icons-v2-generated/signs-and-symbols/check-icon';\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading';\nimport { ListEmptyState } from '../list-empty-state';\nimport { TextSkeleton, UnifiedSkeleton } from '../loading/unified-skeleton';\nimport { Button } from '../ui/button/button';\nimport { ComplianceLogo } from '../ui/compliance-badge';\nimport { EntityImage } from '../ui/entity-image';\nimport { LoadError } from '../ui/error-state';\nimport { ModalV2, ModalV2Content, ModalV2Header, ModalV2Title } from '../ui/modal-v2';\nimport { ScrollShadow } from '../ui/scroll-fade';\nimport { SearchInput } from '../ui/search-input';\nimport { StackedRowsPanel, type PanelRow } from '../ui/stacked-rows-panel';\nimport { StatusBadge } from '../ui/status-badge';\nimport { TabNavigation, type TabItem } from '../ui/tab-navigation';\n\n/** Monitoring `icon` vocabulary (owned by `TRUST_FRAMEWORK_MONITORING`) → icon component, ONE lookup. */\nconst FRAMEWORK_ICONS: Record<TrustFrameworkMonitoringEntry['icon'], typeof ShieldCheckIcon> = {\n 'shield-check': ShieldCheckIcon,\n 'file-shield': FileShieldIcon,\n};\n\nconst BODY_TEXT = 'text-h6 text-ods-text-secondary';\n\n/**\n * The page body's layout, ONE copy for the loaded page and its skeleton, so the\n * skeleton cannot drift from what replaces it: the status row, then the sections\n * beside the section rail.\n */\nexport const TRUST_STATUS_ROW_CLASS =\n 'flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between';\nexport const TRUST_BODY_GRID_CLASS =\n 'grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]';\nexport const TRUST_SECTIONS_COLUMN_CLASS = 'flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]';\n\n/** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */\nexport const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>> = {\n compliance: 'Frameworks we are audited against, and what is next.',\n controls: 'Security controls currently passing in continuous monitoring (via Vanta).',\n documents: 'Public documents open directly. Gated documents are shared under NDA after one short request.',\n subprocessors: 'Third parties that process customer data on our behalf.',\n ai: 'How customer data is handled by the AI in our products.',\n};\n/**\n * The right-hand action / status column: ONE width from tablet up, so every\n * panel's rows line up; on phones it hugs its content so the title keeps the room.\n */\nconst ACTION_COLUMN = 'shrink-0 md:w-36';\n\n// ---------------------------------------------------------------------------\n// Section frame\n// ---------------------------------------------------------------------------\n\n/** One anchored page section: an `h2` + optional lead line + content. */\nexport function TrustSection({\n id,\n title,\n lead,\n children,\n}: {\n id: string;\n title: string;\n lead?: ReactNode;\n children: ReactNode;\n}) {\n const headingId = `${id}-heading`;\n return (\n <section\n id={id}\n aria-labelledby={headingId}\n className=\"flex flex-col gap-[var(--spacing-system-m)]\"\n style={{ scrollMarginTop: STICKY_HEADER_OFFSET_PX }}\n >\n <div className=\"flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]\">\n <h2 id={headingId} className={SECTION_HEADING_CLASS}>\n {title}\n </h2>\n {lead ? <p className={BODY_TEXT}>{lead}</p> : null}\n </div>\n {children}\n </section>\n );\n}\n\n// ---------------------------------------------------------------------------\n// AI & data use\n// ---------------------------------------------------------------------------\n\n/**\n * The AI statements — the company's Vanta Trust Center FAQs in its AI category,\n * in Vanta's order: the question as the row title, the answer beneath. The\n * first is the commitment (success shield). The page's ONE row style.\n */\nexport function AiSection({ practices }: { practices: TrustCenterAiPractice[] }) {\n const rows: PanelRow[] = practices.map(practice => ({\n id: practice.label,\n columns: [\n {\n key: 'practice',\n leadingIcon: practice.commitment ? (\n <ShieldCheckIcon className=\"text-ods-success\" role=\"img\" aria-label=\"Commitment\" />\n ) : (\n <BrainAIIcon aria-hidden=\"true\" />\n ),\n value: practice.label,\n label: practice.value,\n wrap: true,\n },\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Compliance\n// ---------------------------------------------------------------------------\n\n/**\n * One row per framework, in the Vanta Trust Center's order: its name, its\n * Vanta description, and ONE badge: Vanta's completion (\"16% complete\") or\n * \"Planned\" — never a certification claim, which Vanta's API does\n * not hold.\n */\nexport function ComplianceSection({ frameworks }: { frameworks: TrustCenterFramework[] }) {\n const rows: PanelRow[] = frameworks.map(framework => {\n const entry = trustFrameworkMonitoringEntry(framework.monitoring);\n const Icon = FRAMEWORK_ICONS[entry.icon];\n return {\n id: framework.id,\n columns: [\n {\n key: 'framework',\n leadingIcon: (\n <ComplianceLogo names={[framework.standard, framework.label]} fallback={<Icon aria-hidden=\"true\" />} />\n ),\n value: framework.label,\n label: framework.description ?? undefined,\n wrap: true,\n },\n {\n key: 'monitoring',\n width: ACTION_COLUMN,\n align: 'right',\n content: <StatusBadge text={trustFrameworkBadge(framework)} colorScheme={entry.color} singleLine />,\n },\n ],\n };\n });\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Every control row is ONE fixed height (one line of name, one of description),\n * so the list box shows exactly 5 of them and scrolls for\n * the rest.\n */\nconst CONTROL_ROW_CLASS = 'h-14 md:h-20 overflow-hidden';\n\n/** ONE row renderer for the controls list: fixed-height rows, one line each for name and description. */\nfunction controlRows(controls: TrustCenterControl[]): PanelRow[] {\n return controls.map(control => ({\n id: control.id,\n className: CONTROL_ROW_CLASS,\n columns: [\n {\n key: 'control',\n leadingIcon: <CheckIcon className=\"text-ods-success\" role=\"img\" aria-label=\"Passing\" />,\n value: control.name,\n label: control.description ?? undefined,\n },\n ],\n }));\n}\n\nfunction countLabel(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? '' : 's'}`;\n}\n\n/**\n * The list box: exactly 5 control rows (`CONTROL_ROW_CLASS`: 3.5rem, 5rem from\n * md) plus their 1px separators and the panel's border. Controls, loading,\n * empty and error all fill this same box, so nothing below it moves.\n */\nconst CONTROLS_LIST_HEIGHT = 'h-[calc(5*3.5rem+7px)] md:h-[calc(5*5rem+7px)]';\n\n/** Skeleton rows shown in the list box while the server's answer is on its way: exactly the box's visible rows. */\nconst CONTROLS_SKELETON_ROWS = 5;\n\nfunction controlsTabId(domain: string): string {\n return `domain-${domain.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;\n}\n\nfunction ControlsListSkeleton() {\n const rows: PanelRow[] = Array.from({ length: CONTROLS_SKELETON_ROWS }, (_, index) => ({\n id: `skeleton-${index}`,\n className: CONTROL_ROW_CLASS,\n columns: [\n {\n key: 'control',\n content: (\n <div className=\"flex w-full flex-col gap-[var(--spacing-system-xs)]\">\n <TextSkeleton.Body className=\"w-2/3\" />\n <TextSkeleton.Caption className=\"w-full\" />\n </div>\n ),\n },\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n/**\n * Controls — 100% SERVER-driven. Every tab switch and every (debounced) search\n * is ONE request to the controls endpoint (`trustCenterControlsUrl`), which\n * answers the categories with their counts, the category shown and its\n * controls; the section only renders that answer. One tab per category (no\n * \"All\"), the search and the summary line ABOVE a fixed-height list box that\n * scrolls on its own — loading (skeleton rows), empty and error states fill the\n * same box, so the page never jumps.\n *\n * `seed` is the server-rendered answer for the first category with no query\n * (the page's own data), so the first paint needs no request.\n */\nexport function ControlsSection({ endpoint, seed }: { endpoint: string; seed: TrustCenterControlsPage }) {\n const [domain, setDomain] = useState<string | null>(seed.domain);\n const [typed, setTyped] = useState('');\n const query = useDebounce(normalizeTrustControlQuery(typed), TRUST_CENTER_SEARCH_DEBOUNCE_MS);\n const url = trustCenterControlsUrl(endpoint, { domain, query });\n const seedUrl = trustCenterControlsUrl(endpoint, { domain: seed.domain, query: '' });\n const answer = useSelfFetch<TrustCenterControlsPage>(url, url === seedUrl ? { initialData: seed } : undefined);\n\n // The answer counts only when it answers what is on screen NOW (the typed\n // query, the selected tab); otherwise the box shows its loading state.\n const typedQuery = normalizeTrustControlQuery(typed);\n const current =\n answer.data && !answer.isLoading && typedQuery === query && answer.data.query === query ? answer.data : null;\n // Tabs keep the last categories the server sent while a new answer loads.\n const [categories, setCategories] = useState(seed.categories);\n if (current && current.categories !== categories) setCategories(current.categories);\n const total = current?.total ?? seed.total;\n\n const tabs: TabItem[] = categories.map(category => ({\n id: controlsTabId(category.domain),\n label: `${category.domain} · ${category.count}`,\n }));\n const activeDomain = current?.domain ?? domain;\n const activeTab = activeDomain ? controlsTabId(activeDomain) : '';\n\n let summary: string;\n if (answer.error && !current) summary = 'Controls are unavailable';\n else if (!current) summary = typedQuery ? `Searching for “${typedQuery}”…` : 'Loading…';\n else if (query)\n summary = `${current.controls.length} in ${current.domain} match “${query}” · ${countLabel(total, 'control')} in total`;\n else summary = `${countLabel(current.controls.length, 'passing control')} in ${current.domain}`;\n\n let body: ReactNode;\n if (answer.error && !current) {\n body = <LoadError message=\"Could not load the controls\" onRetry={answer.reload} />;\n } else if (!current) {\n body = <ControlsListSkeleton />;\n } else if (current.controls.length === 0) {\n body = (\n <ListEmptyState\n isFiltered\n filtered={{\n title: 'No matching controls',\n description: 'Try another word, or another category.',\n clearText: 'Clear search',\n }}\n onClearFilters={() => setTyped('')}\n empty={{ title: 'No controls', description: '' }}\n />\n );\n } else {\n body = <StackedRowsPanel rows={controlRows(current.controls)} />;\n }\n\n return (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <TabNavigation\n tabs={tabs}\n activeTab={activeTab}\n onTabChange={tabId =>\n setDomain(categories.find(category => controlsTabId(category.domain) === tabId)?.domain ?? null)\n }\n />\n <div className=\"flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between\">\n <p className={BODY_TEXT} aria-live=\"polite\">\n {summary}\n </p>\n <div className=\"w-full md:w-80\">\n <SearchInput\n placeholder=\"Search controls\"\n value={typed}\n onChange={setTyped}\n debounceMs={0}\n showDropdown={false}\n />\n </div>\n </div>\n <ScrollShadow className={CONTROLS_LIST_HEIGHT} scrollClassName=\"h-full\" data-testid=\"controls-list\">\n {body}\n </ScrollShadow>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Documents\n// ---------------------------------------------------------------------------\n\nexport function DocumentsSection({\n documents,\n documentUrl,\n onRequest,\n}: {\n documents: TrustCenterDocument[];\n /** Where a public document opens (`trustCenterDocumentUrl`); `null` → offer a request instead. */\n documentUrl: (document: TrustCenterDocument) => string | null;\n /** Open the ONE access request, optionally for a named document. */\n onRequest: (documentTitle: string | null) => void;\n}) {\n const rows: PanelRow[] = documents.map(document => {\n const gated = document.access === 'request';\n const href = gated ? null : documentUrl(document);\n return {\n id: document.id,\n columns: [\n {\n key: 'document',\n leadingIcon: gated ? (\n <LockIcon role=\"img\" aria-label=\"Available on request\" />\n ) : (\n <FileIcon role=\"img\" aria-label=\"Public\" />\n ),\n value: document.title,\n label: document.description ?? (gated ? 'Available on request' : 'Public'),\n wrap: true,\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content:\n href === null ? (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() => onRequest(document.title)}\n aria-label={`Request ${document.title}`}\n >\n Request\n </Button>\n ) : (\n <Button variant=\"outline\" size=\"small\" href={href} openInNewTab aria-label={`View ${document.title}`}>\n View\n </Button>\n ),\n },\n ],\n };\n });\n return <StackedRowsPanel rows={rows} />;\n}\n\n/**\n * The ONE access request (the existing contact flow, `/api/contact`, kept on the\n * page). Only name, email and a pre-filled message: no marketing questions.\n */\nexport function DocumentRequestModal({\n open,\n documentTitle,\n category,\n onClose,\n}: {\n open: boolean;\n /** The serving platform's help category for these requests (`TrustCenterPublic.documentRequestCategory`). */\n category: string;\n /** The document the request started from, or null for \"all gated documents\". */\n documentTitle: string | null;\n onClose: () => void;\n}) {\n const message = documentTitle\n ? `I'd like access to: ${documentTitle}.`\n : \"I'd like access to your gated security documentation.\";\n return (\n <ModalV2 isOpen={open} onClose={onClose} size=\"medium\">\n <ModalV2Header>\n <ModalV2Title>Request access</ModalV2Title>\n </ModalV2Header>\n <ModalV2Content>\n {open ? (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <p className={BODY_TEXT}>\n We share gated security documents under NDA. Leave your work email and our security team will follow up\n with access, usually within one business day.\n </p>\n <ContactForm\n rescueFormId=\"trust_center_request\"\n title=\"\"\n noBorder\n noPadding\n hideFields={['companySize', 'referralSource', 'helpCategory']}\n defaultValues={{ helpCategory: category, message }}\n submitLabel=\"Request access\"\n successRedirectUrl=\"\"\n successToastMessage=\"Thanks — our security team will follow up by email.\"\n onSubmitSuccess={onClose}\n />\n </div>\n ) : null}\n </ModalV2Content>\n </ModalV2>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Subprocessors\n// ---------------------------------------------------------------------------\n\n/**\n * A subprocessor's mark, best source first: its own site's icon (the hub looks\n * it up from the website Vanta holds), else the brand logo our icon set has for\n * its name (`brandLogoForName`), else initials — which are also what a site\n * icon that fails to load falls back to.\n */\nfunction subprocessorMark({ name, logoUrl }: TrustCenterSubprocessor): ReactNode {\n if (logoUrl) return <EntityImage src={logoUrl} alt={name} fallbackText={name} sizeClassName=\"size-6\" />;\n const logo = brandLogoForName(name);\n return logo ? (\n <span role=\"img\" aria-label={name} className=\"flex\">\n {createElement(logo, { className: 'size-6' })}\n </span>\n ) : (\n <EntityImage alt={name} fallbackText={name} sizeClassName=\"size-6\" />\n );\n}\n\n/** \"🇺🇸 United States\": the flag from the shared country lookup beside the name; the name alone when unknown. */\nfunction countryWithFlag(country: string): string {\n const flag = getFlagFromCountryName(country);\n return flag ? `${flag} ${country}` : country;\n}\n\nexport function SubprocessorsSection({ subprocessors }: { subprocessors: TrustCenterSubprocessor[] }) {\n // The location column exists only when Vanta holds a location for at least one of them.\n const anyLocation = subprocessors.some(subprocessor => subprocessor.location);\n const rows: PanelRow[] = subprocessors.map(subprocessor => ({\n id: subprocessor.name,\n columns: [\n {\n key: 'name',\n leadingIcon: subprocessorMark(subprocessor),\n value: subprocessor.name,\n label: subprocessor.purpose ?? subprocessor.description ?? undefined,\n href: subprocessor.url ?? undefined,\n wrap: true,\n },\n ...(anyLocation\n ? [\n {\n key: 'location',\n value: subprocessor.location ? countryWithFlag(subprocessor.location) : '—',\n label: 'Location',\n hideAt: 'md' as const,\n width: 'w-48 shrink-0',\n },\n ]\n : []),\n ],\n }));\n return <StackedRowsPanel rows={rows} />;\n}\n\n// ---------------------------------------------------------------------------\n// Contact + loading\n// ---------------------------------------------------------------------------\n\n/**\n * Security & compliance questions, routed through the page's ONE request flow\n * (`/api/contact`) — no address is published and no disclosure programme is\n * advertised (industry practice for trust portals: questions go through the\n * account relationship, not an open inbox).\n */\nexport function ContactSection({ onContact }: { onContact: () => void }) {\n return (\n <StackedRowsPanel\n rows={[\n {\n id: 'questions',\n columns: [\n {\n key: 'questions',\n leadingIcon: <FileShieldIcon aria-hidden=\"true\" />,\n value: 'Security questions',\n label:\n 'Customers and prospective customers can reach our security team for questionnaires, documentation or a review call.',\n wrap: true,\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content: (\n <Button variant=\"outline\" size=\"small\" onClick={onContact}>\n Get in touch\n </Button>\n ),\n },\n ],\n },\n ]}\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\n/** A section's heading and lead, exactly as `TrustSection` renders them (real text: it does not load). */\nfunction SkeletonSection({ id, children }: { id: TrustCenterSectionId; children: ReactNode }) {\n const label = TRUST_CENTER_SECTIONS.find(section => section.id === id)?.label ?? id;\n const lead = TRUST_SECTION_LEADS[id];\n return (\n <section aria-hidden=\"true\" className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <div className=\"flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]\">\n <h2 className={SECTION_HEADING_CLASS}>{label}</h2>\n {lead ? <p className={BODY_TEXT}>{lead}</p> : null}\n </div>\n {children}\n </section>\n );\n}\n\n/**\n * Rows of the REAL `StackedRowsPanel` (its own row heights and borders) whose\n * cells hold bars: a mark (a 40px compliance logo or a 24px glyph), a value over\n * a label, and the right-hand badge or button in the page's `ACTION_COLUMN`.\n */\nfunction skeletonRows(count: number, shape: { mark: 'logo' | 'glyph'; action: 'badge' | 'button' }): PanelRow[] {\n return Array.from({ length: count }, (_, index) => ({\n id: `skeleton-${index}`,\n columns: [\n {\n key: 'item',\n content: (\n <div className=\"flex w-full items-center gap-[var(--spacing-system-s)]\">\n <UnifiedSkeleton\n className={shape.mark === 'logo' ? 'size-10 shrink-0 rounded-full' : 'size-6 shrink-0 rounded'}\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]\">\n <UnifiedSkeleton variant=\"text\" className=\"h-5 w-1/3 md:h-6\" />\n <TextSkeleton.Body className=\"w-1/2\" />\n </div>\n </div>\n ),\n },\n {\n key: 'action',\n width: ACTION_COLUMN,\n align: 'right',\n content:\n shape.action === 'badge' ? (\n <UnifiedSkeleton className=\"h-4 w-28 rounded\" />\n ) : (\n <UnifiedSkeleton className=\"h-6 w-16 rounded md:h-8\" />\n ),\n },\n ],\n }));\n}\n\n/** The Controls section while loading: its tab strip, its summary and search, and the list box's own skeleton. */\nfunction ControlsSectionSkeleton() {\n return (\n <div className=\"flex flex-col gap-[var(--spacing-system-m)]\">\n <div className=\"flex items-center gap-[var(--spacing-system-xxs)] overflow-hidden\">\n {[0, 1, 2, 3].map(index => (\n <div key={index} className=\"shrink-0 p-[var(--spacing-system-m)]\">\n <UnifiedSkeleton variant=\"text\" className=\"h-5 w-36 md:h-6\" />\n </div>\n ))}\n </div>\n <div className=\"flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between\">\n <TextSkeleton.Body className=\"w-48\" />\n <UnifiedSkeleton className=\"h-11 w-full rounded-[6px] md:h-12 md:w-80\" />\n </div>\n <div className={CONTROLS_LIST_HEIGHT}>\n <ControlsListSkeleton />\n </div>\n </div>\n );\n}\n\n/** The section rail while loading: `StickySectionNav`'s line and its 40px items. */\nfunction SectionRailSkeleton() {\n return (\n <div className=\"sticky\" style={{ top: STICKY_HEADER_OFFSET_PX }}>\n <div className=\"relative\">\n <div\n className=\"absolute -left-0.5 top-0 w-px bg-ods-border\"\n style={{ height: TRUST_CENTER_SECTIONS.length * 40 }}\n />\n {TRUST_CENTER_SECTIONS.map(section => (\n <div key={section.id} className=\"flex h-10 items-center px-3 py-2\">\n <TextSkeleton.Body className=\"w-24\" />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * The page BODY while the trust center loads: built on the loaded body's own\n * layout classes and the real section headings and panels, so nothing moves when\n * the content replaces it. The status row, then Compliance, Controls and\n * Documents (what fills the first screens), beside the section rail.\n */\nexport function TrustCenterSkeleton() {\n return (\n <>\n <div className={TRUST_STATUS_ROW_CLASS} aria-hidden=\"true\">\n <div className=\"flex items-center gap-1.5\">\n <UnifiedSkeleton className=\"size-3 rounded-full\" />\n <TextSkeleton.Body className=\"w-44\" />\n </div>\n {/* `DataAttribution`'s own layout: two lines below `sm`, one row above it. */}\n <div className=\"flex shrink-0 flex-col gap-[var(--spacing-system-xxs)] sm:flex-row sm:items-center sm:gap-[var(--spacing-system-sf)]\">\n <div className=\"flex items-center gap-[var(--spacing-system-xsf)]\">\n <UnifiedSkeleton className=\"size-4 rounded\" />\n <TextSkeleton.Body className=\"w-36\" />\n </div>\n <TextSkeleton.Body className=\"w-44\" />\n </div>\n </div>\n <div className={TRUST_BODY_GRID_CLASS} aria-busy=\"true\" aria-label=\"Loading the trust center\">\n <div className={TRUST_SECTIONS_COLUMN_CLASS}>\n <SkeletonSection id=\"compliance\">\n <StackedRowsPanel rows={skeletonRows(3, { mark: 'logo', action: 'badge' })} />\n </SkeletonSection>\n <SkeletonSection id=\"controls\">\n <ControlsSectionSkeleton />\n </SkeletonSection>\n <SkeletonSection id=\"documents\">\n <StackedRowsPanel rows={skeletonRows(2, { mark: 'glyph', action: 'button' })} />\n </SkeletonSection>\n </div>\n <aside className=\"hidden lg:block\" aria-hidden=\"true\">\n <SectionRailSkeleton />\n </aside>\n </div>\n </>\n );\n}\n"]}
@@ -6,38 +6,38 @@ import {
6
6
  ProductReleasesView,
7
7
  ReleaseDetailPage,
8
8
  RoadmapView
9
- } from "../../chunk-IJIIL6WU.js";
9
+ } from "../../chunk-IG7M3XKX.js";
10
10
  import {
11
11
  DocsHubPage,
12
12
  useScrollSpy
13
- } from "../../chunk-OYMOG544.js";
14
- import "../../chunk-DUZEFACP.js";
13
+ } from "../../chunk-UFMXQKVT.js";
14
+ import "../../chunk-RRU46J3N.js";
15
15
  import "../../chunk-7EI3NES7.js";
16
16
  import {
17
17
  FaqDocumentPage,
18
18
  FaqSection
19
- } from "../../chunk-K3O4D7LW.js";
19
+ } from "../../chunk-HRM6242Y.js";
20
20
  import {
21
21
  StickySectionNav
22
- } from "../../chunk-UH3BL2OT.js";
22
+ } from "../../chunk-VK5737GE.js";
23
23
  import {
24
24
  HelpCenterList
25
- } from "../../chunk-6C6UUOH4.js";
25
+ } from "../../chunk-TTHBFT7Q.js";
26
26
  import {
27
27
  ContactForm
28
- } from "../../chunk-STLBB6PF.js";
28
+ } from "../../chunk-LMUZZUYC.js";
29
29
  import {
30
30
  DevSectionPage,
31
31
  ListEmptyState
32
- } from "../../chunk-G64BNLDO.js";
32
+ } from "../../chunk-P4GUIOLP.js";
33
33
  import "../../chunk-5ONM3FHQ.js";
34
- import "../../chunk-4PVY4JJN.js";
34
+ import "../../chunk-YWQVL5VJ.js";
35
35
  import {
36
36
  OnboardingGuideDetailView,
37
37
  OnboardingGuidesCatalogView
38
- } from "../../chunk-SNNE34OP.js";
39
- import "../../chunk-RKUCK6MN.js";
40
- import "../../chunk-PQ7YC5Y6.js";
38
+ } from "../../chunk-BF7GZ6BS.js";
39
+ import "../../chunk-OGOMAK7B.js";
40
+ import "../../chunk-II6ABVLK.js";
41
41
  import {
42
42
  useSelfFetch
43
43
  } from "../../chunk-KKSSA4CS.js";
@@ -53,13 +53,13 @@ import {
53
53
  StatusIndicator,
54
54
  TabNavigation,
55
55
  getFlagFromCountryName
56
- } from "../../chunk-NIYIK32F.js";
56
+ } from "../../chunk-UMTXGEPU.js";
57
57
  import "../../chunk-L2SLFCXP.js";
58
58
  import "../../chunk-BJXDHKUX.js";
59
59
  import {
60
60
  useScrollToHash
61
- } from "../../chunk-3PVOF3IB.js";
62
- import "../../chunk-ZRIVCLPR.js";
61
+ } from "../../chunk-UTHZ7DLN.js";
62
+ import "../../chunk-JD5TKRCU.js";
63
63
  import "../../chunk-GRBZC5N3.js";
64
64
  import "../../chunk-MT5A25AX.js";
65
65
  import "../../chunk-BXWXBBAC.js";
@@ -74,7 +74,7 @@ import {
74
74
  UnifiedSkeleton,
75
75
  brandLogoForName,
76
76
  useIsHydrated
77
- } from "../../chunk-TLLTL6LB.js";
77
+ } from "../../chunk-JD4U7QMG.js";
78
78
  import {
79
79
  STICKY_HEADER_OFFSET_PX,
80
80
  navigateSamePageHash,
@@ -475,6 +475,7 @@ function DocumentRequestModal({
475
475
  /* @__PURE__ */ jsx5(
476
476
  ContactForm,
477
477
  {
478
+ rescueFormId: "trust_center_request",
478
479
  title: "",
479
480
  noBorder: true,
480
481
  noPadding: true,