@flamingo-stack/openframe-frontend-core 0.0.703 → 0.0.704-snapshot.20261006133653

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 (336) hide show
  1. package/dist/{chunk-3L7KCOGO.cjs → chunk-2QEUFXNS.cjs} +30 -30
  2. package/dist/{chunk-BMJUKS6S.cjs → chunk-3256UJXS.cjs} +47 -47
  3. package/dist/{chunk-GZYLZ4MB.js → chunk-4S7TJYRQ.js} +3 -3
  4. package/dist/{chunk-ENGBNKUJ.js → chunk-5EFZO5EB.js} +675 -569
  5. package/dist/{chunk-TCZCMD5Q.js → chunk-5HBHO5TB.js} +1 -1
  6. package/dist/{chunk-BOUPNYUV.js → chunk-64HOMVYC.js} +5 -5
  7. package/dist/{chunk-YR6JPJF4.js → chunk-6XIF2Z7I.js} +4 -4
  8. package/dist/{chunk-NU724ZVN.cjs → chunk-7GHB2IK6.cjs} +121 -121
  9. package/dist/{chunk-GX3OUTFO.cjs → chunk-7ZYIC7NA.cjs} +69 -55
  10. package/dist/{chunk-53AC23ZN.js → chunk-A6WVQKOZ.js} +24 -24
  11. package/dist/{chunk-4OGAXAEG.cjs → chunk-ABU3TGXA.cjs} +3 -3
  12. package/dist/{chunk-RTXTGLGB.cjs → chunk-ACV4DFQ3.cjs} +4 -4
  13. package/dist/{chunk-RLDAYA2A.cjs → chunk-BH6PJP66.cjs} +29 -29
  14. package/dist/{chunk-BRXOGCH3.cjs → chunk-BIZNSF3U.cjs} +3 -3
  15. package/dist/{chunk-2A4XES7R.cjs → chunk-BM5MVGH3.cjs} +3 -3
  16. package/dist/{chunk-6YCXA7CN.js → chunk-CH5232HV.js} +20 -19
  17. package/dist/{chunk-UIBSSVON.cjs → chunk-D7BTSVMS.cjs} +2 -2
  18. package/dist/{chunk-YBGEPKC2.cjs → chunk-F7Y2ZS5U.cjs} +6 -6
  19. package/dist/{chunk-5V6O73EH.js → chunk-FLKJYMVB.js} +3 -3
  20. package/dist/{chunk-NMGX2ZTU.js → chunk-IFUQVZF4.js} +17 -17
  21. package/dist/{chunk-2NE3DNXY.js → chunk-IKCQK67U.js} +2 -2
  22. package/dist/{chunk-KFDP3DUL.cjs → chunk-IXPUM3AW.cjs} +98 -98
  23. package/dist/{chunk-QGTDWFT6.js → chunk-IZHZMBIX.js} +2469 -1928
  24. package/dist/{chunk-67CXGPTW.cjs → chunk-JKEEFQOX.cjs} +505 -72
  25. package/dist/{chunk-NYSYSKSH.cjs → chunk-M6HTJTFH.cjs} +15 -15
  26. package/dist/{chunk-OGLCD3UL.js → chunk-MFFCUVWG.js} +6 -6
  27. package/dist/{chunk-KJ5LWATV.js → chunk-MKXO4Q5P.js} +7 -7
  28. package/dist/{chunk-EMCQGFXD.cjs → chunk-MMOODW57.cjs} +28 -27
  29. package/dist/{chunk-Q76ZTHNV.js → chunk-MP2C3LJ4.js} +1 -1
  30. package/dist/{chunk-J4K5JOLI.js → chunk-MV4HYG3B.js} +25 -11
  31. package/dist/{chunk-2T6QWNXU.js → chunk-NDESEPW5.js} +99 -11
  32. package/dist/{chunk-HHFPQNP3.js → chunk-NJDHFC55.js} +15 -15
  33. package/dist/{chunk-HUOLB7ZN.cjs → chunk-OHFS7VOU.cjs} +9 -9
  34. package/dist/{chunk-7632TNE7.js → chunk-OSNIZNVH.js} +7 -7
  35. package/dist/{chunk-UTW4SQDI.js → chunk-OUHMD6EU.js} +10 -10
  36. package/dist/{chunk-MXHJROXJ.cjs → chunk-P23CRLU4.cjs} +1245 -1139
  37. package/dist/{chunk-64O4L7WN.js → chunk-PDWVPI3S.js} +11 -55
  38. package/dist/{chunk-DAW6TDZB.js → chunk-PFC2HWQA.js} +30 -16
  39. package/dist/{chunk-G55W76S6.js → chunk-POZ7F77D.js} +12 -13
  40. package/dist/{chunk-PJP5SMOU.js → chunk-PPEZGUL7.js} +500 -67
  41. package/dist/{chunk-SFGXIFYO.cjs → chunk-QC7Q3XCE.cjs} +24 -24
  42. package/dist/{chunk-MDBHZBW7.cjs → chunk-RKV5B7EU.cjs} +92 -4
  43. package/dist/{chunk-5VPIC5MD.cjs → chunk-SAAZEWUR.cjs} +12 -12
  44. package/dist/{chunk-IIMICN7L.cjs → chunk-TYWLNCNH.cjs} +4 -48
  45. package/dist/{chunk-HYLIQMZH.cjs → chunk-UBP5RNGK.cjs} +33 -33
  46. package/dist/{chunk-HUMWQMCU.js → chunk-UT4YHQZW.js} +3 -3
  47. package/dist/{chunk-BSEIEWM5.cjs → chunk-V3H26CNS.cjs} +28 -14
  48. package/dist/{chunk-NGJCWEYX.cjs → chunk-VHCACDKP.cjs} +46 -46
  49. package/dist/{chunk-SL2FOFT2.js → chunk-VMAZCLX4.js} +7 -7
  50. package/dist/{chunk-5FFNNJM7.js → chunk-VR2ZLFCP.js} +1 -1
  51. package/dist/{chunk-NCTYD7HL.js → chunk-VYWMEL5F.js} +14 -14
  52. package/dist/{chunk-HY5YL3U5.cjs → chunk-XBWE423R.cjs} +1994 -1453
  53. package/dist/{chunk-XCIFHW4E.cjs → chunk-XOYH2YQE.cjs} +2 -2
  54. package/dist/{chunk-S6V7A5FS.cjs → chunk-ZQZFIE2K.cjs} +32 -33
  55. package/dist/components/case-studies/index.cjs +24 -24
  56. package/dist/components/case-studies/index.js +10 -10
  57. package/dist/components/chat/chat-archive-page.d.ts +3 -3
  58. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  59. package/dist/components/chat/chat-input.d.ts.map +1 -1
  60. package/dist/components/chat/chat-nav-sidebar.d.ts +4 -0
  61. package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
  62. package/dist/components/chat/chat-panel-header.d.ts +3 -2
  63. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  64. package/dist/components/chat/embeddable-chat.d.ts +36 -2
  65. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  66. package/dist/components/chat/index.cjs +14 -6
  67. package/dist/components/chat/index.d.ts +4 -1
  68. package/dist/components/chat/index.d.ts.map +1 -1
  69. package/dist/components/chat/index.js +13 -5
  70. package/dist/components/chat/mingo-archive-list.d.ts +17 -0
  71. package/dist/components/chat/mingo-archive-list.d.ts.map +1 -0
  72. package/dist/components/chat/mingo-chat-header.d.ts +31 -0
  73. package/dist/components/chat/mingo-chat-header.d.ts.map +1 -0
  74. package/dist/components/chat/mingo-chat-rail.d.ts +54 -0
  75. package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -0
  76. package/dist/components/chat/model-display.d.ts.map +1 -1
  77. package/dist/components/contact/contact-form.d.ts.map +1 -1
  78. package/dist/components/contact/index.cjs +11 -11
  79. package/dist/components/contact/index.js +10 -10
  80. package/dist/components/content-loading-container.d.ts +1 -1
  81. package/dist/components/content-loading-container.d.ts.map +1 -1
  82. package/dist/components/docs/index.cjs +14 -14
  83. package/dist/components/docs/index.js +13 -13
  84. package/dist/components/docs/skeletons.d.ts.map +1 -1
  85. package/dist/components/embeds/index.cjs +11 -11
  86. package/dist/components/embeds/index.js +10 -10
  87. package/dist/components/faq/index.cjs +12 -12
  88. package/dist/components/faq/index.js +11 -11
  89. package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
  90. package/dist/components/features/index.cjs +10 -10
  91. package/dist/components/features/index.js +9 -9
  92. package/dist/components/features/media-gallery-manager.d.ts.map +1 -1
  93. package/dist/components/features/parallax-image-showcase.d.ts.map +1 -1
  94. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  95. package/dist/components/features/section-selector.d.ts.map +1 -1
  96. package/dist/components/help-center-pages/index.cjs +119 -119
  97. package/dist/components/help-center-pages/index.js +39 -39
  98. package/dist/components/help-center-pages/trust-center-sections.d.ts +2 -2
  99. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  100. package/dist/components/index.cjs +94 -86
  101. package/dist/components/index.js +32 -24
  102. package/dist/components/layout/article-detail-layout.d.ts +1 -1
  103. package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
  104. package/dist/components/layout/page-container.d.ts.map +1 -1
  105. package/dist/components/layout/title-block.d.ts +2 -2
  106. package/dist/components/layout/title-block.d.ts.map +1 -1
  107. package/dist/components/loading/page-layout-skeleton.d.ts.map +1 -1
  108. package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
  109. package/dist/components/meeting-scheduler/booking-form.d.ts +4 -4
  110. package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
  111. package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
  112. package/dist/components/meeting-scheduler/index.cjs +127 -127
  113. package/dist/components/meeting-scheduler/index.d.ts +2 -2
  114. package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
  115. package/dist/components/meeting-scheduler/index.js +50 -50
  116. package/dist/components/navigation/app-header.d.ts +4 -0
  117. package/dist/components/navigation/app-header.d.ts.map +1 -1
  118. package/dist/components/navigation/app-layout-side-panel.d.ts +142 -0
  119. package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -0
  120. package/dist/components/navigation/app-layout.d.ts +11 -1
  121. package/dist/components/navigation/app-layout.d.ts.map +1 -1
  122. package/dist/components/navigation/header-mingo-button.d.ts +4 -1
  123. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  124. package/dist/components/navigation/index.cjs +13 -13
  125. package/dist/components/navigation/index.d.ts +1 -0
  126. package/dist/components/navigation/index.d.ts.map +1 -1
  127. package/dist/components/navigation/index.js +12 -12
  128. package/dist/components/navigation/navigation-sidebar.d.ts +8 -1
  129. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  130. package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
  131. package/dist/components/onboarding-guides/index.cjs +14 -14
  132. package/dist/components/onboarding-guides/index.js +13 -13
  133. package/dist/components/persistent-filter-controls.d.ts.map +1 -1
  134. package/dist/components/related-content/index.cjs +7 -7
  135. package/dist/components/related-content/index.js +6 -6
  136. package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
  137. package/dist/components/tickets/help-center-create-form.d.ts +4 -4
  138. package/dist/components/tickets/index.cjs +15 -15
  139. package/dist/components/tickets/index.js +14 -14
  140. package/dist/components/ui/announcement-bar-view.d.ts.map +1 -1
  141. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  142. package/dist/components/ui/checkbox-block.d.ts.map +1 -1
  143. package/dist/components/ui/dashboard-info-card.d.ts +1 -1
  144. package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
  145. package/dist/components/ui/data-table/data-table-body.d.ts +2 -2
  146. package/dist/components/ui/data-table/data-table-skeleton.d.ts +2 -2
  147. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  148. package/dist/components/ui/data-table/utils.d.ts +2 -2
  149. package/dist/components/ui/dialog.d.ts.map +1 -1
  150. package/dist/components/ui/drawer.d.ts.map +1 -1
  151. package/dist/components/ui/entity-image.d.ts +2 -2
  152. package/dist/components/ui/file-manager/index.cjs +36 -36
  153. package/dist/components/ui/file-manager/index.js +3 -3
  154. package/dist/components/ui/filter-modal.d.ts.map +1 -1
  155. package/dist/components/ui/index.cjs +18 -10
  156. package/dist/components/ui/index.js +17 -9
  157. package/dist/components/ui/info-card-row.d.ts.map +1 -1
  158. package/dist/components/ui/menubar.d.ts +1 -1
  159. package/dist/components/ui/menubar.d.ts.map +1 -1
  160. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  161. package/dist/components/ui/modal.d.ts.map +1 -1
  162. package/dist/components/ui/no-data/no-data-actions.d.ts.map +1 -1
  163. package/dist/components/ui/no-data/no-data-styles.d.ts +1 -1
  164. package/dist/components/ui/no-data/no-data-styles.d.ts.map +1 -1
  165. package/dist/components/ui/page-actions.d.ts.map +1 -1
  166. package/dist/components/ui/sheet.d.ts.map +1 -1
  167. package/dist/components/ui/table/table-skeleton.d.ts +1 -1
  168. package/dist/components/ui/table/table-skeleton.d.ts.map +1 -1
  169. package/dist/hooks/index.cjs +23 -9
  170. package/dist/hooks/index.js +24 -10
  171. package/dist/hooks/ui/index.d.ts +1 -0
  172. package/dist/hooks/ui/index.d.ts.map +1 -1
  173. package/dist/hooks/ui/use-content-breakpoint.d.ts +32 -0
  174. package/dist/hooks/ui/use-content-breakpoint.d.ts.map +1 -0
  175. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts +2 -2
  176. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts.map +1 -1
  177. package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
  178. package/dist/index.cjs +39 -17
  179. package/dist/index.js +41 -19
  180. package/dist/styles/layout-steps.d.ts +30 -0
  181. package/dist/styles/layout-steps.d.ts.map +1 -0
  182. package/dist/tailwind.config.cjs +36 -11
  183. package/dist/tailwind.config.js +36 -11
  184. package/eslint-config/README.md +9 -0
  185. package/eslint-config/base.js +17 -1
  186. package/eslint-config/layout-variants.js +57 -0
  187. package/package.json +3 -1
  188. package/src/ODS_TOKEN_RULES.md +40 -0
  189. package/src/components/chat/chat-archive-page.tsx +9 -7
  190. package/src/components/chat/chat-input.tsx +5 -1
  191. package/src/components/chat/chat-nav-sidebar.tsx +10 -4
  192. package/src/components/chat/chat-panel-header.tsx +8 -5
  193. package/src/components/chat/embeddable-chat.tsx +675 -485
  194. package/src/components/chat/index.ts +4 -0
  195. package/src/components/chat/mingo-archive-list.tsx +108 -0
  196. package/src/components/chat/mingo-chat-header.tsx +104 -0
  197. package/src/components/chat/mingo-chat-rail.tsx +361 -0
  198. package/src/components/chat/model-display.tsx +8 -1
  199. package/src/components/contact/contact-form.tsx +11 -9
  200. package/src/components/content-loading-container.tsx +4 -2
  201. package/src/components/docs/doc-viewer.tsx +7 -7
  202. package/src/components/docs/skeletons.tsx +7 -4
  203. package/src/components/dynamic-skeleton.tsx +1 -1
  204. package/src/components/embeds/claude-embed.tsx +1 -1
  205. package/src/components/embeds/embed-viewer-frame.tsx +4 -4
  206. package/src/components/embeds/figma-embed.tsx +2 -2
  207. package/src/components/embeds/google-sheets-viewer.tsx +1 -1
  208. package/src/components/embeds/pdf-viewer.tsx +3 -3
  209. package/src/components/empty-state.tsx +6 -6
  210. package/src/components/faq/faq-section.tsx +2 -2
  211. package/src/components/faq-accordion.tsx +1 -1
  212. package/src/components/features/board/board-column-header.tsx +1 -1
  213. package/src/components/features/command-box.tsx +3 -3
  214. package/src/components/features/entity-video-section.tsx +2 -2
  215. package/src/components/features/figma-prototype-viewer.tsx +4 -1
  216. package/src/components/features/media-gallery-manager.tsx +3 -1
  217. package/src/components/features/parallax-image-showcase.tsx +9 -6
  218. package/src/components/features/policy-configuration-panel.tsx +9 -7
  219. package/src/components/features/provider-button.tsx +1 -1
  220. package/src/components/features/section-selector.tsx +11 -6
  221. package/src/components/features/select-button.tsx +4 -4
  222. package/src/components/features/seo-editor-preview.tsx +2 -2
  223. package/src/components/features/video-ratio-tabs.tsx +3 -3
  224. package/src/components/features/waitlist-form.tsx +1 -1
  225. package/src/components/help-center-pages/trust-center-page.tsx +1 -1
  226. package/src/components/help-center-pages/trust-center-sections.tsx +14 -14
  227. package/src/components/icon-display.tsx +1 -1
  228. package/src/components/layout/article-detail-layout.tsx +4 -2
  229. package/src/components/layout/list-page-layout.tsx +2 -2
  230. package/src/components/layout/page-container.tsx +26 -14
  231. package/src/components/layout/page-heading.tsx +1 -1
  232. package/src/components/layout/page-layout.tsx +1 -1
  233. package/src/components/layout/title-block.tsx +13 -13
  234. package/src/components/loading/card-skeleton.tsx +11 -11
  235. package/src/components/loading/category-vendor-selector-skeleton.tsx +1 -1
  236. package/src/components/loading/content-skeleton.tsx +6 -6
  237. package/src/components/loading/device-card-skeleton.tsx +1 -1
  238. package/src/components/loading/margin-report-skeleton.tsx +7 -7
  239. package/src/components/loading/msp-profile-form-skeleton.tsx +3 -3
  240. package/src/components/loading/organization-card-skeleton.tsx +2 -2
  241. package/src/components/loading/page-layout-skeleton.tsx +80 -68
  242. package/src/components/loading/unified-skeleton.tsx +16 -6
  243. package/src/components/media-carousel.tsx +3 -3
  244. package/src/components/meeting-scheduler/booking-form.tsx +9 -8
  245. package/src/components/meeting-scheduler/context-panel.tsx +8 -6
  246. package/src/components/meeting-scheduler/directory.tsx +4 -4
  247. package/src/components/meeting-scheduler/index.tsx +15 -15
  248. package/src/components/meeting-scheduler/slot-picker.tsx +12 -12
  249. package/src/components/navigation/app-header.tsx +6 -0
  250. package/src/components/navigation/app-layout-side-panel.tsx +594 -0
  251. package/src/components/navigation/app-layout.tsx +69 -4
  252. package/src/components/navigation/header-mingo-button.tsx +13 -3
  253. package/src/components/navigation/index.ts +5 -0
  254. package/src/components/navigation/navigation-sidebar.tsx +17 -3
  255. package/src/components/navigation/sliding-sidebar.tsx +2 -1
  256. package/src/components/pagination.tsx +3 -3
  257. package/src/components/persistent-filter-controls.tsx +6 -2
  258. package/src/components/platform/ScriptArguments.tsx +1 -1
  259. package/src/components/platform/ScriptInfoSection.tsx +4 -4
  260. package/src/components/platform/SoftwareSourceBadge.tsx +1 -1
  261. package/src/components/shared/article-author-byline.tsx +1 -1
  262. package/src/components/shared/delivery/delivery-lists.tsx +2 -2
  263. package/src/components/shared/delivery/delivery-row.tsx +7 -7
  264. package/src/components/shared/delivery/delivery-table.tsx +3 -3
  265. package/src/components/shared/detail-page-skeleton.tsx +11 -5
  266. package/src/components/shared/dev-section/dev-card-row.tsx +6 -6
  267. package/src/components/shared/dev-section/dev-section-view.tsx +1 -1
  268. package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -8
  269. package/src/components/shared/product-release/product-release-card.tsx +9 -9
  270. package/src/components/shared/product-release/product-releases-view.tsx +1 -1
  271. package/src/components/shared/product-release/release-detail-page.tsx +5 -5
  272. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +1 -1
  273. package/src/components/shared/roadmap/roadmap-grid.tsx +3 -3
  274. package/src/components/tickets/help-center-card.tsx +1 -1
  275. package/src/components/tickets/help-center-create-form.tsx +12 -12
  276. package/src/components/ui/announcement-bar-view.tsx +2 -10
  277. package/src/components/ui/benefit-card.tsx +18 -18
  278. package/src/components/ui/brand-association-grid.tsx +4 -4
  279. package/src/components/ui/breadcrumb.tsx +4 -1
  280. package/src/components/ui/card.tsx +1 -1
  281. package/src/components/ui/checkbox-block.tsx +9 -7
  282. package/src/components/ui/content-loader.tsx +3 -3
  283. package/src/components/ui/cursor-pagination.tsx +3 -3
  284. package/src/components/ui/dashboard-info-card.tsx +13 -9
  285. package/src/components/ui/data-attribution.tsx +2 -2
  286. package/src/components/ui/data-table/data-table-body.tsx +2 -2
  287. package/src/components/ui/data-table/data-table-header.tsx +15 -15
  288. package/src/components/ui/data-table/data-table-skeleton.tsx +6 -6
  289. package/src/components/ui/data-table/utils.ts +5 -5
  290. package/src/components/ui/dialog.tsx +4 -1
  291. package/src/components/ui/drawer.tsx +2 -1
  292. package/src/components/ui/duration-input.tsx +1 -1
  293. package/src/components/ui/entity-image.tsx +3 -3
  294. package/src/components/ui/feature-card.tsx +10 -10
  295. package/src/components/ui/file-manager/file-manager-action-bar.tsx +1 -1
  296. package/src/components/ui/file-manager/file-manager.tsx +1 -1
  297. package/src/components/ui/filter-modal.tsx +8 -1
  298. package/src/components/ui/highlight-card.tsx +7 -7
  299. package/src/components/ui/info-card-row.tsx +10 -6
  300. package/src/components/ui/modal-v2.tsx +7 -2
  301. package/src/components/ui/modal.tsx +3 -2
  302. package/src/components/ui/no-data/no-data-actions.tsx +10 -4
  303. package/src/components/ui/no-data/no-data-styles.ts +1 -1
  304. package/src/components/ui/no-data/no-data.tsx +1 -1
  305. package/src/components/ui/organization-card.tsx +1 -1
  306. package/src/components/ui/page-actions.tsx +34 -5
  307. package/src/components/ui/progress-bar.tsx +2 -2
  308. package/src/components/ui/query-report-table/query-report-table.tsx +1 -1
  309. package/src/components/ui/search-input.tsx +2 -2
  310. package/src/components/ui/service-card.tsx +2 -2
  311. package/src/components/ui/sheet.tsx +2 -1
  312. package/src/components/ui/skeleton.tsx +3 -3
  313. package/src/components/ui/stacked-rows-panel.tsx +4 -4
  314. package/src/components/ui/tab-navigation.tsx +2 -2
  315. package/src/components/ui/tab-selector.tsx +2 -2
  316. package/src/components/ui/table/table-header.tsx +3 -3
  317. package/src/components/ui/table/table-skeleton.tsx +3 -3
  318. package/src/components/ui/table/utils.ts +5 -5
  319. package/src/components/ui/tag-search-input.tsx +3 -3
  320. package/src/components/ui/tags-manager.tsx +5 -5
  321. package/src/components/ui/test-run.tsx +2 -2
  322. package/src/components/ui/ticket-info-section.tsx +1 -1
  323. package/src/components/ui/ticket-notes-section.tsx +1 -1
  324. package/src/components/ui/ticket-status-config-row.tsx +15 -15
  325. package/src/hooks/ui/index.ts +1 -0
  326. package/src/hooks/ui/use-content-breakpoint.ts +58 -0
  327. package/src/hooks/ui/use-media-query.ts +3 -2
  328. package/src/styles/app-globals.css +13 -0
  329. package/src/styles/index.css +2 -0
  330. package/src/styles/layout-steps.ts +14 -0
  331. package/src/styles/ods-colors.css +15 -0
  332. package/src/styles/ods-content-area-tokens.css +27 -0
  333. package/src/styles/ods-content-area.css +31 -0
  334. package/src/styles/ods-effects.css +4 -2
  335. package/src/styles/ods-responsive-tokens.css +14 -4
  336. package/tailwind.config.ts +42 -7
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import type { ReactNode } from 'react';
5
+ import { ContentAreaWidthContext } from '../../hooks/ui/use-content-breakpoint';
6
+ import { useIsomorphicLayoutEffect } from '../../hooks/ui/use-isomorphic-layout-effect';
5
7
  import type { NavigationSidebarConfig } from '../../types/navigation';
6
8
  import { cn } from '../../utils';
7
9
  import { NotificationDrawer } from '../features/notifications/notification-drawer';
@@ -12,6 +14,7 @@ import {
12
14
  AppLayoutDrawerCoordinationContext,
13
15
  type AppLayoutDrawerHandle,
14
16
  } from './app-layout-context';
17
+ import { AppLayoutSidePanel, type AppLayoutSidePanelConfig, useAppLayoutSidePanel } from './app-layout-side-panel';
15
18
  import { MobileBurgerMenu, type MobileBurgerMenuProps } from './mobile-burger-menu';
16
19
  import { NavigationSidebar } from './navigation-sidebar';
17
20
 
@@ -46,6 +49,15 @@ export interface AppLayoutProps {
46
49
  * area occupy the remaining height below it.
47
50
  */
48
51
  topBar?: ReactNode;
52
+ /**
53
+ * A panel docked beside the content (the Mingo chat). It narrows the
54
+ * content instead of covering it, and the content then lays out by its own
55
+ * width: `<main>` becomes an `ods-content-area` whose tokens and
56
+ * `content-md:` / `content-lg:` classes follow the room the panel leaves.
57
+ * Without room to dock (or on a phone) the header's Mingo button opens it
58
+ * over the content area. Optional; without it the layout is unchanged.
59
+ */
60
+ sidePanel?: AppLayoutSidePanelConfig;
49
61
  }
50
62
 
51
63
  export function AppLayout({
@@ -58,9 +70,38 @@ export function AppLayout({
58
70
  disabled = false,
59
71
  drawer,
60
72
  topBar,
73
+ sidePanel,
61
74
  }: AppLayoutProps) {
62
75
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
63
76
  const [drawerContainer, setDrawerContainer] = useState<HTMLDivElement | null>(null);
77
+ const [mainElement, setMainElement] = useState<HTMLElement | null>(null);
78
+ const [contentWidth, setContentWidth] = useState<number | null>(null);
79
+
80
+ const hasSidePanel = sidePanel !== undefined;
81
+ const sidePanelState = useAppLayoutSidePanel(sidePanel, drawerContainer);
82
+
83
+ useIsomorphicLayoutEffect(() => {
84
+ if (!hasSidePanel || !mainElement) return undefined;
85
+ const update = () => setContentWidth(mainElement.clientWidth);
86
+ update();
87
+ const observer = new ResizeObserver(update);
88
+ observer.observe(mainElement);
89
+ return () => observer.disconnect();
90
+ }, [hasSidePanel, mainElement]);
91
+
92
+ // With a side panel the header's Mingo button only exists while the panel
93
+ // cannot dock: it opens the panel over the content and closes it again.
94
+ const resolvedHeaderProps =
95
+ sidePanelState === null
96
+ ? headerProps
97
+ : {
98
+ ...headerProps,
99
+ showMingoAI: !sidePanelState.canDock,
100
+ onMingoAI: sidePanelState.toggle,
101
+ isMingoAIActive: sidePanelState.isOpen,
102
+ mingoAICloseWhenActive: true,
103
+ };
104
+ const contentCovered = sidePanelState?.coversContent === true;
64
105
 
65
106
  // Mirrors `mobileMenuOpen` so the toggle callback can stay identity-stable.
66
107
  // Refreshed after every commit rather than in the render body: the only
@@ -114,7 +155,7 @@ export function AppLayout({
114
155
  `relative` so the tablet sidebar (position:absolute) anchors to this
115
156
  row — below the topBar — instead of the viewport. */}
116
157
  <div className="relative flex min-h-0 flex-1">
117
- <NavigationSidebar config={sidebarConfig} disabled={disabled} />
158
+ <NavigationSidebar config={sidebarConfig} disabled={disabled} pushOnTablet={hasSidePanel} />
118
159
  {/* Mobile Burger Menu - opens below header */}
119
160
  <MobileBurgerMenu
120
161
  {...mobileBurgerMenuProps}
@@ -127,7 +168,7 @@ export function AppLayout({
127
168
  {/* Main Content Area */}
128
169
  <div className="flex flex-1 flex-col overflow-hidden">
129
170
  <AppHeader
130
- {...headerProps}
171
+ {...resolvedHeaderProps}
131
172
  isMobileMenuOpen={mobileMenuOpen}
132
173
  onToggleMobileMenu={handleToggleMobileMenu}
133
174
  disabled={disabled}
@@ -140,8 +181,32 @@ export function AppLayout({
140
181
  animation visually AND contains layout overflow so it doesn't
141
182
  propagate up to <html>. (Scroll-snap-back below handles the
142
183
  browser's programmatic scroll-on-focus side effect.) */}
143
- <div ref={setDrawerContainer} className="relative flex flex-1 flex-col overflow-hidden">
144
- <main className={cn('flex-1 overflow-y-auto', mainClassName)}>{children}</main>
184
+ <div
185
+ ref={setDrawerContainer}
186
+ className={cn('relative flex flex-1 overflow-hidden', sidePanel ? 'flex-row' : 'flex-col')}
187
+ >
188
+ {sidePanel ? (
189
+ // `hidden` while the panel covers the area: the page stays
190
+ // mounted, it just takes no room.
191
+ <main
192
+ ref={setMainElement}
193
+ className={cn(
194
+ 'ods-content-area min-w-0 flex-1 overflow-y-auto',
195
+ contentCovered && 'hidden',
196
+ // While the panel is drawn over the page, keep the page's own
197
+ // z-indexed layers (sticky headers, fixed bars) under it.
198
+ sidePanelState?.overlapsContent && 'isolate',
199
+ mainClassName,
200
+ )}
201
+ >
202
+ <ContentAreaWidthContext.Provider value={contentWidth}>
203
+ <div className="ods-content-scope">{children}</div>
204
+ </ContentAreaWidthContext.Provider>
205
+ </main>
206
+ ) : (
207
+ <main className={cn('flex-1 overflow-y-auto', mainClassName)}>{children}</main>
208
+ )}
209
+ {sidePanel && sidePanelState && <AppLayoutSidePanel config={sidePanel} state={sidePanelState} />}
145
210
  {/* `drawer` slot — rendered here so it sits inside the
146
211
  AppLayoutDrawerContainerContext and can portal into this exact
147
212
  container. Mount location is irrelevant for visual placement
@@ -12,6 +12,9 @@ export interface HeaderMingoButtonProps extends React.ButtonHTMLAttributes<HTMLB
12
12
  * control collapses to a square `HeaderButton`-sized affordance on narrow
13
13
  * viewports). Defaults to `false`. */
14
14
  iconOnly?: boolean;
15
+ /** Show the close "X" while active with the wordmark too, not only in
16
+ * `iconOnly` mode. Defaults to `false`. */
17
+ closeWhenActive?: boolean;
15
18
  className?: string;
16
19
  }
17
20
 
@@ -25,11 +28,18 @@ export interface HeaderMingoButtonProps extends React.ButtonHTMLAttributes<HTMLB
25
28
  *
26
29
  * Figma: 7532:222103 — `button-full`.
27
30
  */
28
- export function HeaderMingoButton({ isActive = false, iconOnly = false, className, ...props }: HeaderMingoButtonProps) {
31
+ export function HeaderMingoButton({
32
+ isActive = false,
33
+ iconOnly = false,
34
+ closeWhenActive = false,
35
+ className,
36
+ ...props
37
+ }: HeaderMingoButtonProps) {
38
+ const showClose = isActive && (iconOnly || closeWhenActive);
29
39
  return (
30
40
  <button
31
41
  type="button"
32
- aria-label={iconOnly && isActive ? 'Close Mingo AI' : 'Mingo AI'}
42
+ aria-label={showClose ? 'Close Mingo AI' : 'Mingo AI'}
33
43
  aria-pressed={isActive}
34
44
  className={cn(
35
45
  'flex h-full shrink-0 items-center gap-2 px-4',
@@ -47,7 +57,7 @@ export function HeaderMingoButton({ isActive = false, iconOnly = false, classNam
47
57
  )}
48
58
  {...props}
49
59
  >
50
- {iconOnly && isActive ? (
60
+ {showClose ? (
51
61
  // Mobile, drawer open: the icon-only button doubles as the close
52
62
  // affordance, so swap the Mingo logo for an X. Match the other header
53
63
  // icons (e.g. notifications): secondary color, w-4 h-4 → md:w-6 h-6.
@@ -30,6 +30,11 @@ export type { AppHeaderProps, HeaderLoadingCell } from './app-header';
30
30
 
31
31
  export { AppLayout, useAppLayoutDrawerContainer } from './app-layout';
32
32
  export type { AppLayoutProps } from './app-layout';
33
+ export type {
34
+ AppLayoutSidePanelConfig,
35
+ AppLayoutSidePanelMode,
36
+ AppLayoutSidePanelRenderState,
37
+ } from './app-layout-side-panel';
33
38
 
34
39
  export {
35
40
  AppLayoutDrawer,
@@ -123,9 +123,16 @@ export interface NavigationSidebarProps {
123
123
  * The collapse/expand toggle button remains interactive.
124
124
  */
125
125
  disabled?: boolean;
126
+ /**
127
+ * On tablet, open the expanded sidebar beside the content instead of over
128
+ * it: the slot it holds widens with it and there is no scrim. A layout with
129
+ * a docked side panel needs the content's real width to change. Default
130
+ * false (overlay).
131
+ */
132
+ pushOnTablet?: boolean;
126
133
  }
127
134
 
128
- export function NavigationSidebar({ config, disabled = false }: NavigationSidebarProps) {
135
+ export function NavigationSidebar({ config, disabled = false, pushOnTablet = false }: NavigationSidebarProps) {
129
136
  const isMdUp = useMdUp() ?? false;
130
137
  const isLgUp = useLgUp() ?? false;
131
138
 
@@ -320,7 +327,7 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
320
327
  'fixed inset-0 z-[40] bg-ods-overlay',
321
328
  'hidden md:block lg:hidden',
322
329
  'transition-opacity duration-300',
323
- isOverlayOpen ? 'opacity-100' : 'pointer-events-none opacity-0',
330
+ isOverlayOpen && !pushOnTablet ? 'opacity-100' : 'pointer-events-none opacity-0',
324
331
  )}
325
332
  onClick={closeOverlay}
326
333
  aria-hidden="true"
@@ -332,7 +339,14 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
332
339
  geometry is: gated on `isTablet`, it was missing from the server HTML
333
340
  and from the first client paint — exactly when the sidebar it
334
341
  compensates for has already gone `absolute`. */}
335
- <div className="hidden h-full w-14 flex-shrink-0 md:block lg:hidden" aria-hidden="true" />
342
+ <div
343
+ className={cn(
344
+ 'hidden h-full flex-shrink-0 md:block lg:hidden',
345
+ isOverlayOpen && pushOnTablet ? 'w-56' : 'w-14',
346
+ transitionsEnabled && 'transition-[width] duration-300',
347
+ )}
348
+ aria-hidden="true"
349
+ />
336
350
 
337
351
  <aside
338
352
  className={cn(
@@ -212,7 +212,8 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
212
212
  role={config.isOpen ? 'dialog' : undefined}
213
213
  aria-label={config.isOpen ? 'Navigation sidebar' : undefined}
214
214
  className={cn(
215
- 'fixed bottom-0 top-0 z-[46] flex w-72 flex-col border-ods-border bg-ods-card shadow-xl',
215
+ // Window chrome: the window's tokens even when opened from a narrow content area.
216
+ 'ods-viewport-layer fixed bottom-0 top-0 z-[46] flex w-72 flex-col border-ods-border bg-ods-card shadow-xl',
216
217
  config.position === 'right' ? 'right-0 border-l' : 'left-0 border-r',
217
218
  config.className,
218
219
  )}
@@ -74,7 +74,7 @@ const Pagination = ({ currentPage, totalPages, onPageChange, className }: Pagina
74
74
  onClick={() => onPageChange(currentPage - 1)}
75
75
  disabled={currentPage === 1}
76
76
  aria-label="Go to previous page"
77
- className="!h-10 !w-10 md:!h-10 md:!w-10"
77
+ className="!h-10 !w-10 content-md:!h-10 content-md:!w-10"
78
78
  >
79
79
  <ChevronLeft className="h-4 w-4" />
80
80
  </Button>
@@ -99,7 +99,7 @@ const Pagination = ({ currentPage, totalPages, onPageChange, className }: Pagina
99
99
  variant={currentPage === page ? 'accent' : 'outline'}
100
100
  size="icon"
101
101
  onClick={() => onPageChange(page)}
102
- className="!h-9 !w-9 md:!h-9 md:!w-9"
102
+ className="!h-9 !w-9 content-md:!h-9 content-md:!w-9"
103
103
  aria-label={`Page ${page}`}
104
104
  aria-current={currentPage === page ? 'page' : undefined}
105
105
  >
@@ -116,7 +116,7 @@ const Pagination = ({ currentPage, totalPages, onPageChange, className }: Pagina
116
116
  onClick={() => onPageChange(currentPage + 1)}
117
117
  disabled={currentPage === totalPages}
118
118
  aria-label="Go to next page"
119
- className="!h-10 !w-10 md:!h-10 md:!w-10"
119
+ className="!h-10 !w-10 content-md:!h-10 content-md:!w-10"
120
120
  >
121
121
  <ChevronRight className="h-4 w-4" />
122
122
  <span className="sr-only">Next page</span>
@@ -138,7 +138,7 @@ export function PersistentSidebar({ isLoading, children, className }: Persistent
138
138
  <PersistentFilterControls
139
139
  isLoading={isLoading}
140
140
  disabledOpacity={0.5}
141
- className={cn('lg:sticky lg:top-20', className)}
141
+ className={cn('content-lg:sticky content-lg:top-20', className)}
142
142
  >
143
143
  {children}
144
144
  </PersistentFilterControls>
@@ -156,7 +156,11 @@ interface PersistentMobileDropdownProps {
156
156
 
157
157
  export function PersistentMobileDropdown({ isLoading, children, className }: PersistentMobileDropdownProps) {
158
158
  return (
159
- <PersistentFilterControls isLoading={isLoading} disabledOpacity={0.7} className={cn('lg:hidden', className)}>
159
+ <PersistentFilterControls
160
+ isLoading={isLoading}
161
+ disabledOpacity={0.7}
162
+ className={cn('content-lg:hidden', className)}
163
+ >
160
164
  {children}
161
165
  </PersistentFilterControls>
162
166
  );
@@ -181,7 +181,7 @@ export const ScriptArguments: React.FC<ScriptArgumentsProps> = ({
181
181
  onClick={() => handleDelete(arg.id)}
182
182
  disabled={disabled}
183
183
  aria-label="Delete argument"
184
- leftIcon={<Trash2 className="size-4 md:size-6" color="var(--color-error)" />}
184
+ leftIcon={<Trash2 className="size-4 content-md:size-6" color="var(--color-error)" />}
185
185
  />
186
186
  </div>
187
187
  </div>
@@ -136,15 +136,15 @@ export const ScriptInfoSection: React.FC<ScriptInfoSectionProps> = ({
136
136
  - Desktop (lg+): 4 columns in single row
137
137
  */}
138
138
  {/* First row: Shell Type, Supported Platforms */}
139
- <div className="grid grid-cols-2 gap-4 border-b border-ods-border px-4 py-4 lg:grid-cols-4 lg:border-b-0">
139
+ <div className="grid grid-cols-2 gap-4 border-b border-ods-border px-4 py-4 content-lg:grid-cols-4 content-lg:border-b-0">
140
140
  <InfoCell label="Shell Type" value={shellLabel} />
141
141
  <InfoCell label="Supported Platforms" value={platformsLabel} />
142
142
  {/* Desktop only: Category and Author in same row */}
143
- <InfoCell label="Category" value={category} className="hidden lg:flex" />
144
- {author && <InfoCell label="Added by" value={author.name} avatar={author} className="hidden lg:flex" />}
143
+ <InfoCell label="Category" value={category} className="hidden content-lg:flex" />
144
+ {author && <InfoCell label="Added by" value={author.name} avatar={author} className="hidden content-lg:flex" />}
145
145
  </div>
146
146
  {/* Second row (mobile/tablet only): Category, Author */}
147
- <div className="grid grid-cols-2 gap-4 px-4 py-4 lg:hidden">
147
+ <div className="grid grid-cols-2 gap-4 px-4 py-4 content-lg:hidden">
148
148
  <InfoCell label="Category" value={category} />
149
149
  {author && <InfoCell label="Added by" value={author.name} avatar={author} />}
150
150
  </div>
@@ -33,7 +33,7 @@
33
33
  */
34
34
 
35
35
  import type React from 'react';
36
- import { cn } from '../../utils/common';
36
+ import { cn } from '../../utils/cn';
37
37
  import { PackageIcon, WindowsIcon, MacOSIcon, LinuxIcon } from '../icons';
38
38
 
39
39
  export type SoftwareSource =
@@ -111,7 +111,7 @@ export function ArticleAuthorByline({
111
111
  <div
112
112
  className={cn(
113
113
  'rounded-lg border border-ods-border bg-ods-card p-6',
114
- 'flex flex-col items-start gap-4 md:flex-row',
114
+ 'flex flex-col items-start gap-4 content-md:flex-row',
115
115
  className,
116
116
  )}
117
117
  >
@@ -202,7 +202,7 @@ export function DeliveryLists({
202
202
  {/* Completed Tasks Table */}
203
203
  {showCompleted && (hasResults || isLoading) && (
204
204
  <div className="w-full">
205
- <h3 className="mb-4 tracking-[-0.48px] text-ods-text-primary text-h2 md:tracking-[-0.56px] lg:tracking-[-0.64px]">
205
+ <h3 className="mb-4 tracking-[-0.48px] text-ods-text-primary text-h2 content-md:tracking-[-0.56px] content-lg:tracking-[-0.64px]">
206
206
  Recently Completed<span className="text-ods-accent">:</span>
207
207
  </h3>
208
208
  <DeliveryTable items={filteredCompleted} isLoading={isLoading} />
@@ -212,7 +212,7 @@ export function DeliveryLists({
212
212
  {/* In Progress Tasks Table */}
213
213
  {showInProgress && (hasResults || isLoading) && (
214
214
  <div className="w-full">
215
- <h3 className="mb-4 tracking-[-0.48px] text-ods-text-primary text-h2 md:tracking-[-0.56px] lg:tracking-[-0.64px]">
215
+ <h3 className="mb-4 tracking-[-0.48px] text-ods-text-primary text-h2 content-md:tracking-[-0.56px] content-lg:tracking-[-0.64px]">
216
216
  Active Tasks<span className="text-ods-accent">:</span>
217
217
  </h3>
218
218
  <DeliveryTable items={filteredInProgress} isLoading={isLoading} />
@@ -82,12 +82,12 @@ export function DeliveryRow({ item, href, target, caption, id, className }: Deli
82
82
  const subtitle = `ACTIVE ${relativeTime}${item.listNames.length > 0 ? `, ${item.listNames.join(', ')}` : ''}, ${item.id}`;
83
83
 
84
84
  const inner = (
85
- <div className="flex w-full flex-col items-start justify-between gap-[12px] md:flex-row md:gap-[16px]">
85
+ <div className="flex w-full flex-col items-start justify-between gap-[12px] content-md:flex-row content-md:gap-[16px]">
86
86
  {/* Left: caption (optional) + title + subtitle + description */}
87
- <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] md:w-auto md:gap-[16px]">
87
+ <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] content-md:w-auto content-md:gap-[16px]">
88
88
  {caption && <p className="text-ods-text-secondary text-h5">{caption}</p>}
89
- <div className="flex min-h-[24px] items-center md:min-h-[24px]">
90
- <h3 className="line-clamp-2 flex-1 break-words tracking-[-0.36px] text-ods-text-primary text-h3 md:truncate">
89
+ <div className="flex min-h-[24px] items-center content-md:min-h-[24px]">
90
+ <h3 className="line-clamp-2 flex-1 break-words tracking-[-0.36px] text-ods-text-primary text-h3 content-md:truncate">
91
91
  {item.title}
92
92
  </h3>
93
93
  </div>
@@ -112,7 +112,7 @@ export function DeliveryRow({ item, href, target, caption, id, className }: Deli
112
112
  assignee owns the trailing slot of the meta row; a column of
113
113
  right-aligned orphans under the description reads as clutter
114
114
  on small screens). */}
115
- <div className="flex w-full flex-shrink-0 flex-row items-center gap-2 self-stretch md:w-auto md:flex-col md:items-end md:self-start">
115
+ <div className="flex w-full flex-shrink-0 flex-row items-center gap-2 self-stretch content-md:w-auto content-md:flex-col content-md:items-end content-md:self-start">
116
116
  <StatusBadge
117
117
  text={item.status.toUpperCase()}
118
118
  colorScheme={statusBadgeScheme}
@@ -127,7 +127,7 @@ export function DeliveryRow({ item, href, target, caption, id, className }: Deli
127
127
  {item.assignees && item.assignees.length > 0 ? (
128
128
  <AvatarStack
129
129
  size="xs"
130
- className="ml-auto md:ml-0"
130
+ className="ml-auto content-md:ml-0"
131
131
  people={item.assignees.map(a => ({
132
132
  key: a.id,
133
133
  name: a.name ?? 'Unknown',
@@ -140,7 +140,7 @@ export function DeliveryRow({ item, href, target, caption, id, className }: Deli
140
140
  );
141
141
 
142
142
  const baseClass = cn(
143
- 'block p-[12px] text-inherit no-underline transition-colors duration-150 md:p-[16px]',
143
+ 'block p-[12px] text-inherit no-underline transition-colors duration-150 content-md:p-[16px]',
144
144
  // `scroll-mt-24` is paid for whether `id` is set or not (it's a
145
145
  // single Tailwind utility, no runtime cost). Keeping it
146
146
  // unconditional means a future caller adding `id` doesn't also
@@ -32,10 +32,10 @@ interface DeliveryTableProps {
32
32
  */
33
33
  function SkeletonRow() {
34
34
  return (
35
- <div className="border-b border-ods-border p-[12px] last:border-b-0 md:p-[16px]">
36
- <div className="flex w-full flex-col items-start justify-between gap-[12px] md:flex-row md:gap-[16px]">
35
+ <div className="border-b border-ods-border p-[12px] last:border-b-0 content-md:p-[16px]">
36
+ <div className="flex w-full flex-col items-start justify-between gap-[12px] content-md:flex-row content-md:gap-[16px]">
37
37
  {/* Left: Title, subtitle, and description skeleton */}
38
- <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] md:w-auto md:gap-[16px]">
38
+ <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] content-md:w-auto content-md:gap-[16px]">
39
39
  {/* Title skeleton - responsive */}
40
40
  <div className="flex min-h-[24px] items-center">
41
41
  <div className="h-[20px] w-full animate-pulse rounded bg-ods-border"></div>
@@ -18,10 +18,10 @@ export function DetailPageSkeleton({
18
18
  bare = false,
19
19
  }: DetailPageSkeletonProps = {}) {
20
20
  const content = (
21
- <div className="animate-pulse space-y-6 md:space-y-10">
21
+ <div className="animate-pulse space-y-6 content-md:space-y-10">
22
22
  {/* Title Block */}
23
23
  <div className="flex w-full flex-col gap-6">
24
- <div className="h-16 w-full max-w-3xl rounded bg-ods-card md:h-20"></div>
24
+ <div className="h-16 w-full max-w-3xl rounded bg-ods-card content-md:h-20"></div>
25
25
  </div>
26
26
 
27
27
  {/* Category Tags Skeleton */}
@@ -33,10 +33,13 @@ export function DetailPageSkeleton({
33
33
 
34
34
  {/* Metadata Grid Skeleton */}
35
35
  <div
36
- className={`grid grid-cols-1 md:grid-cols-${metadataColumns} w-full overflow-hidden rounded-md border border-ods-border`}
36
+ className={`grid grid-cols-1 content-md:grid-cols-${metadataColumns} w-full overflow-hidden rounded-md border border-ods-border`}
37
37
  >
38
38
  {Array.from({ length: metadataColumns }).map((_, i) => (
39
- <div key={i} className="border-b border-ods-border bg-ods-card p-4 last:border-r-0 md:border-b-0 md:border-r">
39
+ <div
40
+ key={i}
41
+ className="border-b border-ods-border bg-ods-card p-4 last:border-r-0 content-md:border-b-0 content-md:border-r"
42
+ >
40
43
  <div className="mb-2 h-6 w-24 rounded bg-ods-border"></div>
41
44
  <div className="h-5 w-20 rounded bg-ods-border"></div>
42
45
  </div>
@@ -70,7 +73,10 @@ export function DetailPageSkeleton({
70
73
  );
71
74
  if (bare) return content;
72
75
  return (
73
- <PageLayout showHeader={false} className="mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 md:px-20 md:py-10">
76
+ <PageLayout
77
+ showHeader={false}
78
+ className="mx-auto max-w-[1280px] bg-ods-bg px-6 py-6 content-md:px-20 content-md:py-10"
79
+ >
74
80
  {content}
75
81
  </PageLayout>
76
82
  );
@@ -48,10 +48,10 @@ export function DevCardRowContent({
48
48
  rightBadges,
49
49
  }: DevCardRowContentProps) {
50
50
  return (
51
- <div className="flex w-full flex-col items-start justify-between gap-[12px] md:flex-row md:gap-[16px]">
52
- <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] md:w-auto md:gap-[16px]">
51
+ <div className="flex w-full flex-col items-start justify-between gap-[12px] content-md:flex-row content-md:gap-[16px]">
52
+ <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] content-md:w-auto content-md:gap-[16px]">
53
53
  <div className="flex min-h-[24px] items-center">
54
- <h3 className="line-clamp-2 flex-1 break-words tracking-[-0.36px] text-ods-text-primary text-h3 md:truncate">
54
+ <h3 className="line-clamp-2 flex-1 break-words tracking-[-0.36px] text-ods-text-primary text-h3 content-md:truncate">
55
55
  {title}
56
56
  </h3>
57
57
  </div>
@@ -74,9 +74,9 @@ export function DevCardRowContent({
74
74
  */
75
75
  export function DevCardRowSkeleton() {
76
76
  return (
77
- <div className="border-b border-ods-border p-[12px] last:border-b-0 md:p-[16px]">
78
- <div className="flex w-full flex-col items-start justify-between gap-[12px] md:flex-row md:gap-[16px]">
79
- <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] md:w-auto md:gap-[16px]">
77
+ <div className="border-b border-ods-border p-[12px] last:border-b-0 content-md:p-[16px]">
78
+ <div className="flex w-full flex-col items-start justify-between gap-[12px] content-md:flex-row content-md:gap-[16px]">
79
+ <div className="flex w-full min-w-0 flex-1 flex-col gap-[12px] content-md:w-auto content-md:gap-[16px]">
80
80
  <div className="flex min-h-[24px] items-center">
81
81
  <div className="h-[20px] w-full animate-pulse rounded bg-ods-border" />
82
82
  </div>
@@ -136,7 +136,7 @@ export function DevSectionView({ sectionKey, hero, preControls, children, showHe
136
136
  </div>
137
137
  ) : (
138
138
  <div className="flex w-full items-center justify-between">
139
- <h2 className="font-['Azeret_Mono'] text-[32px] font-semibold leading-[40px] tracking-[-0.64px] text-ods-text-primary md:text-[40px] md:leading-[48px] md:tracking-[-0.8px] lg:text-[48px] lg:leading-[56px] lg:tracking-[-0.96px]">
139
+ <h2 className="font-['Azeret_Mono'] text-[32px] font-semibold leading-[40px] tracking-[-0.64px] text-ods-text-primary content-md:text-[40px] content-md:leading-[48px] content-md:tracking-[-0.8px] content-lg:text-[48px] content-lg:leading-[56px] content-lg:tracking-[-0.96px]">
140
140
  {section.hero.title}
141
141
  <span className="text-ods-accent">:</span>
142
142
  </h2>
@@ -31,8 +31,8 @@ export function ProductReleaseCardSkeleton({ className, size = 'lg' }: ProductRe
31
31
  between skeleton state and loaded state. Without this,
32
32
  individual placeholder heights underrun the loaded card's
33
33
  min-h reservations and the page jumps on resolve. */}
34
- <div className="flex flex-col gap-4 md:flex-row md:gap-6">
35
- <div className="aspect-[1200/630] w-full flex-shrink-0 rounded-lg bg-ods-border md:w-[256px]" />
34
+ <div className="flex flex-col gap-4 content-md:flex-row content-md:gap-6">
35
+ <div className="aspect-[1200/630] w-full flex-shrink-0 rounded-lg bg-ods-border content-md:w-[256px]" />
36
36
  <div className="flex min-w-0 flex-1 flex-col">
37
37
  {/* Version pill — mirrors `flex items-center gap-3 mb-3` in
38
38
  the loaded card. The loaded `<span text-lg>` renders at
@@ -43,16 +43,16 @@ export function ProductReleaseCardSkeleton({ className, size = 'lg' }: ProductRe
43
43
  </div>
44
44
  {/* Title container — SAME min-h as the loaded card so the
45
45
  card height contributed by this region matches exactly. */}
46
- <div className="mb-3 flex min-h-[60px] flex-col justify-start gap-1.5 md:min-h-[72px]">
47
- <div className="h-[25px] w-3/4 rounded bg-ods-border md:h-[30px]" />
48
- <div className="h-[25px] w-1/2 rounded bg-ods-border md:h-[30px]" />
46
+ <div className="mb-3 flex min-h-[60px] flex-col justify-start gap-1.5 content-md:min-h-[72px]">
47
+ <div className="h-[25px] w-3/4 rounded bg-ods-border content-md:h-[30px]" />
48
+ <div className="h-[25px] w-1/2 rounded bg-ods-border content-md:h-[30px]" />
49
49
  </div>
50
50
  {/* Summary container — SAME min-h as the loaded card. The
51
51
  3 placeholder lines mirror the rendered 3-line clamp;
52
52
  `bg-ods-border/70` keeps summary placeholders slightly
53
53
  dimmer than title placeholders (primary vs secondary
54
54
  text hierarchy). */}
55
- <div className="flex min-h-[68px] flex-col justify-start gap-2 md:min-h-[78px]">
55
+ <div className="flex min-h-[68px] flex-col justify-start gap-2 content-md:min-h-[78px]">
56
56
  <div className="h-3 w-full rounded bg-ods-border/70" />
57
57
  <div className="h-3 w-11/12 rounded bg-ods-border/70" />
58
58
  <div className="h-3 w-5/6 rounded bg-ods-border/70" />
@@ -70,11 +70,11 @@ export function ProductReleaseCardSkeleton({ className, size = 'lg' }: ProductRe
70
70
 
71
71
  {/* METADATA GRID: 4-cell placeholder, the loaded cells' own boxes (no surface of
72
72
  their own; bars on the page background). Inner heights mirror the loaded cells. */}
73
- <div className="grid w-full grid-cols-1 overflow-hidden rounded-md border border-ods-border md:grid-cols-4">
73
+ <div className="grid w-full grid-cols-1 overflow-hidden rounded-md border border-ods-border content-md:grid-cols-4">
74
74
  {[0, 1, 2].map(i => (
75
75
  <div
76
76
  key={`cell-${i}`}
77
- className="flex flex-col gap-3 border-b border-ods-border p-4 md:border-b-0 md:border-r"
77
+ className="flex flex-col gap-3 border-b border-ods-border p-4 content-md:border-b-0 content-md:border-r"
78
78
  >
79
79
  <div className="flex flex-col gap-2">
80
80
  <div className="h-7 w-24 rounded bg-ods-border" />
@@ -211,8 +211,8 @@ export function ProductReleaseCard({
211
211
  // skeleton's shape matches the loaded card exactly. The earlier
212
212
  // dynamic `gridColsClass` ternary collapsed missing cells and
213
213
  // caused 28-56px reflow on resolve.
214
- const gridColsClass = 'md:grid-cols-4';
215
- const dividerClass = 'border-b md:border-b-0 md:border-r border-ods-border';
214
+ const gridColsClass = 'content-md:grid-cols-4';
215
+ const dividerClass = 'border-b content-md:border-b-0 content-md:border-r border-ods-border';
216
216
 
217
217
  // THE shared card frame: interactive (accent border on hover) when the card
218
218
  // links or clicks, the static frame when it does neither.
@@ -228,8 +228,8 @@ export function ProductReleaseCard({
228
228
  const innerLayout = (
229
229
  <>
230
230
  {/* HERO ZONE — cover LEFT + version pill + title + summary RIGHT */}
231
- <div className="flex flex-col gap-4 md:flex-row md:gap-6">
232
- <div className="w-full flex-shrink-0 md:w-[256px]">
231
+ <div className="flex flex-col gap-4 content-md:flex-row content-md:gap-6">
232
+ <div className="w-full flex-shrink-0 content-md:w-[256px]">
233
233
  <div className="relative aspect-[1200/630] w-full overflow-hidden rounded-lg bg-ods-bg">
234
234
  {coverImage ? (
235
235
  <Image
@@ -256,8 +256,8 @@ export function ProductReleaseCard({
256
256
  1-line titles don't shrink and the catalog skeleton-to-
257
257
  content transition is shift-free. Mirrors the
258
258
  onboarding-guide catalog card. */}
259
- <div className="mb-3 flex min-h-[60px] items-start md:min-h-[72px]">
260
- <h3 className="line-clamp-2 font-['Azeret_Mono'] text-xl font-semibold leading-tight text-ods-text-primary md:text-2xl">
259
+ <div className="mb-3 flex min-h-[60px] items-start content-md:min-h-[72px]">
260
+ <h3 className="line-clamp-2 font-['Azeret_Mono'] text-xl font-semibold leading-tight text-ods-text-primary content-md:text-2xl">
261
261
  {title}
262
262
  </h3>
263
263
  </div>
@@ -265,10 +265,10 @@ export function ProductReleaseCard({
265
265
  summaries at 3 lines; `min-h` reserves the same vertical
266
266
  space when content is shorter, so the catalog grid stays
267
267
  row-consistent regardless of per-card content length.
268
- Heights derived from text-sm md:text-base × leading-relaxed
268
+ Heights derived from text-sm content-md:text-base × leading-relaxed
269
269
  (1.625): 14×1.625×3 ≈ 68 px mobile, 16×1.625×3 ≈ 78 px desktop. */}
270
- <div className="min-h-[68px] md:min-h-[78px]">
271
- <p className="line-clamp-3 font-['DM_Sans'] text-sm leading-relaxed text-ods-text-secondary md:text-base">
270
+ <div className="min-h-[68px] content-md:min-h-[78px]">
271
+ <p className="line-clamp-3 font-['DM_Sans'] text-sm leading-relaxed text-ods-text-secondary content-md:text-base">
272
272
  {summary ?? ''}
273
273
  </p>
274
274
  </div>
@@ -231,7 +231,7 @@ export function ProductReleasesView({
231
231
  </div>
232
232
 
233
233
  {/* Pagination — always present at the bottom for consistent spacing. */}
234
- <div className="mt-6 flex justify-center md:mt-8">
234
+ <div className="mt-6 flex justify-center content-md:mt-8">
235
235
  {isLoading ? (
236
236
  <div className="m-3 h-12 w-64" />
237
237
  ) : releases.length > 0 && totalPages > 1 ? (
@@ -333,14 +333,14 @@ export function ReleaseDetailPage({
333
333
  titleWrap
334
334
  backButton={showBackButton ? { label: backLabel, onClick: () => router.push(backHref) } : undefined}
335
335
  >
336
- <div className="space-y-6 md:space-y-8">
336
+ <div className="space-y-6 content-md:space-y-8">
337
337
  {/* Tags — flat product_release_tags[] from entity_tags */}
338
338
  <EntityTagBadges tags={release.product_release_tags as TagAssoc[] | undefined} />
339
339
 
340
340
  {/* Metadata Grid */}
341
- <div className="grid w-full grid-cols-1 overflow-hidden rounded-md border border-ods-border md:grid-cols-4">
341
+ <div className="grid w-full grid-cols-1 overflow-hidden rounded-md border border-ods-border content-md:grid-cols-4">
342
342
  {/* Release Type */}
343
- <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 md:border-b-0 md:border-r">
343
+ <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 content-md:border-b-0 content-md:border-r">
344
344
  <div className="flex flex-col gap-0">
345
345
  <p className="text-ods-text-primary text-h4">{releaseType.toLocaleUpperCase()}</p>
346
346
  <p className="text-ods-text-secondary text-h6">Release Type</p>
@@ -348,7 +348,7 @@ export function ReleaseDetailPage({
348
348
  </div>
349
349
 
350
350
  {/* Release Status */}
351
- <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 md:border-b-0 md:border-r">
351
+ <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 content-md:border-b-0 content-md:border-r">
352
352
  <div className="flex flex-col gap-0">
353
353
  <p className="text-ods-text-primary text-h4">{releaseStatus.toLocaleUpperCase()}</p>
354
354
  <p className="text-ods-text-secondary text-h6">Release Status</p>
@@ -356,7 +356,7 @@ export function ReleaseDetailPage({
356
356
  </div>
357
357
 
358
358
  {/* Release Date */}
359
- <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 md:border-b-0 md:border-r">
359
+ <div className="flex flex-col gap-3 border-b border-ods-border bg-ods-card p-4 content-md:border-b-0 content-md:border-r">
360
360
  <div className="flex flex-col gap-0">
361
361
  <p className="text-ods-text-primary text-h4">{formatReleaseDate(releaseDate)}</p>
362
362
  <p className="text-ods-text-secondary text-h6">Release Date</p>