@flamingo-stack/openframe-frontend-core 0.0.481 → 0.0.482

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 (838) hide show
  1. package/dist/{chunk-H6DFCF44.cjs → chunk-22AED5IC.cjs} +82 -82
  2. package/dist/{chunk-H6DFCF44.cjs.map → chunk-22AED5IC.cjs.map} +1 -1
  3. package/dist/{chunk-MGKCS6KS.js → chunk-3WCARCI4.js} +2 -2
  4. package/dist/{chunk-LNKWY74S.cjs → chunk-4HL6UENH.cjs} +5 -5
  5. package/dist/{chunk-LNKWY74S.cjs.map → chunk-4HL6UENH.cjs.map} +1 -1
  6. package/dist/{chunk-Y3Y2DLNO.js → chunk-6UGYBCEN.js} +3 -3
  7. package/dist/{chunk-BBUEY73L.js → chunk-7I5B5PQ4.js} +6269 -5339
  8. package/dist/chunk-7I5B5PQ4.js.map +1 -0
  9. package/dist/{chunk-PSPWT2Q5.js → chunk-7LWXWXEY.js} +4 -4
  10. package/dist/{chunk-M7O74OWG.cjs → chunk-ACOU5YJI.cjs} +31 -31
  11. package/dist/{chunk-M7O74OWG.cjs.map → chunk-ACOU5YJI.cjs.map} +1 -1
  12. package/dist/{chunk-INDFIVDV.cjs → chunk-AHGSN4AT.cjs} +12 -12
  13. package/dist/{chunk-INDFIVDV.cjs.map → chunk-AHGSN4AT.cjs.map} +1 -1
  14. package/dist/{chunk-VTSRDC4W.js → chunk-CKC3EAYT.js} +2 -2
  15. package/dist/{chunk-SASR7SK5.js → chunk-GZK7FWJJ.js} +20 -4
  16. package/dist/chunk-GZK7FWJJ.js.map +1 -0
  17. package/dist/{chunk-ZRANZNEA.js → chunk-JJE5TUZK.js} +2 -2
  18. package/dist/{chunk-66BNAFO6.js → chunk-JQIUBFHL.js} +4 -4
  19. package/dist/{chunk-MMCBQJKE.cjs → chunk-KUBJHB6I.cjs} +11 -11
  20. package/dist/{chunk-MMCBQJKE.cjs.map → chunk-KUBJHB6I.cjs.map} +1 -1
  21. package/dist/{chunk-ZV6E3U3C.cjs → chunk-MRGTMMJ3.cjs} +30 -30
  22. package/dist/{chunk-ZV6E3U3C.cjs.map → chunk-MRGTMMJ3.cjs.map} +1 -1
  23. package/dist/{chunk-OZJN54R5.cjs → chunk-NWVKG7QC.cjs} +37 -37
  24. package/dist/{chunk-OZJN54R5.cjs.map → chunk-NWVKG7QC.cjs.map} +1 -1
  25. package/dist/{chunk-AN47EIIJ.cjs → chunk-PHTSSHIQ.cjs} +59 -59
  26. package/dist/{chunk-AN47EIIJ.cjs.map → chunk-PHTSSHIQ.cjs.map} +1 -1
  27. package/dist/{chunk-N2JZ33V7.cjs → chunk-RUA3OXSJ.cjs} +26 -26
  28. package/dist/{chunk-N2JZ33V7.cjs.map → chunk-RUA3OXSJ.cjs.map} +1 -1
  29. package/dist/{chunk-LK623WCC.cjs → chunk-RZPF7MJ2.cjs} +9 -9
  30. package/dist/{chunk-LK623WCC.cjs.map → chunk-RZPF7MJ2.cjs.map} +1 -1
  31. package/dist/{chunk-G3UN2DXU.cjs → chunk-STYA77PH.cjs} +20 -4
  32. package/dist/chunk-STYA77PH.cjs.map +1 -0
  33. package/dist/{chunk-JUJY3HQS.cjs → chunk-U37UTLP7.cjs} +19 -19
  34. package/dist/{chunk-JUJY3HQS.cjs.map → chunk-U37UTLP7.cjs.map} +1 -1
  35. package/dist/{chunk-2DIOV7BR.js → chunk-UPN6EFOM.js} +4 -4
  36. package/dist/{chunk-OWMLWJQY.cjs → chunk-VPVOJD5X.cjs} +2432 -1502
  37. package/dist/chunk-VPVOJD5X.cjs.map +1 -0
  38. package/dist/{chunk-4SMRQ4SN.js → chunk-WBL5Y4PS.js} +3 -3
  39. package/dist/{chunk-FP4H6Y5O.js → chunk-WPYPSIY2.js} +3 -3
  40. package/dist/{chunk-R5WJJL4V.js → chunk-X5X43HFV.js} +5 -5
  41. package/dist/{chunk-DEHZ5CTC.js → chunk-XTWKDMCG.js} +2 -2
  42. package/dist/components/case-studies/index.cjs +9 -9
  43. package/dist/components/case-studies/index.js +3 -3
  44. package/dist/components/chat/index.cjs +3 -3
  45. package/dist/components/chat/index.js +2 -2
  46. package/dist/components/contact/index.cjs +4 -4
  47. package/dist/components/contact/index.js +3 -3
  48. package/dist/components/docs/index.cjs +6 -6
  49. package/dist/components/docs/index.js +5 -5
  50. package/dist/components/embeds/index.cjs +4 -4
  51. package/dist/components/embeds/index.js +3 -3
  52. package/dist/components/faq/index.cjs +4 -4
  53. package/dist/components/faq/index.js +3 -3
  54. package/dist/components/features/captions-url.cjs +12 -0
  55. package/dist/components/features/captions-url.cjs.map +1 -0
  56. package/dist/components/features/captions-url.js +10 -0
  57. package/dist/components/features/captions-url.js.map +1 -0
  58. package/dist/components/features/card-hit-layer.d.ts +40 -0
  59. package/dist/components/features/card-hit-layer.d.ts.map +1 -0
  60. package/dist/components/features/floating-walkthrough-video.d.ts +71 -0
  61. package/dist/components/features/floating-walkthrough-video.d.ts.map +1 -0
  62. package/dist/components/features/index.cjs +11 -3
  63. package/dist/components/features/index.cjs.map +1 -1
  64. package/dist/components/features/index.d.ts +4 -0
  65. package/dist/components/features/index.d.ts.map +1 -1
  66. package/dist/components/features/index.js +12 -4
  67. package/dist/components/features/use-walkthrough-video.d.ts +16 -0
  68. package/dist/components/features/use-walkthrough-video.d.ts.map +1 -0
  69. package/dist/components/features/video-bites-strip.d.ts.map +1 -1
  70. package/dist/components/features/video-center-badge.d.ts.map +1 -1
  71. package/dist/components/features/video-hover-preview.d.ts +68 -0
  72. package/dist/components/features/video-hover-preview.d.ts.map +1 -0
  73. package/dist/components/features/video.d.ts +78 -40
  74. package/dist/components/features/video.d.ts.map +1 -1
  75. package/dist/components/form.d.ts +0 -1
  76. package/dist/components/form.d.ts.map +1 -1
  77. package/dist/components/help-center-pages/index.cjs +21 -21
  78. package/dist/components/help-center-pages/index.js +12 -12
  79. package/dist/components/index.cjs +163 -155
  80. package/dist/components/index.cjs.map +1 -1
  81. package/dist/components/index.js +18 -10
  82. package/dist/components/index.js.map +1 -1
  83. package/dist/components/navigation/index.cjs +3 -3
  84. package/dist/components/navigation/index.js +2 -2
  85. package/dist/components/onboarding-guides/index.cjs +6 -6
  86. package/dist/components/onboarding-guides/index.js +5 -5
  87. package/dist/components/related-content/index.cjs +4 -4
  88. package/dist/components/related-content/index.js +3 -3
  89. package/dist/components/tickets/index.cjs +6 -6
  90. package/dist/components/tickets/index.js +5 -5
  91. package/dist/components/ui/button/button-styles.d.ts +2 -0
  92. package/dist/components/ui/button/button-styles.d.ts.map +1 -1
  93. package/dist/components/ui/button/button.d.ts +2 -2
  94. package/dist/components/ui/button/button.d.ts.map +1 -1
  95. package/dist/components/ui/file-manager/index.cjs +50 -50
  96. package/dist/components/ui/file-manager/index.js +1 -1
  97. package/dist/components/ui/index.cjs +3 -3
  98. package/dist/components/ui/index.js +2 -2
  99. package/dist/components/ui/page-actions.d.ts +2 -1
  100. package/dist/components/ui/page-actions.d.ts.map +1 -1
  101. package/dist/index.cjs +27 -3
  102. package/dist/index.cjs.map +1 -1
  103. package/dist/index.js +28 -4
  104. package/dist/types/supabase.d.ts +80 -0
  105. package/dist/types/supabase.d.ts.map +1 -1
  106. package/dist/types/video-processing.d.ts +1 -1
  107. package/dist/types/video-processing.d.ts.map +1 -1
  108. package/dist/utils/announcement-storage.d.ts +1 -1
  109. package/dist/utils/announcement-storage.d.ts.map +1 -1
  110. package/dist/utils/dismiss-cookie.d.ts +27 -0
  111. package/dist/utils/dismiss-cookie.d.ts.map +1 -0
  112. package/dist/utils/dismissal-storage.d.ts +19 -0
  113. package/dist/utils/dismissal-storage.d.ts.map +1 -0
  114. package/dist/utils/index.cjs +48 -10
  115. package/dist/utils/index.cjs.map +1 -1
  116. package/dist/utils/index.d.ts +2 -0
  117. package/dist/utils/index.d.ts.map +1 -1
  118. package/dist/utils/index.js +41 -11
  119. package/dist/utils/index.js.map +1 -1
  120. package/package.json +8 -2
  121. package/src/.index.md +39 -32
  122. package/src/.platform-domains.md +65 -0
  123. package/src/assets/.fae-avatar.md +33 -0
  124. package/src/assets/.index.md +15 -9
  125. package/src/components/.agent-mark.md +31 -0
  126. package/src/components/.announcement-bar.md +47 -35
  127. package/src/components/.calendar.md +19 -15
  128. package/src/components/.categories-cart.md +34 -30
  129. package/src/components/.category-card.md +33 -33
  130. package/src/components/.chart.md +38 -40
  131. package/src/components/.command.md +47 -23
  132. package/src/components/.comment-card.md +31 -26
  133. package/src/components/.content-loading-container.md +45 -20
  134. package/src/components/.context-menu.md +45 -31
  135. package/src/components/.date-time-picker.md +36 -30
  136. package/src/components/.empty-state.md +31 -31
  137. package/src/components/.faq-accordion.md +20 -30
  138. package/src/components/.filter-chip.md +36 -33
  139. package/src/components/.footer-waitlist-button.md +27 -21
  140. package/src/components/.footer.md +39 -24
  141. package/src/components/.form.md +32 -20
  142. package/src/components/.hero-image-uploader.md +35 -31
  143. package/src/components/.icon-display.md +53 -0
  144. package/src/components/.image-cropper.md +34 -33
  145. package/src/components/.index.md +25 -29
  146. package/src/components/.logs-list.md +27 -23
  147. package/src/components/.media-carousel.md +47 -26
  148. package/src/components/.metric-value.md +27 -24
  149. package/src/components/.msp-display.md +29 -23
  150. package/src/components/.open-source-features.md +23 -26
  151. package/src/components/.pagination.md +44 -39
  152. package/src/components/.persistent-filter-controls.md +61 -28
  153. package/src/components/.pricing-display.md +34 -33
  154. package/src/components/.results-count.md +33 -20
  155. package/src/components/.smooth-accordion.md +24 -20
  156. package/src/components/.social-icon-row.md +33 -21
  157. package/src/components/.sonner.md +40 -25
  158. package/src/components/.steps.md +39 -25
  159. package/src/components/.tool-icon.md +34 -21
  160. package/src/components/.unified-filter-logic.md +32 -36
  161. package/src/components/.user-display.md +33 -28
  162. package/src/components/.user-summary-stub.md +40 -33
  163. package/src/components/.vendor-display-button.md +17 -15
  164. package/src/components/.vendor-icon.md +17 -25
  165. package/src/components/.vendor-tag.md +39 -34
  166. package/src/components/.why-it-matters.md +15 -11
  167. package/src/components/.yes-no-display.md +36 -31
  168. package/src/components/authors/.author-detail-view.md +50 -0
  169. package/src/components/case-studies/.index.md +26 -0
  170. package/src/components/case-studies/.share-experience-section.md +45 -0
  171. package/src/components/chat/.approval-batch-message.md +48 -33
  172. package/src/components/chat/.approval-request-message.md +37 -26
  173. package/src/components/chat/.chat-archive-page.md +56 -0
  174. package/src/components/chat/.chat-attachment-bar.md +32 -40
  175. package/src/components/chat/.chat-composer.md +43 -0
  176. package/src/components/chat/.chat-container.md +41 -25
  177. package/src/components/chat/.chat-context-memory-bar.md +53 -0
  178. package/src/components/chat/.chat-context-picker.md +89 -0
  179. package/src/components/chat/.chat-header-icon-button.md +43 -0
  180. package/src/components/chat/.chat-header-search-field.md +51 -0
  181. package/src/components/chat/.chat-input.md +63 -30
  182. package/src/components/chat/.chat-list-empty-state.md +44 -0
  183. package/src/components/chat/.chat-message-enhanced.md +29 -31
  184. package/src/components/chat/.chat-message-list.md +34 -44
  185. package/src/components/chat/.chat-message-row.md +56 -0
  186. package/src/components/chat/.chat-message-skeleton.md +32 -26
  187. package/src/components/chat/.chat-panel-header-mobile.md +46 -0
  188. package/src/components/chat/.chat-panel-header.md +80 -0
  189. package/src/components/chat/.chat-sidebar-skeleton.md +31 -30
  190. package/src/components/chat/.chat-sidebar.md +36 -29
  191. package/src/components/chat/.chat-ticket-list.md +38 -23
  192. package/src/components/chat/.chat-typing-indicator.md +37 -19
  193. package/src/components/chat/.context-compaction-display.md +19 -15
  194. package/src/components/chat/.context-items-list.md +57 -0
  195. package/src/components/chat/.embeddable-chat.md +57 -66
  196. package/src/components/chat/.error-message-display.md +40 -22
  197. package/src/components/chat/.guide-mode-banner.md +41 -0
  198. package/src/components/chat/.guide-welcome.md +43 -0
  199. package/src/components/chat/.index.md +26 -21
  200. package/src/components/chat/.mingo-chat-history.md +63 -0
  201. package/src/components/chat/.mingo-chat-modals.md +44 -0
  202. package/src/components/chat/.mingo-history-rail.md +63 -0
  203. package/src/components/chat/.mingo-info-card.md +59 -0
  204. package/src/components/chat/.mingo-onboarding-card.md +21 -27
  205. package/src/components/chat/.mingo-welcome.md +60 -0
  206. package/src/components/chat/.model-display.md +33 -25
  207. package/src/components/chat/.msp-organization-card-skeleton.md +44 -0
  208. package/src/components/chat/.msp-organization-card.md +51 -0
  209. package/src/components/chat/.nav-link-anchor-via-runtime.md +36 -25
  210. package/src/components/chat/.quick-action-chip.md +60 -0
  211. package/src/components/chat/.quick-action-marquee.md +53 -0
  212. package/src/components/chat/.quick-action-wall.md +68 -0
  213. package/src/components/chat/.remark-mention-chips.md +41 -0
  214. package/src/components/chat/.slash-command-suggestions.md +30 -36
  215. package/src/components/chat/.source-action-button.md +28 -25
  216. package/src/components/chat/.tool-call-blocks.md +18 -30
  217. package/src/components/chat/.tool-execution-display.md +29 -34
  218. package/src/components/chat/entity-cards/.admin-content-card.md +29 -22
  219. package/src/components/chat/entity-cards/.blog-card.md +44 -33
  220. package/src/components/chat/entity-cards/.blog-image-placeholder.md +20 -18
  221. package/src/components/chat/entity-cards/.case-study-card.md +24 -33
  222. package/src/components/chat/entity-cards/.chat-ticket-item.md +39 -22
  223. package/src/components/chat/entity-cards/.customer-interview-card.md +29 -32
  224. package/src/components/chat/entity-cards/.data-room-doc-card.md +21 -22
  225. package/src/components/chat/entity-cards/.dispatch.md +41 -24
  226. package/src/components/chat/entity-cards/.entity-author-card.md +36 -33
  227. package/src/components/chat/entity-cards/.entity-portrait-card.md +47 -0
  228. package/src/components/chat/entity-cards/.generic-entity-card.md +26 -25
  229. package/src/components/chat/entity-cards/.github-activity-card.md +35 -41
  230. package/src/components/chat/entity-cards/.hubspot-ticket-card.md +22 -28
  231. package/src/components/chat/entity-cards/.index.md +33 -44
  232. package/src/components/chat/entity-cards/.investor-update-card.md +36 -25
  233. package/src/components/chat/entity-cards/.onboarding-guide-card.md +20 -29
  234. package/src/components/chat/entity-cards/.product-release-card-defaults.md +26 -19
  235. package/src/components/chat/entity-cards/.program-card.md +30 -33
  236. package/src/components/chat/entity-cards/.roadmap-card.md +40 -31
  237. package/src/components/chat/entity-cards/.roadmap-vote-button.md +33 -36
  238. package/src/components/chat/entity-cards/.slack-message-card.md +31 -32
  239. package/src/components/chat/entity-cards/.use-cover-image-fallback.md +47 -0
  240. package/src/components/chat/entity-cards/.use-entity-card-link.md +51 -0
  241. package/src/components/chat/entity-cards/.use-entity-card-placeholder.md +47 -0
  242. package/src/components/chat/entity-cards/.what-i-shipped-card.md +43 -0
  243. package/src/components/chat/hooks/.index.md +33 -30
  244. package/src/components/chat/hooks/.use-chat-card-item.md +30 -17
  245. package/src/components/chat/hooks/.use-chat-dialog-manager.md +59 -0
  246. package/src/components/chat/hooks/.use-chat-identity.md +37 -25
  247. package/src/components/chat/hooks/.use-chunk-catchup.md +53 -36
  248. package/src/components/chat/hooks/.use-empty-state-config.md +39 -0
  249. package/src/components/chat/hooks/.use-jetstream-dialog-subscription.md +65 -30
  250. package/src/components/chat/hooks/.use-nats-chat-adapter.md +55 -36
  251. package/src/components/chat/hooks/.use-nats-dialog-subscription.md +66 -50
  252. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +58 -31
  253. package/src/components/chat/hooks/.use-scripted-stream.md +46 -0
  254. package/src/components/chat/hooks/.use-slash-commands.md +32 -25
  255. package/src/components/chat/hooks/.use-sse-chat-adapter.md +32 -21
  256. package/src/components/chat/hooks/.use-unified-chat.md +45 -35
  257. package/src/components/chat/types/.api.types.md +49 -41
  258. package/src/components/chat/types/.chat.types.md +56 -34
  259. package/src/components/chat/types/.component.types.md +36 -36
  260. package/src/components/chat/types/.context-item.types.md +61 -0
  261. package/src/components/chat/types/.index.md +32 -17
  262. package/src/components/chat/types/.message.types.md +44 -45
  263. package/src/components/chat/types/.network.types.md +46 -46
  264. package/src/components/chat/types/.processing.types.md +49 -37
  265. package/src/components/chat/types/.unified-chat-state.types.md +41 -26
  266. package/src/components/chat/types/entities/.content-ref.md +20 -31
  267. package/src/components/chat/types/entities/.investor-update.md +14 -16
  268. package/src/components/chat/types/entities/.onboarding-guide.md +35 -34
  269. package/src/components/chat/types/entities/.program-types.md +55 -43
  270. package/src/components/chat/utils/.chip-action-class.md +17 -19
  271. package/src/components/chat/utils/.chip-styles.md +18 -18
  272. package/src/components/chat/utils/.chunk-parser.md +48 -37
  273. package/src/components/chat/utils/.compact-card-classes.md +33 -35
  274. package/src/components/chat/utils/.execute-navigation.md +49 -0
  275. package/src/components/chat/utils/.extract-incomplete-message-state.md +9 -12
  276. package/src/components/chat/utils/.history-merge.md +65 -0
  277. package/src/components/chat/utils/.icon-library.md +33 -31
  278. package/src/components/chat/utils/.icon-registry.md +44 -0
  279. package/src/components/chat/utils/.index.md +37 -33
  280. package/src/components/chat/utils/.is-cross-origin-url.md +26 -22
  281. package/src/components/chat/utils/.message-segment-accumulator.md +46 -25
  282. package/src/components/chat/utils/.nav-anchor-props.md +29 -34
  283. package/src/components/chat/utils/.nav-click-handler.md +29 -33
  284. package/src/components/chat/utils/.process-historical-messages.md +29 -33
  285. package/src/components/chat/utils/.scripted-stream.md +52 -0
  286. package/src/components/chat/utils/.source-row-cta.md +32 -31
  287. package/src/components/chat/utils/.tool-call-helpers.md +25 -17
  288. package/src/components/contact/.contact-form.md +24 -19
  289. package/src/components/docs/.doc-navigation-context.md +49 -0
  290. package/src/components/docs/.doc-viewer.md +64 -0
  291. package/src/components/docs/.docs-hub-page.md +49 -0
  292. package/src/components/docs/.index.md +51 -0
  293. package/src/components/docs/.skeletons.md +47 -0
  294. package/src/components/docs/.use-docs-resolve-link.md +61 -0
  295. package/src/components/docs/.use-document-tree.md +83 -0
  296. package/src/components/docs/.use-scroll-spy.md +58 -0
  297. package/src/components/embeds/.embed-container.md +45 -0
  298. package/src/components/embeds/.embed-iframe.md +51 -0
  299. package/src/components/embeds/.figma-embed.md +43 -0
  300. package/src/components/embeds/.file-download-card.md +47 -0
  301. package/src/components/embeds/.google-sheets-viewer.md +40 -0
  302. package/src/components/embeds/.index.md +58 -0
  303. package/src/components/embeds/.linkedin-embed-client.md +50 -0
  304. package/src/components/embeds/.markdown-image.md +40 -0
  305. package/src/components/embeds/.og-link-preview.md +54 -0
  306. package/src/components/embeds/.pdf-viewer.md +43 -0
  307. package/src/components/embeds/.reddit-embed-client.md +40 -0
  308. package/src/components/embeds/.rich-markdown-runtime.md +53 -0
  309. package/src/components/embeds/.twitter-embed-client.md +44 -0
  310. package/src/components/faq/.faq-document-page.md +68 -0
  311. package/src/components/faq/.faq-section.md +55 -0
  312. package/src/components/faq/.index.md +29 -0
  313. package/src/components/faq/.json-ld.md +48 -0
  314. package/src/components/features/.array-entry-manager.md +32 -24
  315. package/src/components/features/.auth-providers-list.md +33 -32
  316. package/src/components/features/.captions-url.md +43 -0
  317. package/src/components/features/.cards-strip.md +48 -0
  318. package/src/components/features/.changelog-manager.md +17 -20
  319. package/src/components/features/.command-box.md +11 -16
  320. package/src/components/features/.entity-summary-editor.md +20 -15
  321. package/src/components/features/.entity-tag-badges.md +43 -0
  322. package/src/components/features/.entity-video-section.md +31 -41
  323. package/src/components/features/.figma-prototype-viewer.md +55 -37
  324. package/src/components/features/.filters-dropdown.md +14 -13
  325. package/src/components/features/.highlight-config-section.md +19 -16
  326. package/src/components/features/.highlight-video-combined-section.md +28 -19
  327. package/src/components/features/.highlight-video-preview.md +13 -11
  328. package/src/components/features/.highlight-video-section.md +26 -22
  329. package/src/components/features/.index.md +40 -36
  330. package/src/components/features/.media-gallery-manager.md +37 -35
  331. package/src/components/features/.mux-origins.md +46 -0
  332. package/src/components/features/.og-editor-preview.md +29 -24
  333. package/src/components/features/.os-type-badge-group.md +26 -18
  334. package/src/components/features/.os-type-badge.md +41 -24
  335. package/src/components/features/.paths-display.md +42 -25
  336. package/src/components/features/.platform-badge.md +26 -17
  337. package/src/components/features/.policy-configuration-panel.md +33 -31
  338. package/src/components/features/.provider-button.md +29 -20
  339. package/src/components/features/.push-button-selector.md +39 -41
  340. package/src/components/features/.release-media-manager.md +41 -34
  341. package/src/components/features/.section-selector.md +27 -16
  342. package/src/components/features/.select-button.md +21 -32
  343. package/src/components/features/.seo-editor-preview.md +49 -36
  344. package/src/components/features/.social-links-manager.md +40 -18
  345. package/src/components/features/.start-with-openframe-button.md +30 -32
  346. package/src/components/features/.status-filter-component.md +29 -38
  347. package/src/components/features/.tags-selector.md +25 -29
  348. package/src/components/features/.transcript-summary-editor.md +24 -18
  349. package/src/components/features/.use-video-warmup.md +47 -0
  350. package/src/components/features/.video-bites-display.md +36 -37
  351. package/src/components/features/.video-bites-shared.md +44 -0
  352. package/src/components/features/.video-bites-strip.md +63 -0
  353. package/src/components/features/.video-center-badge.md +38 -0
  354. package/src/components/features/.video-ratio-tabs.md +38 -31
  355. package/src/components/features/.video-source-selector.md +24 -27
  356. package/src/components/features/.video.md +35 -27
  357. package/src/components/features/.waitlist-form.md +39 -33
  358. package/src/components/features/.warning-block.md +53 -0
  359. package/src/components/features/ai-enrich/.AIEnrichSection.md +24 -25
  360. package/src/components/features/ai-enrich/.AIRequiredBadge.md +22 -21
  361. package/src/components/features/ai-enrich/.AIStatusIndicator.md +42 -35
  362. package/src/components/features/ai-enrich/.AIWarningsSection.md +23 -20
  363. package/src/components/features/ai-enrich/.ConfidenceBadge.md +35 -20
  364. package/src/components/features/auth/.auth-benefits-panel.md +48 -0
  365. package/src/components/features/auth/.auth-branding.md +51 -0
  366. package/src/components/features/auth/.auth-shell.md +51 -0
  367. package/src/components/features/auth/.back-to-login-link.md +48 -0
  368. package/src/components/features/auth/.complete-account-form.md +45 -0
  369. package/src/components/features/auth/.create-organization-form.md +53 -0
  370. package/src/components/features/auth/.index.md +48 -0
  371. package/src/components/features/auth/.invite-link-invalid-modal.md +42 -0
  372. package/src/components/features/auth/.login-form.md +42 -0
  373. package/src/components/features/auth/.openframe-sso-login-form.md +46 -0
  374. package/src/components/features/auth/.openframe-sso-signup-form.md +51 -0
  375. package/src/components/features/auth/.password-reset-form.md +57 -0
  376. package/src/components/features/auth/.sso-auth-shell.md +35 -0
  377. package/src/components/features/auth/.sso-providers.md +42 -0
  378. package/src/components/features/auth/.terms-agreement-label.md +28 -0
  379. package/src/components/features/board/.board-column-header.md +24 -18
  380. package/src/components/features/board/.board-column.md +32 -32
  381. package/src/components/features/board/.board-ticket-approval.md +42 -0
  382. package/src/components/features/board/.board.md +38 -38
  383. package/src/components/features/board/.index.md +35 -38
  384. package/src/components/features/board/.ticket-card.md +33 -36
  385. package/src/components/features/board/.types.md +38 -31
  386. package/src/components/features/card-hit-layer.tsx +84 -0
  387. package/src/components/features/floating-walkthrough-video.tsx +891 -0
  388. package/src/components/features/index.ts +4 -0
  389. package/src/components/features/notifications/.approval-request-notification-tile.md +45 -0
  390. package/src/components/features/notifications/.index.md +35 -31
  391. package/src/components/features/notifications/.notification-drawer.md +32 -19
  392. package/src/components/features/notifications/.notification-popups.md +51 -0
  393. package/src/components/features/notifications/.notification-tile.md +43 -27
  394. package/src/components/features/notifications/.notifications-context.md +42 -45
  395. package/src/components/features/notifications/.types.md +51 -31
  396. package/src/components/features/ticket-status-config-list/.ticket-status-config-list.md +51 -0
  397. package/src/components/features/time-tracker/.index.md +48 -0
  398. package/src/components/features/time-tracker/.time-tracker-context.md +55 -0
  399. package/src/components/features/time-tracker/.time-tracker-header-button.md +51 -0
  400. package/src/components/features/time-tracker/.time-tracker-panel.md +58 -0
  401. package/src/components/features/time-tracker/.types.md +51 -0
  402. package/src/components/features/time-tracker/.use-tracker-clock.md +32 -0
  403. package/src/components/features/use-walkthrough-video.ts +70 -0
  404. package/src/components/features/video-bites-strip.tsx +46 -146
  405. package/src/components/features/video-center-badge.tsx +11 -2
  406. package/src/components/features/video-hover-preview.tsx +201 -0
  407. package/src/components/features/video.tsx +368 -73
  408. package/src/components/help-center-pages/.delivery-page.md +51 -0
  409. package/src/components/help-center-pages/.index.md +51 -0
  410. package/src/components/help-center-pages/.onboarding-guides-catalog-page.md +53 -0
  411. package/src/components/help-center-pages/.product-releases-list-page.md +51 -0
  412. package/src/components/help-center-pages/.roadmap-page.md +46 -0
  413. package/src/components/icons/.figma-icon.md +13 -7
  414. package/src/components/icons/.index.md +26 -22
  415. package/src/components/icons/.vendor-directory-icon.md +30 -18
  416. package/src/components/icons/.x-logo.md +26 -10
  417. package/src/components/icons-v2-generated/.index.md +30 -41
  418. package/src/components/icons-v2-generated/brand-logos/.index.md +25 -19
  419. package/src/components/icons-v2-generated/communication/.index.md +26 -30
  420. package/src/components/icons-v2-generated/logos/.flamingo-logo-icon.md +41 -0
  421. package/src/components/layout/.article-detail-layout.md +32 -21
  422. package/src/components/layout/.page-container.md +25 -23
  423. package/src/components/layout/.page-heading.md +48 -0
  424. package/src/components/layout/.page-layout.md +30 -25
  425. package/src/components/layout/.title-block.md +43 -24
  426. package/src/components/loading/.card-skeleton.md +28 -35
  427. package/src/components/loading/.category-card-skeleton.md +25 -23
  428. package/src/components/loading/.device-card-skeleton.md +22 -15
  429. package/src/components/loading/.margin-report-skeleton.md +30 -35
  430. package/src/components/loading/.openmsp-heartbeat.md +30 -32
  431. package/src/components/loading/.organization-card-skeleton.md +22 -25
  432. package/src/components/loading/.organization-icon-skeleton.md +39 -27
  433. package/src/components/loading/.page-layout-skeleton.md +50 -28
  434. package/src/components/loading/.unified-skeleton.md +37 -40
  435. package/src/components/loading/.users-grid-skeleton.md +19 -38
  436. package/src/components/navigation/.app-header.md +35 -28
  437. package/src/components/navigation/.app-layout-context.md +48 -0
  438. package/src/components/navigation/.app-layout-drawer.md +56 -0
  439. package/src/components/navigation/.app-layout.md +24 -23
  440. package/src/components/navigation/.header-mingo-button.md +46 -0
  441. package/src/components/navigation/.header-organization-filter.md +32 -18
  442. package/src/components/navigation/.header.md +24 -31
  443. package/src/components/navigation/.index.md +36 -50
  444. package/src/components/navigation/.mingo-ai-button.md +41 -0
  445. package/src/components/navigation/.mobile-burger-menu.md +29 -29
  446. package/src/components/navigation/.mobile-nav-panel.md +41 -49
  447. package/src/components/navigation/.multi-level-navigation.md +76 -0
  448. package/src/components/navigation/.navigation-sidebar-item.md +14 -14
  449. package/src/components/navigation/.navigation-sidebar.md +30 -23
  450. package/src/components/navigation/.sliding-sidebar.md +30 -25
  451. package/src/components/navigation/.sticky-section-nav.md +23 -21
  452. package/src/components/onboarding-guides/.index.md +40 -0
  453. package/src/components/onboarding-guides/.onboarding-guide-detail-view.md +55 -0
  454. package/src/components/onboarding-guides/.onboarding-guides-catalog-skeleton.md +33 -0
  455. package/src/components/onboarding-guides/.onboarding-guides-catalog-view.md +50 -0
  456. package/src/components/onboarding-guides/hooks/.use-onboarding-guides.md +54 -0
  457. package/src/components/platform/.CveLink.md +28 -17
  458. package/src/components/platform/.ScriptArguments.md +18 -23
  459. package/src/components/platform/.ScriptInfoSection.md +27 -21
  460. package/src/components/platform/.ShellTypeBadge.md +34 -18
  461. package/src/components/platform/.SoftwareInfo.md +23 -21
  462. package/src/components/platform/.ToolBadge.md +18 -16
  463. package/src/components/profile/.ProfileLoadingSkeleton.md +24 -23
  464. package/src/components/related-content/.index.md +41 -0
  465. package/src/components/related-content/.related-content-section.md +62 -0
  466. package/src/components/shared/.article-author-byline.md +56 -0
  467. package/src/components/shared/.detail-page-skeleton.md +32 -43
  468. package/src/components/shared/.media-gallery-strip.md +43 -0
  469. package/src/components/shared/delivery/.delivery-lists.md +20 -19
  470. package/src/components/shared/delivery/.delivery-row.md +24 -20
  471. package/src/components/shared/delivery/.delivery-table.md +20 -22
  472. package/src/components/shared/dev-section/.dev-card-row.md +28 -36
  473. package/src/components/shared/dev-section/.dev-section-page.md +36 -25
  474. package/src/components/shared/dev-section/.dev-section-view.md +32 -22
  475. package/src/components/shared/doc-search/.doc-search-bar.md +58 -0
  476. package/src/components/shared/doc-search/.doc-search-result-row.md +50 -0
  477. package/src/components/shared/doc-search/.format-relative-path.md +30 -0
  478. package/src/components/shared/doc-search/.index.md +46 -0
  479. package/src/components/shared/doc-search/.map-doc-search-results.md +44 -0
  480. package/src/components/shared/doc-search/.resolve-search-result-action.md +52 -0
  481. package/src/components/shared/doc-search/.types.md +49 -0
  482. package/src/components/shared/doc-search/.use-doc-search.md +66 -0
  483. package/src/components/shared/legal-document/.legal-document-page.md +26 -20
  484. package/src/components/shared/legal-document/.use-legal-docs.md +36 -30
  485. package/src/components/shared/onboarding/.onboarding-step-card.md +24 -16
  486. package/src/components/shared/onboarding/.onboarding-walkthrough.md +42 -28
  487. package/src/components/shared/product-release/.index.md +33 -24
  488. package/src/components/shared/product-release/.product-release-card-skeleton.md +22 -28
  489. package/src/components/shared/product-release/.product-release-card.md +28 -29
  490. package/src/components/shared/product-release/.product-releases-view.md +49 -0
  491. package/src/components/shared/product-release/.release-detail-page.md +30 -15
  492. package/src/components/shared/roadmap/.index.md +29 -24
  493. package/src/components/shared/roadmap/.roadmap-grid.md +43 -54
  494. package/src/components/shared/roadmap/.roadmap-view.md +41 -0
  495. package/src/components/shared/roadmap/.use-roadmap-voting.md +27 -17
  496. package/src/components/switch.tsx +1 -1
  497. package/src/components/tickets/.help-center-card.md +32 -28
  498. package/src/components/tickets/.help-center-create-form.md +34 -22
  499. package/src/components/tickets/.help-center-list.md +36 -25
  500. package/src/components/tickets/.index.md +30 -45
  501. package/src/components/tickets/.ticket-center.md +31 -38
  502. package/src/components/tickets/.ticket-detail-drawer.md +20 -30
  503. package/src/components/tickets/.ticket-open-form.md +24 -26
  504. package/src/components/tickets/.ticket-reply-composer.md +51 -0
  505. package/src/components/tickets/.ticket-row.md +26 -11
  506. package/src/components/tickets/.types.md +56 -38
  507. package/src/components/tickets/hooks/.use-ticket-actions.md +60 -47
  508. package/src/components/tickets/hooks/.use-ticket-engagements.md +27 -27
  509. package/src/components/tickets/hooks/.use-tickets-list.md +24 -30
  510. package/src/components/ui/.accordion.md +28 -21
  511. package/src/components/ui/.actions-menu.md +36 -54
  512. package/src/components/ui/.ai-generated-badge.md +25 -0
  513. package/src/components/ui/.alert-dialog.md +21 -16
  514. package/src/components/ui/.alert.md +30 -25
  515. package/src/components/ui/.allowed-domains-input.md +26 -15
  516. package/src/components/ui/.announcement-bar-view.md +46 -0
  517. package/src/components/ui/.autocomplete.md +40 -27
  518. package/src/components/ui/.badge.md +34 -31
  519. package/src/components/ui/.brand-association-card.md +22 -18
  520. package/src/components/ui/.breadcrumb.md +18 -14
  521. package/src/components/ui/.bullet-list.md +35 -23
  522. package/src/components/ui/.card.md +47 -30
  523. package/src/components/ui/.checkbox-block.md +34 -22
  524. package/src/components/ui/.checkbox-with-description.md +18 -20
  525. package/src/components/ui/.chevron-button.md +29 -29
  526. package/src/components/ui/.circular-progress.md +30 -41
  527. package/src/components/ui/.color-preset-select.md +54 -0
  528. package/src/components/ui/.color-swatch.md +37 -0
  529. package/src/components/ui/.content-loader.md +34 -27
  530. package/src/components/ui/.crossfade-panels.md +50 -0
  531. package/src/components/ui/.cursor-pagination.md +38 -26
  532. package/src/components/ui/.dashboard-info-card.md +46 -27
  533. package/src/components/ui/.date-picker.md +52 -36
  534. package/src/components/ui/.device-card-compact.md +23 -20
  535. package/src/components/ui/.device-card.md +39 -30
  536. package/src/components/ui/.dialog.md +14 -14
  537. package/src/components/ui/.drawer.md +45 -39
  538. package/src/components/ui/.dropdown-button.md +41 -33
  539. package/src/components/ui/.dropdown-menu.md +40 -23
  540. package/src/components/ui/.duration-input.md +41 -0
  541. package/src/components/ui/.entity-image.md +32 -28
  542. package/src/components/ui/.error-state.md +30 -26
  543. package/src/components/ui/.fade-preview.md +44 -0
  544. package/src/components/ui/.field-wrapper.md +34 -23
  545. package/src/components/ui/.file-upload.md +30 -18
  546. package/src/components/ui/.filter-modal.md +30 -32
  547. package/src/components/ui/.filter-pill-row.md +58 -0
  548. package/src/components/ui/.floating-tooltip.md +27 -32
  549. package/src/components/ui/.hidden-tags-popup.md +18 -20
  550. package/src/components/ui/.highlight-card.md +36 -23
  551. package/src/components/ui/.honeypot-field.md +41 -0
  552. package/src/components/ui/.hover-dropdown.md +44 -31
  553. package/src/components/ui/.image-gallery-modal.md +28 -30
  554. package/src/components/ui/.image-uploader.md +39 -22
  555. package/src/components/ui/.index.md +33 -31
  556. package/src/components/ui/.info-card-row.md +50 -0
  557. package/src/components/ui/.info-card.md +27 -20
  558. package/src/components/ui/.info-row.md +30 -24
  559. package/src/components/ui/.info-section.md +63 -0
  560. package/src/components/ui/.input.md +29 -28
  561. package/src/components/ui/.label.md +34 -26
  562. package/src/components/ui/.markdown-editor.md +26 -25
  563. package/src/components/ui/.marquee-wall.md +64 -0
  564. package/src/components/ui/.menubar.md +50 -16
  565. package/src/components/ui/.modal-v2.md +41 -27
  566. package/src/components/ui/.modal.md +22 -19
  567. package/src/components/ui/.more-actions-menu.md +30 -24
  568. package/src/components/ui/.navigation-menu.md +40 -25
  569. package/src/components/ui/.organization-card.md +46 -27
  570. package/src/components/ui/.page-actions.md +55 -41
  571. package/src/components/ui/.page-loader.md +40 -23
  572. package/src/components/ui/.password-input.md +30 -0
  573. package/src/components/ui/.phone-input.md +20 -22
  574. package/src/components/ui/.portal-container.md +43 -0
  575. package/src/components/ui/.progress-bar.md +40 -27
  576. package/src/components/ui/.release-changelog-section.md +32 -26
  577. package/src/components/ui/.rich-markdown-renderer.md +45 -0
  578. package/src/components/ui/.scroll-fade.md +56 -0
  579. package/src/components/ui/.search-input.md +55 -37
  580. package/src/components/ui/.select.md +25 -26
  581. package/src/components/ui/.service-card.md +25 -19
  582. package/src/components/ui/.sheet.md +21 -21
  583. package/src/components/ui/.simple-markdown-renderer.md +36 -24
  584. package/src/components/ui/.slack-channel-chip.md +49 -0
  585. package/src/components/ui/.square-avatar.md +29 -26
  586. package/src/components/ui/.stacked-rows-panel.md +61 -0
  587. package/src/components/ui/.status-badge.md +30 -24
  588. package/src/components/ui/.status-indicator.md +35 -21
  589. package/src/components/ui/.tab-content.md +34 -22
  590. package/src/components/ui/.tab-navigation.md +40 -50
  591. package/src/components/ui/.tab-selector.md +48 -31
  592. package/src/components/ui/.tabs.md +34 -21
  593. package/src/components/ui/.tag-search-input.md +20 -20
  594. package/src/components/ui/.tag-select-dropdown.md +59 -0
  595. package/src/components/ui/.tag.md +25 -43
  596. package/src/components/ui/.tags-input.md +31 -31
  597. package/src/components/ui/.textarea.md +32 -30
  598. package/src/components/ui/.ticket-attachments-list.md +36 -29
  599. package/src/components/ui/.ticket-info-section.md +37 -42
  600. package/src/components/ui/.ticket-status-config-row.md +66 -0
  601. package/src/components/ui/.ticket-status-tag.md +39 -35
  602. package/src/components/ui/.toaster.md +21 -15
  603. package/src/components/ui/.toggle.md +38 -37
  604. package/src/components/ui/.tooltip.md +16 -13
  605. package/src/components/ui/button/.button-styles.md +17 -18
  606. package/src/components/ui/button/.button.md +30 -37
  607. package/src/components/ui/button/.split-button.md +46 -23
  608. package/src/components/ui/button/button-styles.ts +13 -0
  609. package/src/components/ui/button/button.tsx +5 -0
  610. package/src/components/ui/data-table/.data-table-body.md +50 -35
  611. package/src/components/ui/data-table/.data-table-empty.md +17 -33
  612. package/src/components/ui/data-table/.data-table-header.md +43 -40
  613. package/src/components/ui/data-table/.data-table-row.md +31 -31
  614. package/src/components/ui/data-table/.types.md +23 -28
  615. package/src/components/ui/file-manager/.file-manager-breadcrumb.md +16 -24
  616. package/src/components/ui/file-manager/.file-manager-empty.md +23 -15
  617. package/src/components/ui/file-manager/.file-manager-skeleton.md +34 -36
  618. package/src/components/ui/file-manager/.file-manager-table-row.md +29 -29
  619. package/src/components/ui/file-manager/.file-manager-table.md +39 -24
  620. package/src/components/ui/no-data/.index.md +22 -0
  621. package/src/components/ui/no-data/.no-data-actions.md +29 -0
  622. package/src/components/ui/no-data/.no-data-message.md +37 -0
  623. package/src/components/ui/no-data/.no-data-styles.md +39 -0
  624. package/src/components/ui/no-data/.no-data.md +59 -0
  625. package/src/components/ui/page-actions.tsx +2 -1
  626. package/src/components/ui/query-report-table/.query-report-table-header.md +15 -15
  627. package/src/components/ui/query-report-table/.query-report-table-row.md +16 -16
  628. package/src/components/ui/query-report-table/.query-report-table.md +39 -24
  629. package/src/components/ui/table/.table-column-filter-dropdown.md +15 -9
  630. package/src/components/ui/table/.table-description-cell.md +13 -12
  631. package/src/components/ui/table/.table-empty-state.md +19 -20
  632. package/src/components/ui/table/.table-row.md +30 -20
  633. package/src/components/ui/table/.table-skeleton.md +32 -27
  634. package/src/components/ui/table/.table-timestamp-cell.md +24 -23
  635. package/src/components/ui/table/.table.md +43 -30
  636. package/src/components/ui/table/.types.md +49 -45
  637. package/src/components/ui/table/.use-table-motion.md +45 -0
  638. package/src/contexts/.chat-runtime-context.md +55 -26
  639. package/src/contexts/.endpoints-runtime-context.md +40 -44
  640. package/src/embed-shims/.next-navigation.md +38 -31
  641. package/src/hooks/.api-hooks.md +28 -21
  642. package/src/hooks/.index.md +30 -25
  643. package/src/hooks/.use-authed-image-src.md +38 -0
  644. package/src/hooks/.use-contact-submission.md +35 -36
  645. package/src/hooks/.use-humanity-signals.md +47 -0
  646. package/src/hooks/.use-near-viewport.md +30 -30
  647. package/src/hooks/.use-og-placeholder-url.md +57 -0
  648. package/src/hooks/.use-scroll-to-hash.md +46 -0
  649. package/src/hooks/.use-self-fetch.md +52 -0
  650. package/src/hooks/state/.flatten-schema.md +37 -29
  651. package/src/hooks/ui/.index.md +37 -25
  652. package/src/hooks/ui/.use-auto-limit-tags.md +55 -38
  653. package/src/hooks/ui/.use-deferred-error.md +44 -0
  654. package/src/hooks/ui/.use-focus-trap.md +59 -0
  655. package/src/hooks/ui/.use-header-height.md +37 -0
  656. package/src/hooks/ui/.use-horizontal-scrollbar.md +25 -34
  657. package/src/hooks/ui/.use-marquee-engine.md +73 -0
  658. package/src/hooks/ui/.use-notification-permission.md +49 -0
  659. package/src/hooks/ui/.use-reset-on-page-hidden.md +40 -0
  660. package/src/hooks/ui/.use-suppress-clone-focus.md +41 -0
  661. package/src/nats/.index.md +39 -0
  662. package/src/nats/.nats-provider.md +46 -0
  663. package/src/nats/.nats.md +38 -28
  664. package/src/nats/.shared-connection.md +56 -0
  665. package/src/nats/.use-nats-subscription.md +57 -0
  666. package/src/stories/.ActionsMenu.stories.md +57 -0
  667. package/src/stories/.Alert.stories.md +46 -0
  668. package/src/stories/.AnnouncementBar.stories.md +59 -32
  669. package/src/stories/.AppLayoutDrawer.stories.md +60 -0
  670. package/src/stories/.AppLayoutSidebar.stories.md +45 -0
  671. package/src/stories/.ApprovalBatchMessage.stories.md +46 -0
  672. package/src/stories/.ApprovalBatchMessageClient.stories.md +55 -0
  673. package/src/stories/.ApprovalRequestMessage.stories.md +50 -0
  674. package/src/stories/.ApprovalRequestMessageClient.stories.md +41 -0
  675. package/src/stories/.BlogCard.stories.md +52 -0
  676. package/src/stories/.Board.stories.md +25 -20
  677. package/src/stories/.Button.stories.md +35 -40
  678. package/src/stories/.CampaignCardAdmin.stories.md +38 -0
  679. package/src/stories/.CardsStrip.stories.md +39 -0
  680. package/src/stories/.CaseStudyCard.stories.md +41 -0
  681. package/src/stories/.ChatContainer.stories.md +41 -0
  682. package/src/stories/.ChatInlineVideoPill.stories.md +36 -0
  683. package/src/stories/.ChatMessageEnhanced.stories.md +51 -0
  684. package/src/stories/.ChatMingoInfoCards.stories.md +30 -0
  685. package/src/stories/.ChatTicketList.stories.md +38 -22
  686. package/src/stories/.ChatTypingIndicator.stories.md +47 -0
  687. package/src/stories/.CheckboxBlock.stories.md +34 -35
  688. package/src/stories/.ContextCompactionDisplay.stories.md +28 -0
  689. package/src/stories/.CustomerInterviewCard.stories.md +36 -0
  690. package/src/stories/.DashboardInfoCard.stories.md +40 -33
  691. package/src/stories/.DataRoomDocCard.stories.md +42 -0
  692. package/src/stories/.DataTable.stories.md +37 -32
  693. package/src/stories/.DatePicker.stories.md +34 -30
  694. package/src/stories/.EmbeddableChat.stories.md +45 -28
  695. package/src/stories/.EmptyState.stories.md +55 -0
  696. package/src/stories/.ErrorMessageDisplay.stories.md +45 -0
  697. package/src/stories/.FilterModal.stories.md +28 -22
  698. package/src/stories/.GenericEntityCard.stories.md +50 -0
  699. package/src/stories/.GitHubActivityCard.stories.md +41 -0
  700. package/src/stories/.GuideWelcome.stories.md +38 -0
  701. package/src/stories/.Header.stories.md +32 -22
  702. package/src/stories/.HubspotTicketCard.stories.md +41 -0
  703. package/src/stories/.InfoCard.stories.md +37 -26
  704. package/src/stories/.InfoCardRow.stories.md +48 -0
  705. package/src/stories/.InfoSection.stories.md +55 -0
  706. package/src/stories/.Input.stories.md +45 -35
  707. package/src/stories/.InvestorUpdateCard.stories.md +41 -0
  708. package/src/stories/.MarqueeWall.stories.md +53 -0
  709. package/src/stories/.MingoAiButton.stories.md +31 -0
  710. package/src/stories/.MingoChatModals.stories.md +47 -0
  711. package/src/stories/.MingoInfoCard.stories.md +50 -0
  712. package/src/stories/.MingoOnboardingCard.stories.md +19 -19
  713. package/src/stories/.MingoWelcome.stories.md +41 -0
  714. package/src/stories/.MobileNavPanel.stories.md +52 -0
  715. package/src/stories/.Modal.stories.md +19 -16
  716. package/src/stories/.MspOrganizationCard.stories.md +45 -0
  717. package/src/stories/.NotificationDrawer.stories.md +39 -28
  718. package/src/stories/.OSTypeBadge.stories.md +34 -25
  719. package/src/stories/.OnboardingGuideCard.stories.md +41 -0
  720. package/src/stories/.PageActions.stories.md +21 -21
  721. package/src/stories/.PageContainer.stories.md +29 -24
  722. package/src/stories/.PageLayout.stories.md +18 -35
  723. package/src/stories/.PolicyConfigurationPanel.stories.md +58 -0
  724. package/src/stories/.ProductReleaseCard.stories.md +39 -0
  725. package/src/stories/.ProgramCard.stories.md +51 -0
  726. package/src/stories/.QuickActionChip.stories.md +59 -0
  727. package/src/stories/.QuickActionMarquee.stories.md +51 -0
  728. package/src/stories/.QuickActionWall.stories.md +58 -0
  729. package/src/stories/.RoadmapCard.stories.md +41 -0
  730. package/src/stories/.ShellTypeBadge.stories.md +42 -22
  731. package/src/stories/.SlackMessageCard.stories.md +42 -0
  732. package/src/stories/.SlashCommandSuggestions.stories.md +21 -31
  733. package/src/stories/.SplitButton.stories.md +23 -25
  734. package/src/stories/.StackedRowsPanel.stories.md +65 -0
  735. package/src/stories/.TabSelector.stories.md +26 -38
  736. package/src/stories/.Tag.stories.md +32 -27
  737. package/src/stories/.ThinkingDisplay.stories.md +43 -0
  738. package/src/stories/.TicketCard.stories.md +60 -0
  739. package/src/stories/.TicketInfoSection.stories.md +28 -20
  740. package/src/stories/.TimeTracker.stories.md +35 -0
  741. package/src/stories/.Toaster.stories.md +26 -20
  742. package/src/stories/.ToolBadge.stories.md +34 -24
  743. package/src/stories/.ToolExecutionDisplay.stories.md +22 -20
  744. package/src/stories/.ToolExecutionDisplayClient.stories.md +33 -0
  745. package/src/stories/.VideoBitesStrip.stories.md +37 -0
  746. package/src/stories/.WarningBlock.stories.md +51 -0
  747. package/src/stories/__fixtures__/.chat-card-decorator.md +39 -0
  748. package/src/stories/__fixtures__/.chat-cards.md +57 -0
  749. package/src/stories/auth/.CompleteAccount.stories.md +36 -0
  750. package/src/stories/auth/.CreateOrganization.stories.md +52 -0
  751. package/src/stories/auth/.InviteLinkInvalid.stories.md +21 -0
  752. package/src/stories/auth/.Login.stories.md +32 -0
  753. package/src/stories/auth/.OpenFrameSso.stories.md +58 -0
  754. package/src/stories/auth/.PasswordReset.stories.md +49 -0
  755. package/src/styles/ods-interaction-states.css +32 -0
  756. package/src/types/.announcement.md +67 -35
  757. package/src/types/.blog.md +44 -41
  758. package/src/types/.case-study.md +31 -17
  759. package/src/types/.content-ref.md +37 -0
  760. package/src/types/.customer-interview.md +38 -38
  761. package/src/types/.doc-source.md +87 -0
  762. package/src/types/.entity-author.md +53 -0
  763. package/src/types/.entity-platform.md +40 -0
  764. package/src/types/.index.md +23 -17
  765. package/src/types/.marketing.md +32 -35
  766. package/src/types/.navigation.md +31 -19
  767. package/src/types/.os.types.md +30 -31
  768. package/src/types/.permissions.md +49 -35
  769. package/src/types/.platform.md +44 -28
  770. package/src/types/.product-release.md +59 -42
  771. package/src/types/.shell.types.md +46 -30
  772. package/src/types/.supabase.md +43 -29
  773. package/src/types/.tool.types.md +43 -23
  774. package/src/types/.video-processing.md +40 -31
  775. package/src/types/supabase.ts +80 -0
  776. package/src/types/video-processing.ts +1 -0
  777. package/src/utils/.announcement-storage.md +49 -17
  778. package/src/utils/.app-config.md +22 -18
  779. package/src/utils/.brand-marks.md +46 -0
  780. package/src/utils/.cn.md +40 -29
  781. package/src/utils/.color-analysis.md +34 -25
  782. package/src/utils/.common.md +43 -23
  783. package/src/utils/.content-href.md +64 -0
  784. package/src/utils/.content-ref-groups.md +51 -0
  785. package/src/utils/.date-utils.md +46 -35
  786. package/src/utils/.doc-path-utils.md +38 -0
  787. package/src/utils/.doc-tree-nav.md +51 -0
  788. package/src/utils/.embed-authed-fetch.md +48 -32
  789. package/src/utils/.embed-content-fetch.md +36 -0
  790. package/src/utils/.embed-url-converters.md +52 -0
  791. package/src/utils/.extract-items.md +49 -0
  792. package/src/utils/.faq-anchor.md +48 -0
  793. package/src/utils/.format.md +73 -55
  794. package/src/utils/.humanity-signals.md +59 -0
  795. package/src/utils/.index.md +73 -31
  796. package/src/utils/.list-url.md +43 -0
  797. package/src/utils/.markdown-section-extractor.md +57 -0
  798. package/src/utils/.markdown-to-plain.md +51 -0
  799. package/src/utils/.media-carousel-utils-stub.md +42 -31
  800. package/src/utils/.ods-color-utils.md +84 -43
  801. package/src/utils/.og-placeholder.md +45 -0
  802. package/src/utils/.page-header-constants.md +26 -0
  803. package/src/utils/.platform-config.md +34 -34
  804. package/src/utils/.release-badge.md +47 -0
  805. package/src/utils/.release-cover.md +45 -0
  806. package/src/utils/.same-page-hash-nav.md +45 -0
  807. package/src/utils/.scroll-into-view.md +32 -25
  808. package/src/utils/.seo-description.md +37 -0
  809. package/src/utils/.seo-title.md +25 -0
  810. package/src/utils/.social-embed-cache.md +37 -0
  811. package/src/utils/.source-icons.md +30 -34
  812. package/src/utils/.suggestion-url.md +46 -0
  813. package/src/utils/.tool-utils.md +39 -24
  814. package/src/utils/.tree-builder.md +64 -0
  815. package/src/utils/.video-bite-id.md +42 -0
  816. package/src/utils/announcement-storage.ts +9 -18
  817. package/src/utils/dev-sections/.dev-section-param-keys.md +51 -0
  818. package/src/utils/dev-sections/.openframe-dev-sections.md +40 -28
  819. package/src/utils/dismiss-cookie.ts +55 -0
  820. package/src/utils/dismissal-storage.ts +38 -0
  821. package/src/utils/index.ts +3 -0
  822. package/dist/chunk-BBUEY73L.js.map +0 -1
  823. package/dist/chunk-G3UN2DXU.cjs.map +0 -1
  824. package/dist/chunk-OWMLWJQY.cjs.map +0 -1
  825. package/dist/chunk-SASR7SK5.js.map +0 -1
  826. package/src/components/chat/utils/.onboarding-icons.md +0 -28
  827. package/src/hooks/.use-og-placeholder.md +0 -51
  828. /package/dist/{chunk-MGKCS6KS.js.map → chunk-3WCARCI4.js.map} +0 -0
  829. /package/dist/{chunk-Y3Y2DLNO.js.map → chunk-6UGYBCEN.js.map} +0 -0
  830. /package/dist/{chunk-PSPWT2Q5.js.map → chunk-7LWXWXEY.js.map} +0 -0
  831. /package/dist/{chunk-VTSRDC4W.js.map → chunk-CKC3EAYT.js.map} +0 -0
  832. /package/dist/{chunk-ZRANZNEA.js.map → chunk-JJE5TUZK.js.map} +0 -0
  833. /package/dist/{chunk-66BNAFO6.js.map → chunk-JQIUBFHL.js.map} +0 -0
  834. /package/dist/{chunk-2DIOV7BR.js.map → chunk-UPN6EFOM.js.map} +0 -0
  835. /package/dist/{chunk-4SMRQ4SN.js.map → chunk-WBL5Y4PS.js.map} +0 -0
  836. /package/dist/{chunk-FP4H6Y5O.js.map → chunk-WPYPSIY2.js.map} +0 -0
  837. /package/dist/{chunk-R5WJJL4V.js.map → chunk-X5X43HFV.js.map} +0 -0
  838. /package/dist/{chunk-DEHZ5CTC.js.map → chunk-XTWKDMCG.js.map} +0 -0
@@ -0,0 +1,891 @@
1
+ "use client";
2
+
3
+ /**
4
+ * <FloatingWalkthroughVideo> — THE generic, embeddable per-platform demo-video
5
+ * widget. A collapsed card pinned bottom-left (bite-identical hover grammar via
6
+ * <VideoHoverPreviewSurface>) that opens a large in-page theater (Radix Dialog
7
+ * primitives, NOT native fullscreen) showing ONLY the video: a bare 16:9 stage
8
+ * with the player's own controls + captions. No card chrome, no summary.
9
+ *
10
+ * All UI lives here in the lib so every platform site AND the react-embedding
11
+ * example mount the same component; the host supplies only the video data.
12
+ *
13
+ * STATE MODEL (read before adding a boolean — the recurring bugs in this file
14
+ * all came from ad-hoc flags contradicting each other):
15
+ * `cardMode` — ONE discriminant: 'resume' | 'preview' | 'poster'. Derived,
16
+ * never stored, so the two players can't both be mounted.
17
+ * `cardMuted` / `cardPaused` — transport state for whichever card player is
18
+ * live. Both toggles ALWAYS render in both states; a control
19
+ * that hides itself on use is a dead end (that bug shipped
20
+ * twice). Never gate a control's visibility on its own value.
21
+ * `cardFallback` — autoplay-BLOCKED prompt only. Distinct from `cardMuted`
22
+ * (user intent); conflating them is what made unmute vanish.
23
+ * `handoff` — theater→card continuation snapshot. Clearing it UNMOUNTS the
24
+ * mini player, so never clear it to express "paused".
25
+ * Layering: the media layer is `pointer-events-none` so a paused/idle player
26
+ * can never swallow a click; every non-control click reaches the activation
27
+ * overlay. Corners are exclusive: transport TL, dismiss TR, title+presenter BL.
28
+ *
29
+ * Audio invariant (both directions): never two overlapping streams, nor a blip.
30
+ * - open: pause the hover preview synchronously (pointerdown), force the
31
+ * surface inactive for the whole open duration, reset the YouTube suspend.
32
+ * - close: snapshot then pause the theater player (file) / suspend (YouTube).
33
+ * NOTE: this theater's Content carries no exit animation, so Radix unmounts
34
+ * it in the same commit and the iframe dies before the suspend postMessage
35
+ * can run. The suspend path exists for hosts that DO animate the exit; here
36
+ * the unmount itself is what stops the audio.
37
+ */
38
+
39
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
40
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
41
+ import { cn } from '../../utils/cn';
42
+ import { Button } from '../ui/button';
43
+ import { SquareAvatar } from '../ui/square-avatar';
44
+ import { DialogPortal, DialogOverlay } from '../ui/dialog';
45
+ import { VideoHoverPreviewSurface } from './video-hover-preview';
46
+ import { CardHitLayer } from './card-hit-layer';
47
+ import { VideoPlayBadge, VideoUnmuteGlyph } from './video-center-badge';
48
+ import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon';
49
+ import { VolumeUpIcon } from '../icons-v2-generated/audio-and-visual/volume-up-icon';
50
+ import { PauseIcon } from '../icons-v2-generated/media-playback/pause-icon';
51
+ import { PlayIcon } from '../icons-v2-generated/media-playback/play-icon';
52
+ import { VolumeXmarkIcon } from '../icons-v2-generated/audio-and-visual/volume-xmark-icon';
53
+ import { Video, type VideoPlayerHandle, type VideoMutedFallbackState } from './video';
54
+ import {
55
+ WALKTHROUGH_VIDEO_DISMISS_KEY,
56
+ isWalkthroughDismissed,
57
+ dismissWalkthrough,
58
+ } from '../../utils/dismissal-storage';
59
+
60
+ /** Wire-shape data for the widget. Kept as the shared contract the hub DAL
61
+ * re-exports as `PublicWalkthroughVideo & { id }`. `mainVideoUrl`/`youtubeUrl`
62
+ * stay SEPARATE — YouTube wins when both are set (card matches theater). */
63
+ export interface WalkthroughVideoData {
64
+ /** Row id — used for id-match cookie dismissal (a new video re-shows the
65
+ * card even after an old one was dismissed). */
66
+ id?: string | number;
67
+ mainVideoUrl?: string | null;
68
+ youtubeUrl?: string | null;
69
+ posterUrl?: string | null;
70
+ /** RELATIVE VTT path (/api/captions/...); embedders prefix their proxy base. */
71
+ captionsUrl?: string | null;
72
+ title?: string | null;
73
+ presenterAvatarUrl?: string | null;
74
+ }
75
+
76
+ export interface FloatingWalkthroughVideoProps {
77
+ video: WalkthroughVideoData | null | undefined;
78
+ open?: boolean;
79
+ onOpenChange?: (open: boolean) => void;
80
+ defaultOpen?: boolean;
81
+ label?: string;
82
+ appearDelayMs?: number;
83
+ /** Cookie-based dismissal (id-match, mirrors the announcement bar). `false`
84
+ * disables the X entirely. `storageKey` is the per-platform cookie name. */
85
+ dismissal?: { storageKey?: string } | false;
86
+ hideNearSelector?: string;
87
+ /** Route identity from the host (the lib can't observe navigation). Changing
88
+ * it re-queries the footer IO target. */
89
+ pathname?: string;
90
+ className?: string;
91
+ }
92
+
93
+ /** "Paused at the end" — used by BOTH the close snapshot and the reopen seed,
94
+ * which must agree or a finished video reopens at its own last frame. */
95
+ const isAtEnd = (duration: number, time: number) => duration > 0 && duration - time < 1;
96
+
97
+ const WALKTHROUGH_Z = 'z-[9980]'; // one layer BELOW the chat dock (z-[9990]).
98
+
99
+ interface Handoff {
100
+ time: number;
101
+ muted: boolean;
102
+ playing: boolean;
103
+ }
104
+
105
+ export function FloatingWalkthroughVideo({
106
+ video,
107
+ open: openProp,
108
+ onOpenChange,
109
+ defaultOpen,
110
+ label = 'Play Demo Video',
111
+ appearDelayMs = 3000,
112
+ dismissal = {},
113
+ hideNearSelector = 'footer',
114
+ pathname,
115
+ className,
116
+ }: FloatingWalkthroughVideoProps): React.ReactElement | null {
117
+ const dismissEnabled = dismissal !== false;
118
+ const storageKey = (dismissEnabled && dismissal.storageKey) || WALKTHROUGH_VIDEO_DISMISS_KEY;
119
+ // id-match dismissal key: a new video (new id) re-shows the card. No id
120
+ // (embedder) → a stable presence marker so dismissal still works.
121
+ const dismissalId = video?.id != null ? String(video.id) : 'dismissed';
122
+
123
+ // --- mount gate (never LCP; cookie read happens post-delay, no hydration mismatch) ---
124
+ const [mounted, setMounted] = useState(false);
125
+ const [dismissed, setDismissed] = useState(false);
126
+ useEffect(() => {
127
+ let idleHandle: number | null = null;
128
+ // Wait for the data: on the embedder path `video` starts null, so the
129
+ // dismissal id was 'dismissed' on the first pass and a dismissed card
130
+ // flashed for the length of a second appear-delay before hiding.
131
+ if (!video) return; // wait for data — see the id-based dep below
132
+ const timer = setTimeout(() => {
133
+ const reveal = () => {
134
+ if (dismissEnabled && isWalkthroughDismissed(storageKey, dismissalId)) {
135
+ setDismissed(true);
136
+ return;
137
+ }
138
+ // Cleared on the way through: a client-only embedder can swap to a NEW
139
+ // video id in-session (the cookie is id-matched, so the new one isn't
140
+ // dismissed), and a sticky `dismissed` kept the card hidden until a
141
+ // full reload — contradicting the id-match behaviour documented above.
142
+ setDismissed(false);
143
+ setMounted(true);
144
+ };
145
+ const ric = (window as unknown as { requestIdleCallback?: (cb: () => void) => number }).requestIdleCallback;
146
+ if (typeof ric === 'function') idleHandle = ric(reveal);
147
+ else reveal();
148
+ }, appearDelayMs);
149
+ return () => {
150
+ clearTimeout(timer);
151
+ const cic = (window as unknown as { cancelIdleCallback?: (h: number) => void }).cancelIdleCallback;
152
+ if (idleHandle !== null && typeof cic === 'function') cic(idleHandle);
153
+ };
154
+ // `video?.id`, not the object: a new identity (embedder refetch, re-seeded
155
+ // RSC payload) would otherwise restart the appear delay.
156
+ }, [appearDelayMs, dismissEnabled, storageKey, dismissalId, video?.id]);
157
+
158
+ // --- environment preferences (read after mount; no SSR access) ---
159
+ const [previewSuppressed, setPreviewSuppressed] = useState(false);
160
+ useEffect(() => {
161
+ try {
162
+ const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
163
+ const conn = (navigator as unknown as { connection?: { saveData?: boolean; effectiveType?: string } }).connection;
164
+ const saveData = conn?.saveData === true || conn?.effectiveType === '2g' || conn?.effectiveType === 'slow-2g';
165
+ setPreviewSuppressed(Boolean(reducedMotion || saveData));
166
+ } catch { /* ignore */ }
167
+ }, []);
168
+
169
+ // --- controlled/uncontrolled open ---
170
+ const [openState, setOpenState] = useState(Boolean(defaultOpen));
171
+ const open = openProp !== undefined ? openProp : openState;
172
+
173
+ // --- refs & continuation state ---
174
+ const previewHandleRef = useRef<VideoPlayerHandle | null>(null);
175
+ const theaterHandleRef = useRef<VideoPlayerHandle | null>(null);
176
+ // Why the theater reports its fallback state even though it renders no card
177
+ // control: `blocked`/`muted` distinguish "the user muted this" from "the
178
+ // browser refused sound". Only the former may become standing intent.
179
+ const theaterForcedMuteRef = useRef(false);
180
+ const resumeHandleRef = useRef<VideoPlayerHandle | null>(null);
181
+ const closeButtonRef = useRef<HTMLButtonElement | null>(null);
182
+ // TRUE only when the user pressed Mute. An autoplay-forced mute (cardFallback)
183
+ // is NOT intent, so the theater must not inherit it — conflating the two is
184
+ // what made the theater open silently after a blocked hover preview.
185
+ const userMutedRef = useRef(false);
186
+ const endedLatchRef = useRef(false);
187
+
188
+ const [hovered, setHovered] = useState(false);
189
+ const [handoff, setHandoff] = useState<Handoff | null>(null);
190
+ const [suspended, setSuspended] = useState(false);
191
+ const [theaterStart, setTheaterStart] = useState<{ time: number; muted: boolean }>({ time: 0, muted: false });
192
+ const [footerHidden, setFooterHidden] = useState(false);
193
+ const [cardFallback, setCardFallback] = useState<VideoMutedFallbackState>({ muted: false, blocked: false });
194
+ // Card-player transport state, driving the persistent mute/play TOGGLES.
195
+ // Separate from `cardFallback` (which is the autoplay-blocked prompt).
196
+ const [cardMuted, setCardMuted] = useState(false);
197
+ const [cardPaused, setCardPaused] = useState(false);
198
+ // Renderable mirror of `userMutedRef` — the ref is for synchronous reads in
199
+ // callbacks, this is what the player subtree observes. Deliberately NOT
200
+ // `cardMuted`: that also tracks policy-forced mutes.
201
+ const [userMuted, setUserMuted] = useState(false);
202
+
203
+ const isYouTube = Boolean(video?.youtubeUrl);
204
+ // PRESENCE-based: any handoff means "the card owns this video at this
205
+ // timestamp"; `playing` only seeds the pause toggle. Keying it on `playing`
206
+ // made a paused close (or a tab-switch) a one-way door — the card lost its
207
+ // transport AND its hover preview with no way back. `!open` keeps a
208
+ // host-controlled `open` from running the card and theater at once.
209
+ const resumeMode = !open && handoff !== null && !isYouTube;
210
+ const previewAllowed = !previewSuppressed && !isYouTube;
211
+
212
+ // --- footer fade (re-query on pathname change; null-tolerant) ---
213
+ useEffect(() => {
214
+ if (!mounted) return;
215
+ const el = typeof document !== 'undefined' ? document.querySelector(hideNearSelector) : null;
216
+ if (!el || typeof IntersectionObserver === 'undefined') {
217
+ setFooterHidden(false);
218
+ return;
219
+ }
220
+ const io = new IntersectionObserver(
221
+ entries => setFooterHidden(entries[0]?.isIntersecting ?? false),
222
+ { rootMargin: '0px 0px -40px 0px' },
223
+ );
224
+ io.observe(el);
225
+ return () => io.disconnect();
226
+ }, [mounted, hideNearSelector, pathname]);
227
+
228
+ // --- tab-hidden: pause resume-mode playback, keep the timestamp ---
229
+ useEffect(() => {
230
+ if (!resumeMode) return;
231
+ const onVis = () => {
232
+ if (document.visibilityState === 'hidden') {
233
+ const h = resumeHandleRef.current;
234
+ if (h) {
235
+ // Pause in place. Rewriting the handoff here used to unmount the
236
+ // mini player, so a background tab-switch silently destroyed it.
237
+ h.pause();
238
+ setCardPaused(true);
239
+ }
240
+ }
241
+ };
242
+ document.addEventListener('visibilitychange', onVis);
243
+ return () => document.removeEventListener('visibilitychange', onVis);
244
+ }, [resumeMode]);
245
+
246
+ // Faded out behind the footer: stop audio. The card is `pointer-events-none`
247
+ // while hidden, so a still-playing player would be unreachable — the same
248
+ // reason the tab-hidden handler exists.
249
+ // Depends on cardMode too: a player that MOUNTS while the card is already
250
+ // hidden (e.g. the footer scrolled into view during the theater) would
251
+ // otherwise autoplay behind an invisible, pointer-inert card.
252
+ useEffect(() => {
253
+ if (!footerHidden) return;
254
+ const h = resumeHandleRef.current ?? previewHandleRef.current;
255
+ try { h?.pause(); } catch { /* ignore */ }
256
+ setCardPaused(true);
257
+ setHovered(false);
258
+ // `resumeMode`/`hovered` (not the derived cardMode, declared below) are the
259
+ // inputs that decide whether a player is mounted.
260
+ }, [footerHidden, resumeMode, hovered]);
261
+
262
+ // Render-synced mirror of the derived `cardMode`, which is declared BELOW
263
+ // the callbacks that need it. Reading state declared later from a memoized
264
+ // callback is the exact trap that made every hover-preview open restart at
265
+ // 0:00 (the closure kept its render-0 value), so this is a ref, not a dep.
266
+ const cardModeRef = useRef<'resume' | 'preview' | 'poster'>('poster');
267
+
268
+ // Set by the card's OWN handlers, consumed by the controlled-mode sync
269
+ // below. Without it, a controlled host flipping `open` in response to
270
+ // `onOpenChange` looks identical to a genuinely host-originated open, and
271
+ // the sync clobbers the seed/snapshot the handler just computed.
272
+ const selfDrivenForRef = useRef<boolean | null>(null);
273
+
274
+ // Set on every close so the focus Radix restores to the hit layer can't be
275
+ // mistaken for the user tabbing to the card. CONSUMED BY THE FOCUS HANDLER
276
+ // (see commitOpen) — the timer beside it is only a safety valve.
277
+ const justClosedRef = useRef(false);
278
+ const justClosedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
279
+
280
+ const commitOpen = useCallback((next: boolean) => {
281
+ if (!next) {
282
+ // CONSUMED BY THE FOCUS HANDLER, not by a timer racing Radix.
283
+ // Radix's FocusScope restores focus from its OWN `setTimeout(…, 0)`,
284
+ // registered during cleanup — i.e. AFTER any timer we schedule here.
285
+ // A timer-cleared latch was therefore always false by the time the
286
+ // restore landed, and Escape-close still restarted preview audio.
287
+ // The timer below is only a safety valve for the case where no focus
288
+ // restoration arrives at all (host-driven close, no focus to return).
289
+ justClosedRef.current = true;
290
+ if (justClosedTimerRef.current) clearTimeout(justClosedTimerRef.current);
291
+ justClosedTimerRef.current = setTimeout(() => {
292
+ justClosedTimerRef.current = null;
293
+ justClosedRef.current = false;
294
+ }, 400);
295
+ }
296
+ if (openProp === undefined) setOpenState(next);
297
+ onOpenChange?.(next);
298
+ }, [openProp, onOpenChange]);
299
+
300
+ // Synchronous preview pause on pointerdown — the window-capture activation
301
+ // waiter unmutes a pre-activation preview on the first pointerdown; without
302
+ // this the opening/dismissing press itself blips at 50% volume.
303
+ const pausePreviewNow = useCallback(() => {
304
+ // Whichever player is live — in resume mode that is the continuation
305
+ // player, so pausing only the preview left a playing card audible through
306
+ // the whole pointerdown -> click -> commit window.
307
+ const h = resumeHandleRef.current ?? previewHandleRef.current;
308
+ try { h?.pause(); } catch { /* ignore */ }
309
+ // Keep the toggle honest: an aborted press (pointerdown then drag away)
310
+ // otherwise left it reading "Pause" over a paused video.
311
+ setCardPaused(true);
312
+ }, []);
313
+
314
+ const openTheater = useCallback(() => {
315
+ // Captured BEFORE `pausePreviewNow()` below. NOTE this only helps keyboard
316
+ // activation: a mouse click already ran the hit layer's `onPointerDown`
317
+ // pause, so on that path the element IS paused here and the test below
318
+ // still reduces to `liveTime > 0.5`. Kept because Enter/Space dispatches
319
+ // no pointerdown, and there the live playing state is real.
320
+ const handleBeforePause = resumeHandleRef.current ?? previewHandleRef.current;
321
+ const liveWasPlaying = handleBeforePause?.getPaused() === false;
322
+ // Idempotent: keyboard activation (Enter/Space) dispatches no pointerdown,
323
+ // so the synchronous audio-stop guard would otherwise be skipped.
324
+ pausePreviewNow();
325
+ // Seed the theater from whatever the card was ACTUALLY playing, so the
326
+ // theater continues at the same second in every direction:
327
+ // resume player (live) > hover preview (live) > paused handoff snapshot.
328
+ // Reading the preview handle is what makes "hover-preview → click → theater"
329
+ // continue mid-video instead of restarting at 0. Pausing on pointerdown does
330
+ // not reset currentTime, so the value is still accurate here.
331
+ // Source chosen by MODE. Picking "whichever handle reports > 0.5s" let a
332
+ // stale hover-preview position (0:05) shadow a stored handoff (3:20) on
333
+ // desktop while mobile — which never hovers — resumed correctly: the same
334
+ // gesture gave different results per platform.
335
+ // Read the refs directly — no render-scoped state. The previous version
336
+ // consulted `cardActive`, which is declared BELOW this callback and is not
337
+ // a dependency, so the memoized closure always saw its render-0 value
338
+ // (false) and every hover-preview -> theater open restarted at 0:00.
339
+ // Mode-gated: the card is `position: fixed`, so its preview player is
340
+ // permanently in-viewport and is NEVER unmounted — it keeps currentTime
341
+ // from a hover abandoned minutes ago. Reading it unconditionally made
342
+ // "Play Demo Video" silently start 10s in. Only a LIVE mode may seed.
343
+ const liveHandle =
344
+ resumeHandleRef.current ?? (cardModeRef.current === 'preview' ? previewHandleRef.current : null);
345
+ const liveTime = liveHandle?.getCurrentTime() ?? 0;
346
+ // A preview that ran to completion keeps its currentTime, so without the
347
+ // same at-end guard the close path uses, clicking the card would open the
348
+ // theater seeked to the final frame and it would instantly re-end.
349
+ const liveDuration = liveHandle?.getDuration() ?? 0;
350
+ const liveAtEnd = isAtEnd(liveDuration, liveTime);
351
+ const start = {
352
+ // At end -> start over. Falling back to `handoff.time` here would rewind
353
+ // to an unrelated older anchor (e.g. 3:20) instead of restarting.
354
+ time: liveAtEnd ? 0 : (liveTime > 0.5 ? liveTime : (handoff?.time ?? 0)),
355
+ // Live element wins over remembered intent (the theater's own chrome may
356
+ // have changed it) — EXCEPT when the element is muted only because
357
+ // autoplay policy forced it, which is not intent.
358
+ // `cardFallback.muted` is armed BOTH by autoplay policy and by our own
359
+ // `startMuted` (i.e. by the user's mute), so it alone cannot mean
360
+ // "forced". Only a fallback the user never asked for is policy-forced;
361
+ // otherwise an explicitly muted video reopened at full volume.
362
+ // Trust the live element only when it has actually been playing; a
363
+ // freshly remounted poster-mode player always reports muted=false and
364
+ // would silently discard the user's last explicit mute.
365
+ muted: (cardFallback.muted && !userMutedRef.current)
366
+ ? false
367
+ : ((liveHandle && (liveTime > 0.5 || liveWasPlaying))
368
+ ? liveHandle.getMuted()
369
+ : userMutedRef.current),
370
+ };
371
+ setTheaterStart(start);
372
+ setHovered(false); // force preview inactive
373
+ setSuspended(false); // reset YouTube suspend for this open
374
+ endedLatchRef.current = false;
375
+ setHandoff(null); // resume player unmounts
376
+ selfDrivenForRef.current = true;
377
+ commitOpen(true);
378
+ }, [handoff, cardFallback.muted, commitOpen]);
379
+
380
+ // Sync for open/close transitions the card did NOT originate — a host
381
+ // driving the controlled `open` prop from its own UI. Card-originated
382
+ // transitions are latched by `selfDrivenRef` and skipped here, because the
383
+ // handler already did the seeding (and a controlled host flips `open` in
384
+ // RESPONSE to that handler, which is indistinguishable from a host-driven
385
+ // change without the latch). `defaultOpen` is NOT covered: it is the
386
+ // uncontrolled initial value, and the initial `theaterStart` already equals
387
+ // what this seed would compute. This MUST run during the
388
+ // render that flips `open` — not in an effect: the Dialog's Content (and
389
+ // therefore MuxPlayer) mounts in that same render, and `startTime` /
390
+ // `autoPlay` / `startMuted` are LOAD-TIME props read once at construction.
391
+ // An effect commits one render too late, so the player would already have
392
+ // been built from the previous `theaterStart` and a stale `suspended`
393
+ // would post `pauseVideo` at mount. This is React's documented
394
+ // "adjust state while rendering" pattern; the extra render is discarded
395
+ // before children are committed.
396
+ const [prevOpen, setPrevOpen] = useState(open);
397
+ if (open !== prevOpen) {
398
+ setPrevOpen(open);
399
+ // Compared, not cleared: React can discard and re-run a render attempt
400
+ // (concurrent interruption, error retry, a host wrapping setOpen in
401
+ // startTransition). A read-and-clear would decide differently on the
402
+ // retry; an equality test gives the same answer every time.
403
+ const selfDriven = selfDrivenForRef.current === open;
404
+ if (openProp !== undefined && !selfDriven) {
405
+ if (open) {
406
+ // A host open has no originating gesture, but resume mode DOES have a
407
+ // live element — reading only `handoff` discarded however long the mini
408
+ // player had been running since the close. Mirrors openTheater's seed.
409
+ const liveResume = resumeHandleRef.current;
410
+ const liveTime = liveResume?.getCurrentTime() ?? 0;
411
+ const liveAtEnd = isAtEnd(liveResume?.getDuration() ?? 0, liveTime);
412
+ setTheaterStart({
413
+ time: liveAtEnd ? 0 : (liveTime > 0.5 ? liveTime : (handoff?.time ?? 0)),
414
+ muted: liveResume && liveTime > 0.5 ? liveResume.getMuted() : userMutedRef.current,
415
+ });
416
+ setHovered(false);
417
+ setSuspended(false);
418
+ endedLatchRef.current = false;
419
+ setHandoff(null);
420
+ } else {
421
+ // Falling edge: the host bypassed handleOpenChange, so without this the
422
+ // position, the mute intent and the ended state are all simply lost.
423
+ const h = theaterHandleRef.current;
424
+ if (h && !isYouTube) {
425
+ const time = h.getCurrentTime();
426
+ const muted = h.getMuted();
427
+ const paused = h.getPaused();
428
+ const atEnd = isAtEnd(h.getDuration(), time);
429
+ try { h.pause(); } catch { /* already gone */ }
430
+ if (!(muted && theaterForcedMuteRef.current && !userMutedRef.current)) {
431
+ userMutedRef.current = muted;
432
+ setUserMuted(muted);
433
+ }
434
+ setHandoff(
435
+ (endedLatchRef.current && paused && atEnd) || time < 1
436
+ ? null
437
+ : { time, muted, playing: !paused },
438
+ );
439
+ } else if (isYouTube) {
440
+ setSuspended(true);
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ const handleOpenChange = useCallback((next: boolean) => {
447
+ if (next) { openTheater(); return; }
448
+ // This close IS the card's own — a controlled host will flip `open` in
449
+ // response, and the sync above must not re-snapshot the player we are
450
+ // about to pause (it would read paused=true and downgrade a playing
451
+ // handoff to paused).
452
+ selfDrivenForRef.current = false;
453
+ // Closing: snapshot + stop the theater player BEFORE the exit animation.
454
+ if (isYouTube) {
455
+ setSuspended(true);
456
+ commitOpen(false);
457
+ return;
458
+ }
459
+ const h = theaterHandleRef.current;
460
+ if (h) {
461
+ const time = h.getCurrentTime();
462
+ const muted = h.getMuted();
463
+ // The theater's own chrome can mute/unmute; that IS user intent, so it
464
+ // must flow back or reopening would contradict what they just did.
465
+ // EXCEPT when the element is muted only because unmuted autoplay was
466
+ // rejected: promoting that into intent silently muted every later hover
467
+ // preview and every later theater open, for the whole session, with no
468
+ // gesture from the user. Mirrors the same test `openTheater` already
469
+ // applies in the opposite direction.
470
+ const policyForcedMute = muted && theaterForcedMuteRef.current && !userMutedRef.current;
471
+ if (!policyForcedMute) {
472
+ userMutedRef.current = muted;
473
+ setUserMuted(muted);
474
+ }
475
+ const paused = h.getPaused();
476
+ const duration = h.getDuration();
477
+ const atEnd = isAtEnd(duration, time);
478
+ h.pause();
479
+ if (!paused) {
480
+ // Live playing snapshot always wins (beats a stale ended latch).
481
+ setHandoff({ time, muted, playing: true });
482
+ } else if (endedLatchRef.current && atEnd) {
483
+ setHandoff(null); // finished — plain poster, no continuation
484
+ } else if (time < 1) {
485
+ setHandoff(null); // degenerate close — keep hover preview enabled
486
+ } else {
487
+ setHandoff({ time, muted, playing: false });
488
+ }
489
+ }
490
+ commitOpen(false);
491
+ }, [isYouTube, commitOpen, openTheater]);
492
+
493
+ const onCardFallbackChange = useCallback((s: VideoMutedFallbackState) => {
494
+ setCardFallback(s);
495
+ setCardMuted(s.muted); // sync the toggle with fallback / chrome unmutes
496
+ }, []);
497
+
498
+ // Seed transport state whenever a card player (re)starts.
499
+ useEffect(() => {
500
+ if (resumeMode) { setCardMuted(handoff?.muted ?? false); setCardPaused(handoff?.playing === false); }
501
+ // eslint-disable-next-line react-hooks/exhaustive-deps
502
+ }, [resumeMode]);
503
+ // A fresh hover starts a fresh preview — clear the paused flag, and re-seed
504
+ // the mute toggle from the user's standing intent so an explicit mute is not
505
+ // silently undone by the hover-start path (which unmutes the element).
506
+ useEffect(() => {
507
+ if (hovered) { setCardPaused(false); setCardMuted(userMutedRef.current); }
508
+ }, [hovered]);
509
+
510
+ const dismiss = useCallback((e: React.MouseEvent) => {
511
+ e.stopPropagation();
512
+ pausePreviewNow();
513
+ if (dismissEnabled) dismissWalkthrough(storageKey, dismissalId);
514
+ setDismissed(true);
515
+ }, [dismissEnabled, storageKey, dismissalId, pausePreviewNow]);
516
+
517
+ // Card controls act on whichever player is live (resume continuation or the
518
+ // hover preview). The internal glyph is unreachable under the activation
519
+ // overlay (hideMutedBadge), so these are the only affordances.
520
+ const activeHandle = () => (resumeMode ? resumeHandleRef.current : previewHandleRef.current);
521
+
522
+ /** Big centered glyph — the muted-fallback prompt (unmute, or play when even
523
+ * muted autoplay was blocked). Unchanged bite grammar. */
524
+ const onUnmuteOrPlay = useCallback((e: React.MouseEvent) => {
525
+ e.stopPropagation();
526
+ const h = activeHandle();
527
+ if (!h) return;
528
+ try {
529
+ // Branch on the LABEL this button is currently rendering, so the action
530
+ // always matches the words. Guarding the unmute on `!userMutedRef.current`
531
+ // (the previous attempt) turned the "Unmute" case into a no-op — and
532
+ // because the corner mute toggle hides while this glyph is up, that left
533
+ // the card with NO way to restore sound. A control that hides itself on
534
+ // use is a dead end; see the state-model note at the top of this file.
535
+ // Recomputed here, NOT read from the render-scoped `controlIsPlay`
536
+ // (declared below this callback — reading it would repeat the stale
537
+ // closure bug that silently restarted the theater at 0:00).
538
+ const actionIsPlay = cardFallback.blocked || cardPaused;
539
+ if (actionIsPlay) {
540
+ void h.play();
541
+ setCardPaused(false);
542
+ } else {
543
+ h.setMuted(false);
544
+ setCardMuted(false);
545
+ userMutedRef.current = false;
546
+ setUserMuted(false);
547
+ void h.play();
548
+ setCardPaused(false);
549
+ }
550
+ } catch { /* ignore */ }
551
+ }, [resumeMode, cardFallback.blocked, cardPaused]);
552
+
553
+ /** Mute TOGGLE — stays on screen in both states so the user can come back. */
554
+ const onToggleMute = useCallback((e: React.MouseEvent) => {
555
+ e.stopPropagation();
556
+ const h = activeHandle();
557
+ if (!h) return;
558
+ // Live element, not the `cardMuted` snapshot: the module-level activation
559
+ // waiter can unmute between this button's pointerdown and its click, which
560
+ // made the first press of a button labelled "Unmute" mute instead.
561
+ // Read the LIVE element, never `cardMuted`: the two diverge whenever the
562
+ // player's own chrome changed mute without telling us. (`getMuted()`
563
+ // returns `false` rather than nullish on a torn-down element, so a `??`
564
+ // fallback here would silently invert the button — hence the null guard
565
+ // above rather than a default.)
566
+ const next = !h.getMuted();
567
+ try {
568
+ h.setMuted(next);
569
+ if (!next && !cardPaused) void h.play(); // never override an explicit pause
570
+ setCardMuted(next);
571
+ userMutedRef.current = next;
572
+ setUserMuted(next);
573
+ } catch { /* ignore */ }
574
+ }, [resumeMode, cardMuted, cardPaused]);
575
+
576
+ /** Play/pause TOGGLE. Deliberately does NOT tear down the resume player
577
+ * (an earlier version cleared the handoff, which unmounted the mini player
578
+ * and made the controls vanish with no way back). */
579
+ const onTogglePlay = useCallback((e: React.MouseEvent) => {
580
+ e.stopPropagation();
581
+ const h = activeHandle();
582
+ if (!h) return;
583
+ try {
584
+ // Same predicate the label renders from (`controlIsPlay`). Branching on
585
+ // raw `cardPaused` meant that in the blocked state the button read "Play"
586
+ // but took the pause branch, so the first press did nothing.
587
+ if (cardFallback.blocked || cardPaused) { void h.play(); setCardPaused(false); }
588
+ else { h.pause(); setCardPaused(true); }
589
+ } catch { /* ignore */ }
590
+ }, [resumeMode, cardFallback.blocked, cardPaused]);
591
+
592
+ const summaryTitle = video?.title || undefined;
593
+
594
+ // Already proxied by the host (the hub's DAL applies the prefix AND its
595
+ // skip-list). Threading a prefix through the widget meant each host restated
596
+ // it and silently dropped the skip-list.
597
+ const presenterAvatarSrc = video?.presenterAvatarUrl ?? null;
598
+
599
+ const embedUrlKey = useMemo(() => `${video?.mainVideoUrl ?? ''}|${video?.youtubeUrl ?? ''}`, [video?.mainVideoUrl, video?.youtubeUrl]);
600
+
601
+ if (!video || (!video.mainVideoUrl && !video.youtubeUrl)) return null;
602
+
603
+ // The collapsed card is suppressed by the appear-delay mount gate and by
604
+ // dismissal — but the THEATER (Dialog) is NOT. A host that controls
605
+ // `open={true}` must be able to force the theater even before the card has
606
+ // appeared or after it was dismissed, so the gate lives on the card only.
607
+ const showCard = mounted && !dismissed;
608
+
609
+ // Gate on `!resumeMode`, NOT `!handoff`: a non-playing handoff is only a
610
+ // remembered timestamp. Gating on `handoff` froze the card as an inert poster
611
+ // after closing a paused theater (no hover preview, no transport, no way back
612
+ // except reopening) for the rest of the session.
613
+ const cardActive = !open && previewAllowed && hovered && !resumeMode;
614
+
615
+ // ONE derived mode drives every branch below. Deriving a single discriminant
616
+ // (instead of testing raw booleans at each call site) is what keeps the card
617
+ // consistent: 'resume' and 'preview' are mutually exclusive by construction,
618
+ // so a player can never be half-mounted or a control half-shown.
619
+ const cardMode: 'resume' | 'preview' | 'poster' =
620
+ resumeMode ? 'resume' : cardActive ? 'preview' : 'poster';
621
+
622
+ // Big centered glyph = the muted-fallback prompt (bite grammar), nothing else.
623
+ // Gated on an actual player: `cardFallback` is only ever rewritten by a
624
+ // mounted one, so after it unmounts (e.g. the resume clip ended) a stale
625
+ // `muted:true` left an orphan glyph that played from 0 with sound and no
626
+ // transport controls.
627
+ cardModeRef.current = cardMode;
628
+
629
+ const showBigUnmute = cardFallback.muted && cardMode !== 'poster';
630
+ // "Play" whenever the action will start playback: autoplay was blocked, or
631
+ // the user paused. Otherwise the label understated what the button does.
632
+ const controlIsPlay = cardFallback.blocked || cardPaused;
633
+ // Transport toggles stay visible for as long as a card player is mounted —
634
+ // in BOTH states of each toggle, so muting/pausing is always reversible.
635
+ const showPlaybackControls = cardMode !== 'poster';
636
+
637
+ // --- collapsed card (div root + overlay button + sibling X/unmute controls) ---
638
+ const collapsed = (
639
+ <div
640
+ className={cn(
641
+ // `group/card` is the SHARED hover contract: VideoHoverPreviewSurface's
642
+ // badge accents off it. It must live on an element that actually
643
+ // receives pointer events — the media layer is pointer-events-none, so
644
+ // a group inside it can never match :hover.
645
+ 'group/card pointer-events-auto relative overflow-hidden rounded-lg border border-ods-border bg-ods-card shadow-2xl',
646
+ 'aspect-video w-60 sm:w-80 transition-opacity duration-200',
647
+ footerHidden ? 'opacity-0 pointer-events-none' : 'opacity-100',
648
+ className,
649
+ )}
650
+ // `mouse` only: pointerenter also fires for touch, which started a
651
+ // preview on every tap. Focus mirrors hover so keyboard users can reach
652
+ // the transport controls at all.
653
+ onPointerEnter={e => { if (e.pointerType === 'mouse' && previewAllowed && !resumeMode) setHovered(true); }}
654
+ onPointerLeave={e => { if (e.pointerType === 'mouse') setHovered(false); }}
655
+ // Re-arm hover after a close that left the pointer parked on the card.
656
+ onPointerMove={e => { if (e.pointerType === 'mouse' && previewAllowed && !resumeMode && !hovered) setHovered(true); }}
657
+ // Keyboard intent only. Chrome focuses buttons on click, so a raw focus
658
+ // mirror autostarted an unmuted preview with the pointer nowhere near
659
+ // the card. `:focus-visible` alone is NOT enough: Radix restores focus
660
+ // to the hit layer on close, and after an Escape-close that restored
661
+ // focus IS :focus-visible — the guard admitted precisely the case it was
662
+ // written to block, so closing with Escape restarted audio in the
663
+ // corner. Hence the explicit just-closed latch.
664
+ onFocusCapture={e => {
665
+ const el = e.target as HTMLElement;
666
+ // `matches` THROWS SyntaxError on engines that don't know
667
+ // :focus-visible (Safari < 15.4) — that would escape a React handler.
668
+ let keyboard = false;
669
+ try { keyboard = el.matches?.(':focus-visible') ?? false; } catch { keyboard = false; }
670
+ // Consume the latch: the FIRST focus after a close is Radix returning
671
+ // focus to this element, never the user arriving at it.
672
+ if (justClosedRef.current) {
673
+ justClosedRef.current = false;
674
+ if (justClosedTimerRef.current) {
675
+ clearTimeout(justClosedTimerRef.current);
676
+ justClosedTimerRef.current = null;
677
+ }
678
+ return;
679
+ }
680
+ if (previewAllowed && !resumeMode && keyboard) setHovered(true);
681
+ }}
682
+ onBlurCapture={e => { if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setHovered(false); }}
683
+ >
684
+ {/* Media layer is CLICK-TRANSPARENT: a paused/idle player must never
685
+ swallow the click. Every click that isn't on a control falls through
686
+ to the activation overlay below and opens the theater. */}
687
+ <div className="pointer-events-none absolute inset-0">
688
+ {/* ONE player component for BOTH modes. The card used to render a
689
+ bespoke resume player next to the shared preview surface, and that
690
+ duplication is exactly why desktop (preview) and mobile (poster/
691
+ resume) diverged. Same component, same props, one behaviour. */}
692
+ <VideoHoverPreviewSurface
693
+ key={`card-${embedUrlKey}`}
694
+ // File player only. The theater resolves YouTube-wins; the card must
695
+ // not mount MuxPlayer on a youtube.com URL (it renders the poster).
696
+ url={video.mainVideoUrl || ''}
697
+ posterUrl={video.posterUrl}
698
+ active={cardActive}
699
+ isClone={false}
700
+ badge="play"
701
+ fit="cover"
702
+ posterSizes="320px"
703
+ preload="none"
704
+ hideMutedBadge
705
+ onMutedFallbackChange={onCardFallbackChange}
706
+ previewHandleRef={cardMode === 'resume' ? resumeHandleRef : previewHandleRef}
707
+ mutedIntent={userMuted}
708
+ continuation={cardMode === 'resume'}
709
+ // `!footerHidden`: mounting with autoplay and pausing in an effect
710
+ // loses to MuxPlayer re-issuing play when media is ready, which left
711
+ // audio running behind an invisible, pointer-inert card.
712
+ autoPlay={cardMode === 'resume' && handoff?.playing !== false && !footerHidden}
713
+ startTime={cardMode === 'resume' ? handoff!.time : undefined}
714
+ startMuted={cardMode === 'resume' ? handoff!.muted : false}
715
+ onEnded={cardMode === 'resume' ? () => setHandoff(null) : () => setCardPaused(true)}
716
+ />
717
+ </div>
718
+
719
+ {/* CONTENTLESS full-card hit layer. Deliberately a bare <button>, NOT the
720
+ DS <Button>: that component is an inline-flex, content-sized,
721
+ centre-aligned control, so as a full-bleed layer it collapsed around
722
+ its child — which dragged the title pill to the top-left (overlapping
723
+ the transport) and left most of the card unclickable. A hit layer has
724
+ no content and no typography; separating it from the label is what
725
+ makes both positions deterministic. */}
726
+ <CardHitLayer
727
+ label={video.title ? `${label}: ${video.title}` : label}
728
+ onPointerDown={pausePreviewNow}
729
+ onClick={openTheater}
730
+ className="z-20"
731
+ />
732
+
733
+ {/* Title pill — its own positioned element (bottom-left), never a child
734
+ of the hit layer. pointer-events-none so clicks fall through to it. */}
735
+ <span className="pointer-events-none absolute bottom-[var(--spacing-system-xsf)] left-[var(--spacing-system-xsf)] z-20 flex max-w-[calc(100%-2*var(--spacing-system-xsf))] items-center gap-[var(--spacing-system-xsf)] rounded-full bg-ods-overlay py-[var(--spacing-system-xxs)] pl-[var(--spacing-system-xxs)] pr-[var(--spacing-system-sf)] text-h6 text-ods-text-primary">
736
+ {presenterAvatarSrc ? (
737
+ <SquareAvatar src={presenterAvatarSrc} alt="" sizePx={20} variant="round" className="shrink-0 border-0" />
738
+ ) : (
739
+ <VideoPlayBadge size="sm" className="h-5 w-5 shrink-0" />
740
+ )}
741
+ <span className="truncate">{label}</span>
742
+ </span>
743
+
744
+ {/* BIG centred glyph — the muted-fallback prompt (bite grammar). It IS
745
+ interactive, but ONLY exists while the card is muted, so the two
746
+ intents never compete: muted → this button unmutes; once unmuted it
747
+ unmounts and the same click lands on the hit layer and opens the
748
+ theater. `size="icon-glyph"` keeps the glyph at its own 56px instead
749
+ of the DS default `[&_svg]:h-5`; `variant="glyph"` means no button
750
+ surface paints behind a glyph that already carries its own scrim. */}
751
+ {showBigUnmute && (
752
+ <Button
753
+ variant="glyph"
754
+ size="icon-glyph"
755
+ aria-label={controlIsPlay ? 'Play' : 'Unmute'}
756
+ title={controlIsPlay ? 'Play' : 'Unmute'}
757
+ onPointerDown={e => e.stopPropagation()}
758
+ onClick={onUnmuteOrPlay}
759
+ className="absolute inset-0 z-30 m-auto"
760
+ >
761
+ {controlIsPlay ? <VideoPlayBadge /> : <VideoUnmuteGlyph />}
762
+ </Button>
763
+ )}
764
+
765
+ {/* Transport toggles — mute/unmute + play/pause. Rendered in BOTH states
766
+ (never self-hiding) so every action is reversible.
767
+ CORNER MAP (each control owns exactly one corner, nothing overlaps):
768
+ top-left = transport top-right = dismiss
769
+ bottom-left = title pill (presenter avatar lives INSIDE it) */}
770
+ {showPlaybackControls && (
771
+ <div className="absolute left-[var(--spacing-system-xsf)] top-[var(--spacing-system-xsf)] z-30 flex items-center gap-[var(--spacing-system-xxs)]">
772
+ {/* Hidden while the big centre glyph owns unmuting, so the two are
773
+ never on screen with the same label and different behaviour. */}
774
+ <Button
775
+ variant="overlay"
776
+ size="icon-sm"
777
+ aria-label={cardMuted ? 'Unmute' : 'Mute'}
778
+ title={cardMuted ? 'Unmute' : 'Mute'}
779
+ onPointerDown={e => e.stopPropagation()}
780
+ onClick={onToggleMute}
781
+ className={cn(showBigUnmute && !controlIsPlay && 'hidden')}
782
+ >
783
+ {cardMuted ? <VolumeXmarkIcon /> : <VolumeUpIcon />}
784
+ </Button>
785
+ <Button
786
+ variant="overlay"
787
+ size="icon-sm"
788
+ aria-label={controlIsPlay ? 'Play' : 'Pause'}
789
+ title={controlIsPlay ? 'Play' : 'Pause'}
790
+ onPointerDown={e => e.stopPropagation()}
791
+ onClick={onTogglePlay}
792
+ >
793
+ {controlIsPlay ? <PlayIcon /> : <PauseIcon />}
794
+ </Button>
795
+ </div>
796
+ )}
797
+
798
+ {/* dismiss (highest) */}
799
+ {dismissEnabled && (
800
+ <Button
801
+ variant="overlay"
802
+ size="icon-sm"
803
+ aria-label="Dismiss video"
804
+ onPointerDown={pausePreviewNow}
805
+ onClick={dismiss}
806
+ className="absolute right-[var(--spacing-system-xsf)] top-[var(--spacing-system-xsf)] z-40"
807
+ >
808
+ <XmarkIcon />
809
+ </Button>
810
+ )}
811
+ </div>
812
+ );
813
+
814
+ return (
815
+ <>
816
+ {showCard && (
817
+ <div className={cn('pointer-events-none fixed bottom-0 left-0 p-[var(--spacing-system-mf)]', WALKTHROUGH_Z)} style={{ paddingBottom: 'max(var(--spacing-system-mf), env(safe-area-inset-bottom))' }}>
818
+ {collapsed}
819
+ </div>
820
+ )}
821
+
822
+ <DialogPrimitive.Root open={open} onOpenChange={handleOpenChange}>
823
+ <DialogPortal>
824
+ <DialogOverlay />
825
+ <DialogPrimitive.Content
826
+ onOpenAutoFocus={e => {
827
+ // Prevent Radix's default (first focusable) and move focus to the
828
+ // close button so keyboard/AT users land inside the dialog.
829
+ e.preventDefault();
830
+ closeButtonRef.current?.focus();
831
+ }}
832
+ className={cn(
833
+ // PURE theater: just the video, centered on a dimmed backdrop.
834
+ // No card chrome, no padding, no summary — the 16:9 stage IS the
835
+ // dialog, so nothing competes with the video at any breakpoint.
836
+ // `158svh` is the width at which a 16:9 stage is exactly 89svh
837
+ // tall, so the height bound is expressed as a WIDTH cap — the
838
+ // stage keeps its aspect ratio and simply gets narrower on short
839
+ // viewports, instead of overflowing a fixed centred box that
840
+ // clips equally off the top and bottom (which put the close
841
+ // button off-screen on 1366x768 laptops and landscape phones).
842
+ // Two declarations on purpose: an engine without `svh` (Safari
843
+ // < 15.4) treats the whole min() as invalid, and on a `fixed`
844
+ // element that falls back to width:auto and the stage collapses.
845
+ // The first rule is the safe floor; the second wins where svh
846
+ // parses.
847
+ 'fixed left-1/2 top-1/2 z-[9999] max-w-none -translate-x-1/2 -translate-y-1/2',
848
+ 'w-[min(92vw,1400px)] w-[min(92vw,1400px,158svh)]',
849
+ 'focus:outline-none',
850
+ )}
851
+ >
852
+ <DialogPrimitive.Title className="sr-only">{summaryTitle || 'Walkthrough video'}</DialogPrimitive.Title>
853
+ <Video
854
+ kind={video.youtubeUrl ? 'youtube' : 'file'}
855
+ url={(video.youtubeUrl || video.mainVideoUrl) as string}
856
+ poster={video.posterUrl}
857
+ captionsUrl={video.captionsUrl}
858
+ title={summaryTitle}
859
+ layout="wide"
860
+ startTime={theaterStart.time}
861
+ playerHandleRef={theaterHandleRef}
862
+ // Muted still means PLAYING: without this a deliberately muted
863
+ // card opened a paused theater, breaking parity with the
864
+ // unmuted path.
865
+ autoPlay={theaterStart.muted}
866
+ autoPlayUnmuted={!theaterStart.muted}
867
+ startMuted={theaterStart.muted}
868
+ autoActivate
869
+ suspended={suspended}
870
+ onMutedFallbackChange={st => { theaterForcedMuteRef.current = st.muted; }}
871
+ onEnded={() => { endedLatchRef.current = true; }}
872
+ />
873
+ {/* Radix `asChild` so the shared Button IS the close trigger (same
874
+ pattern as every other dialog close in the lib). */}
875
+ <DialogPrimitive.Close asChild>
876
+ <Button
877
+ ref={closeButtonRef}
878
+ variant="overlay"
879
+ size="icon-sm"
880
+ aria-label="Close"
881
+ className="absolute right-[var(--spacing-system-sf)] top-[var(--spacing-system-sf)] z-10"
882
+ >
883
+ <XmarkIcon />
884
+ </Button>
885
+ </DialogPrimitive.Close>
886
+ </DialogPrimitive.Content>
887
+ </DialogPortal>
888
+ </DialogPrimitive.Root>
889
+ </>
890
+ );
891
+ }