@sonordev/site-kit 5.8.3 → 6.0.1

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 (543) hide show
  1. package/README.md +11 -11
  2. package/agent-manifest.json +17 -11
  3. package/dist/{AnalyticsProvider-DPXR6FSV.js → AnalyticsProvider-4RLYTVWO.js} +10 -10
  4. package/dist/{AnalyticsProvider-DPXR6FSV.js.map → AnalyticsProvider-4RLYTVWO.js.map} +1 -1
  5. package/dist/{AnalyticsProvider-IGOD64MY.mjs → AnalyticsProvider-YU6QANW5.mjs} +6 -6
  6. package/dist/{AnalyticsProvider-IGOD64MY.mjs.map → AnalyticsProvider-YU6QANW5.mjs.map} +1 -1
  7. package/dist/ArticleFaqSection-3KZ24MFS.js +13 -0
  8. package/dist/ArticleFaqSection-3KZ24MFS.js.map +1 -0
  9. package/dist/ArticleFaqSection-XZPXJVH4.mjs +11 -0
  10. package/dist/ArticleFaqSection-XZPXJVH4.mjs.map +1 -0
  11. package/dist/{BlogViewTracker-Q7Y4MN4B.mjs → ArticleViewTracker-LI44SA4T.mjs} +13 -13
  12. package/dist/ArticleViewTracker-LI44SA4T.mjs.map +1 -0
  13. package/dist/{BlogViewTracker-S3CSRBIZ.js → ArticleViewTracker-OQB2VT73.js} +16 -16
  14. package/dist/ArticleViewTracker-OQB2VT73.js.map +1 -0
  15. package/dist/ChatWidget-56SW5ITH.mjs +15 -0
  16. package/dist/{ChatWidget-G3KOUDX5.mjs.map → ChatWidget-56SW5ITH.mjs.map} +1 -1
  17. package/dist/ChatWidget-HR3OC3WA.js +24 -0
  18. package/dist/{ChatWidget-76R7NOWD.js.map → ChatWidget-HR3OC3WA.js.map} +1 -1
  19. package/dist/EngageWidget-HXGI3ZKG.js +21 -0
  20. package/dist/{EngageWidget-HD4F2ZVE.js.map → EngageWidget-HXGI3ZKG.js.map} +1 -1
  21. package/dist/EngageWidget-L4X35N2B.mjs +12 -0
  22. package/dist/{EngageWidget-UJ47PQFG.mjs.map → EngageWidget-L4X35N2B.mjs.map} +1 -1
  23. package/dist/{FileField-WNMEA3PK.mjs → FileField-GSTLU42O.mjs} +5 -5
  24. package/dist/{FileField-WNMEA3PK.mjs.map → FileField-GSTLU42O.mjs.map} +1 -1
  25. package/dist/{FileField-FV6WSZMX.js → FileField-N2P6E2PN.js} +6 -6
  26. package/dist/{FileField-FV6WSZMX.js.map → FileField-N2P6E2PN.js.map} +1 -1
  27. package/dist/{FormSpotlight-2VRQXEFI.js → FormSpotlight-QLXOS5LN.js} +9 -9
  28. package/dist/{FormSpotlight-2VRQXEFI.js.map → FormSpotlight-QLXOS5LN.js.map} +1 -1
  29. package/dist/{FormSpotlight-NGWEPGI4.mjs → FormSpotlight-UFATQTN3.mjs} +3 -3
  30. package/dist/{FormSpotlight-NGWEPGI4.mjs.map → FormSpotlight-UFATQTN3.mjs.map} +1 -1
  31. package/dist/{FormStage-Q3KQVFVW.js → FormStage-4HYDMCYU.js} +9 -9
  32. package/dist/{FormStage-Q3KQVFVW.js.map → FormStage-4HYDMCYU.js.map} +1 -1
  33. package/dist/{FormStage-7L4BBOLC.mjs → FormStage-SYOKD5TJ.mjs} +3 -3
  34. package/dist/{FormStage-7L4BBOLC.mjs.map → FormStage-SYOKD5TJ.mjs.map} +1 -1
  35. package/dist/ManagedForm-3TDSIRCM.mjs +16 -0
  36. package/dist/{ManagedForm-Z7QE4KYI.mjs.map → ManagedForm-3TDSIRCM.mjs.map} +1 -1
  37. package/dist/ManagedForm-TUSGW5EH.js +25 -0
  38. package/dist/{ManagedForm-4ZGU4R3G.js.map → ManagedForm-TUSGW5EH.js.map} +1 -1
  39. package/dist/{ManagedNewsletterForm-4TVO2VL4.mjs → ManagedNewsletterForm-CNGCBA2N.mjs} +11 -11
  40. package/dist/ManagedNewsletterForm-CNGCBA2N.mjs.map +1 -0
  41. package/dist/{ManagedNewsletterForm-22EPZB7Z.js → ManagedNewsletterForm-XJEPVJRM.js} +13 -13
  42. package/dist/ManagedNewsletterForm-XJEPVJRM.js.map +1 -0
  43. package/dist/{SignalCore-57OK3DHA.mjs → SignalCore-ARRVP6F7.mjs} +5 -5
  44. package/dist/{SignalCore-57OK3DHA.mjs.map → SignalCore-ARRVP6F7.mjs.map} +1 -1
  45. package/dist/{SignalCore-2N7UZGF3.js → SignalCore-AXK6RCR4.js} +11 -11
  46. package/dist/{SignalCore-2N7UZGF3.js.map → SignalCore-AXK6RCR4.js.map} +1 -1
  47. package/dist/SitemapSync-42VKJUOU.mjs +9 -0
  48. package/dist/{SitemapSync-WIUEYOBS.mjs.map → SitemapSync-42VKJUOU.mjs.map} +1 -1
  49. package/dist/SitemapSync-GAGREAX6.js +18 -0
  50. package/dist/{SitemapSync-GF4OKN4Z.js.map → SitemapSync-GAGREAX6.js.map} +1 -1
  51. package/dist/analytics/index.js +9 -9
  52. package/dist/analytics/index.mjs +4 -4
  53. package/dist/{blog-styles-7AKRUMV7.mjs → article-styles-DACUAOVT.mjs} +124 -124
  54. package/dist/article-styles-DACUAOVT.mjs.map +1 -0
  55. package/dist/{blog-styles-6EY3BS5Y.js → article-styles-JHHTNHOK.js} +125 -125
  56. package/dist/article-styles-JHHTNHOK.js.map +1 -0
  57. package/dist/article-tables-EWXG4LNP.js +17 -0
  58. package/dist/article-tables-EWXG4LNP.js.map +1 -0
  59. package/dist/article-tables-OGE52LNT.mjs +4 -0
  60. package/dist/article-tables-OGE52LNT.mjs.map +1 -0
  61. package/dist/{blog/BlogPost.d.ts → articles/Article.d.ts} +18 -16
  62. package/dist/articles/Article.d.ts.map +1 -0
  63. package/dist/{blog/BlogFAQ.d.ts → articles/ArticleFAQ.d.ts} +6 -6
  64. package/dist/articles/ArticleFAQ.d.ts.map +1 -0
  65. package/dist/articles/ArticleFaqSection.d.ts +3 -0
  66. package/dist/articles/ArticleFaqSection.d.ts.map +1 -0
  67. package/dist/{blog/BlogList.d.ts → articles/ArticleList.d.ts} +16 -16
  68. package/dist/articles/ArticleList.d.ts.map +1 -0
  69. package/dist/{blog/BlogViewTracker.d.ts → articles/ArticleViewTracker.d.ts} +4 -4
  70. package/dist/articles/ArticleViewTracker.d.ts.map +1 -0
  71. package/dist/{blog → articles}/AuthorCard.d.ts +3 -3
  72. package/dist/articles/AuthorCard.d.ts.map +1 -0
  73. package/dist/{blog → articles}/AuthorPage.d.ts +1 -1
  74. package/dist/articles/AuthorPage.d.ts.map +1 -0
  75. package/dist/{blog → articles}/ClusterLandingPage.d.ts +6 -6
  76. package/dist/articles/ClusterLandingPage.d.ts.map +1 -0
  77. package/dist/{blog → articles}/ClusterNavigation.d.ts +5 -5
  78. package/dist/articles/ClusterNavigation.d.ts.map +1 -0
  79. package/dist/{blog → articles}/ManagedNewsletterForm.d.ts +1 -1
  80. package/dist/articles/ManagedNewsletterForm.d.ts.map +1 -0
  81. package/dist/{blog → articles}/NewsletterShell.d.ts +1 -1
  82. package/dist/articles/NewsletterShell.d.ts.map +1 -0
  83. package/dist/{blog → articles}/NewsletterWidget.d.ts +4 -4
  84. package/dist/articles/NewsletterWidget.d.ts.map +1 -0
  85. package/dist/{blog/BlogLayout.d.ts → articles/PublicationLayout.d.ts} +18 -18
  86. package/dist/articles/PublicationLayout.d.ts.map +1 -0
  87. package/dist/{blog/BlogSidebar.d.ts → articles/PublicationSidebar.d.ts} +8 -8
  88. package/dist/articles/PublicationSidebar.d.ts.map +1 -0
  89. package/dist/{blog → articles}/RelatedPosts.d.ts +6 -6
  90. package/dist/articles/RelatedPosts.d.ts.map +1 -0
  91. package/dist/{blog → articles}/ServiceCallout.d.ts +4 -4
  92. package/dist/articles/ServiceCallout.d.ts.map +1 -0
  93. package/dist/{blog → articles}/TableOfContents.d.ts +1 -1
  94. package/dist/articles/TableOfContents.d.ts.map +1 -0
  95. package/dist/{blog/blog-styles.d.ts → articles/article-styles.d.ts} +5 -5
  96. package/dist/articles/article-styles.d.ts.map +1 -0
  97. package/dist/{blog/blog-tables.d.ts → articles/article-tables.d.ts} +4 -4
  98. package/dist/articles/article-tables.d.ts.map +1 -0
  99. package/dist/articles/artwork.d.ts +14 -0
  100. package/dist/articles/artwork.d.ts.map +1 -0
  101. package/dist/{blog → articles}/author-schema.d.ts +6 -5
  102. package/dist/articles/author-schema.d.ts.map +1 -0
  103. package/dist/{blog → articles}/author-social.d.ts +2 -2
  104. package/dist/articles/author-social.d.ts.map +1 -0
  105. package/dist/{blog → articles}/excerpt.d.ts +2 -2
  106. package/dist/articles/excerpt.d.ts.map +1 -0
  107. package/dist/{blog → articles}/index.d.ts +26 -31
  108. package/dist/articles/index.d.ts.map +1 -0
  109. package/dist/{blog → articles}/index.js +39 -73
  110. package/dist/articles/index.js.map +1 -0
  111. package/dist/{blog → articles}/index.mjs +16 -17
  112. package/dist/articles/index.mjs.map +1 -0
  113. package/dist/articles/news-sitemap.d.ts +47 -0
  114. package/dist/articles/news-sitemap.d.ts.map +1 -0
  115. package/dist/{blog/processBlogHtml.d.ts → articles/processArticleHtml.d.ts} +4 -4
  116. package/dist/articles/processArticleHtml.d.ts.map +1 -0
  117. package/dist/articles/public-reads.d.ts +27 -0
  118. package/dist/articles/public-reads.d.ts.map +1 -0
  119. package/dist/{blog → articles}/reading-time.d.ts +2 -2
  120. package/dist/articles/reading-time.d.ts.map +1 -0
  121. package/dist/{blog → articles}/routes.d.ts +16 -12
  122. package/dist/articles/routes.d.ts.map +1 -0
  123. package/dist/{blog → articles}/server-core.d.ts +94 -81
  124. package/dist/articles/server-core.d.ts.map +1 -0
  125. package/dist/articles/server-ui.d.ts +29 -0
  126. package/dist/articles/server-ui.d.ts.map +1 -0
  127. package/dist/{chunk-W2U5SLMY.js → articles/server-ui.js} +378 -499
  128. package/dist/articles/server-ui.js.map +1 -0
  129. package/dist/{chunk-MWKI6ZIS.mjs → articles/server-ui.mjs} +328 -474
  130. package/dist/articles/server-ui.mjs.map +1 -0
  131. package/dist/{blog → articles}/server.d.ts +94 -81
  132. package/dist/articles/server.d.ts.map +1 -0
  133. package/dist/articles/server.js +236 -0
  134. package/dist/articles/server.mjs +11 -0
  135. package/dist/{blog → articles}/types.d.ts +34 -34
  136. package/dist/articles/types.d.ts.map +1 -0
  137. package/dist/articles/widget-styles.d.ts +9 -0
  138. package/dist/articles/widget-styles.d.ts.map +1 -0
  139. package/dist/brand/extract-brand-profile.d.ts +1 -1
  140. package/dist/{chunk-AJBOEV4I.mjs → chunk-3T2CVHFO.mjs} +3 -3
  141. package/dist/chunk-3T2CVHFO.mjs.map +1 -0
  142. package/dist/{chunk-I3ND4XYW.js → chunk-4BEGKFMN.js} +8 -8
  143. package/dist/{chunk-I3ND4XYW.js.map → chunk-4BEGKFMN.js.map} +1 -1
  144. package/dist/{chunk-YFX3Q56O.mjs → chunk-4KAMU5HD.mjs} +3 -3
  145. package/dist/chunk-4KAMU5HD.mjs.map +1 -0
  146. package/dist/{chunk-CNTDV7HM.js → chunk-4NBJDP6Y.js} +4 -4
  147. package/dist/{chunk-CNTDV7HM.js.map → chunk-4NBJDP6Y.js.map} +1 -1
  148. package/dist/{chunk-PIEHBSCN.js → chunk-4VCR6TXK.js} +9 -9
  149. package/dist/{chunk-PIEHBSCN.js.map → chunk-4VCR6TXK.js.map} +1 -1
  150. package/dist/{chunk-ZPLN537Q.mjs → chunk-4WCA2HM6.mjs} +2 -2
  151. package/dist/chunk-4WCA2HM6.mjs.map +1 -0
  152. package/dist/{chunk-I7HOMMIK.js → chunk-5ABTBPPK.js} +15 -15
  153. package/dist/{chunk-I7HOMMIK.js.map → chunk-5ABTBPPK.js.map} +1 -1
  154. package/dist/{chunk-GHECYDSZ.mjs → chunk-5F2FIM5A.mjs} +16 -15
  155. package/dist/chunk-5F2FIM5A.mjs.map +1 -0
  156. package/dist/{chunk-Y5QZPUL3.mjs → chunk-5YVQWLSQ.mjs} +5 -5
  157. package/dist/chunk-5YVQWLSQ.mjs.map +1 -0
  158. package/dist/chunk-6DCAMW62.mjs +55 -0
  159. package/dist/chunk-6DCAMW62.mjs.map +1 -0
  160. package/dist/{chunk-ROSYCNXG.mjs → chunk-6URNNS3V.mjs} +4 -4
  161. package/dist/{chunk-ROSYCNXG.mjs.map → chunk-6URNNS3V.mjs.map} +1 -1
  162. package/dist/{chunk-PI3FHABB.js → chunk-6YEPFH3Q.js} +10 -10
  163. package/dist/{chunk-PI3FHABB.js.map → chunk-6YEPFH3Q.js.map} +1 -1
  164. package/dist/{chunk-NBT4L2D7.js → chunk-6ZOMIPU5.js} +8 -8
  165. package/dist/{chunk-NBT4L2D7.js.map → chunk-6ZOMIPU5.js.map} +1 -1
  166. package/dist/chunk-7MAHP735.mjs +6 -0
  167. package/dist/{chunk-5FETEH3Y.mjs.map → chunk-7MAHP735.mjs.map} +1 -1
  168. package/dist/{chunk-Q2NBD54V.js → chunk-7THQ4UW3.js} +2 -2
  169. package/dist/chunk-7THQ4UW3.js.map +1 -0
  170. package/dist/{chunk-CY2RFEW6.js → chunk-AQMQOH7V.js} +3 -3
  171. package/dist/{chunk-CY2RFEW6.js.map → chunk-AQMQOH7V.js.map} +1 -1
  172. package/dist/{chunk-4TLFZ6NR.mjs → chunk-AS2HUIDW.mjs} +3 -3
  173. package/dist/chunk-AS2HUIDW.mjs.map +1 -0
  174. package/dist/{chunk-YN7QHLWF.js → chunk-BBBJHYPH.js} +5 -5
  175. package/dist/chunk-BBBJHYPH.js.map +1 -0
  176. package/dist/{chunk-3OO5EDKD.mjs → chunk-BIU234TJ.mjs} +9 -9
  177. package/dist/{chunk-3OO5EDKD.mjs.map → chunk-BIU234TJ.mjs.map} +1 -1
  178. package/dist/{chunk-ZVI7MBVF.mjs → chunk-BJ4T65OK.mjs} +14 -10
  179. package/dist/chunk-BJ4T65OK.mjs.map +1 -0
  180. package/dist/{chunk-HBHS5VRN.mjs → chunk-BMH2D2EP.mjs} +3 -3
  181. package/dist/{chunk-HBHS5VRN.mjs.map → chunk-BMH2D2EP.mjs.map} +1 -1
  182. package/dist/{chunk-QQQAW6WY.js → chunk-BWSYPQ5E.js} +6 -6
  183. package/dist/{chunk-QQQAW6WY.js.map → chunk-BWSYPQ5E.js.map} +1 -1
  184. package/dist/{chunk-4LYW6PMU.mjs → chunk-C2CTLLIO.mjs} +3 -3
  185. package/dist/chunk-C2CTLLIO.mjs.map +1 -0
  186. package/dist/{chunk-O237KKCX.mjs → chunk-C5VITBN7.mjs} +5 -5
  187. package/dist/chunk-C5VITBN7.mjs.map +1 -0
  188. package/dist/{chunk-75A4KY4X.mjs → chunk-CFRJ2MK5.mjs} +2 -2
  189. package/dist/chunk-CFRJ2MK5.mjs.map +1 -0
  190. package/dist/{chunk-G2WC3CXW.js → chunk-CLO3K7PF.js} +2 -2
  191. package/dist/chunk-CLO3K7PF.js.map +1 -0
  192. package/dist/{chunk-AK6JZ3OE.mjs → chunk-EDLNMGQC.mjs} +3 -3
  193. package/dist/{chunk-DO6ATNLN.js.map → chunk-EDLNMGQC.mjs.map} +1 -1
  194. package/dist/{chunk-HX6ZG3PP.js → chunk-EMHSHAVW.js} +3 -3
  195. package/dist/chunk-EMHSHAVW.js.map +1 -0
  196. package/dist/chunk-F3EAAAZ3.js +8 -0
  197. package/dist/{chunk-7R6H3GQ4.js.map → chunk-F3EAAAZ3.js.map} +1 -1
  198. package/dist/{chunk-MYMZRHTC.js → chunk-FKFVN3GT.js} +8 -8
  199. package/dist/{chunk-MYMZRHTC.js.map → chunk-FKFVN3GT.js.map} +1 -1
  200. package/dist/{chunk-7TC3GRXQ.js → chunk-G3L26XQ7.js} +27 -23
  201. package/dist/chunk-G3L26XQ7.js.map +1 -0
  202. package/dist/{chunk-VVBMWV4O.js → chunk-GCZHKHEH.js} +7 -7
  203. package/dist/{chunk-VVBMWV4O.js.map → chunk-GCZHKHEH.js.map} +1 -1
  204. package/dist/{chunk-LGOQ65JQ.mjs → chunk-GJCQVVO3.mjs} +7 -7
  205. package/dist/{chunk-LGOQ65JQ.mjs.map → chunk-GJCQVVO3.mjs.map} +1 -1
  206. package/dist/{chunk-R5IPDAMM.mjs → chunk-GKPVGVHY.mjs} +3 -3
  207. package/dist/{chunk-R5IPDAMM.mjs.map → chunk-GKPVGVHY.mjs.map} +1 -1
  208. package/dist/{chunk-XZO5I4RS.mjs → chunk-HIZJD7TU.mjs} +3 -3
  209. package/dist/{chunk-XZO5I4RS.mjs.map → chunk-HIZJD7TU.mjs.map} +1 -1
  210. package/dist/chunk-HXHRS3PX.mjs +349 -0
  211. package/dist/chunk-HXHRS3PX.mjs.map +1 -0
  212. package/dist/{chunk-XBX2K3GA.js → chunk-IBJOGXQI.js} +4 -4
  213. package/dist/{chunk-XBX2K3GA.js.map → chunk-IBJOGXQI.js.map} +1 -1
  214. package/dist/{chunk-F2KW4OJF.mjs → chunk-IDOHDP7D.mjs} +4 -4
  215. package/dist/{chunk-F2KW4OJF.mjs.map → chunk-IDOHDP7D.mjs.map} +1 -1
  216. package/dist/{chunk-R7GHP3FY.js → chunk-JONWE26J.js} +3 -3
  217. package/dist/chunk-JONWE26J.js.map +1 -0
  218. package/dist/{chunk-PQYQHZWV.mjs → chunk-JSXLTZUP.mjs} +6 -6
  219. package/dist/{chunk-PQYQHZWV.mjs.map → chunk-JSXLTZUP.mjs.map} +1 -1
  220. package/dist/{chunk-K4TCEAUX.js → chunk-KR7ATPAN.js} +5 -5
  221. package/dist/chunk-KR7ATPAN.js.map +1 -0
  222. package/dist/{chunk-UQY5KC64.mjs → chunk-KUHMHKG5.mjs} +4 -4
  223. package/dist/{chunk-UQY5KC64.mjs.map → chunk-KUHMHKG5.mjs.map} +1 -1
  224. package/dist/{chunk-JLVCWC6A.mjs → chunk-KZVSB5WE.mjs} +2 -2
  225. package/dist/chunk-KZVSB5WE.mjs.map +1 -0
  226. package/dist/{chunk-NCDF2RK4.mjs → chunk-L63DFM4R.mjs} +3 -3
  227. package/dist/{chunk-NCDF2RK4.mjs.map → chunk-L63DFM4R.mjs.map} +1 -1
  228. package/dist/{chunk-25F2EXOC.mjs → chunk-LCYODFNY.mjs} +3 -3
  229. package/dist/{chunk-25F2EXOC.mjs.map → chunk-LCYODFNY.mjs.map} +1 -1
  230. package/dist/{chunk-RFAAY3BW.mjs → chunk-LFASEDBO.mjs} +3 -3
  231. package/dist/{chunk-RFAAY3BW.mjs.map → chunk-LFASEDBO.mjs.map} +1 -1
  232. package/dist/chunk-LWCAZDY2.js +355 -0
  233. package/dist/chunk-LWCAZDY2.js.map +1 -0
  234. package/dist/{chunk-7VZYP4K5.js → chunk-LZPICBND.js} +15 -15
  235. package/dist/{chunk-7VZYP4K5.js.map → chunk-LZPICBND.js.map} +1 -1
  236. package/dist/{chunk-LMEUYRRR.js → chunk-NAA5J2JM.js} +233 -120
  237. package/dist/chunk-NAA5J2JM.js.map +1 -0
  238. package/dist/{chunk-OE7CTIOU.mjs → chunk-NE6QGHFT.mjs} +4 -4
  239. package/dist/{chunk-OE7CTIOU.mjs.map → chunk-NE6QGHFT.mjs.map} +1 -1
  240. package/dist/{chunk-UEUWC3PH.mjs → chunk-NNQG25M2.mjs} +3 -3
  241. package/dist/{chunk-UEUWC3PH.mjs.map → chunk-NNQG25M2.mjs.map} +1 -1
  242. package/dist/{chunk-S2Y7LVQ2.js → chunk-NVOYCK2S.js} +14 -14
  243. package/dist/{chunk-S2Y7LVQ2.js.map → chunk-NVOYCK2S.js.map} +1 -1
  244. package/dist/{chunk-FD72QL42.mjs → chunk-NVWUXCUQ.mjs} +3 -3
  245. package/dist/{chunk-FD72QL42.mjs.map → chunk-NVWUXCUQ.mjs.map} +1 -1
  246. package/dist/{chunk-4NVMAYMQ.mjs → chunk-NYNL6UQV.mjs} +4 -4
  247. package/dist/{chunk-4NVMAYMQ.mjs.map → chunk-NYNL6UQV.mjs.map} +1 -1
  248. package/dist/{chunk-GQF6QAQS.js → chunk-OGNX6JT4.js} +5 -5
  249. package/dist/chunk-OGNX6JT4.js.map +1 -0
  250. package/dist/{chunk-FVW6FT6L.js → chunk-OSU4QA5M.js} +8 -8
  251. package/dist/{chunk-FVW6FT6L.js.map → chunk-OSU4QA5M.js.map} +1 -1
  252. package/dist/{chunk-2TS4ALOA.js → chunk-P2HOVWJW.js} +14 -14
  253. package/dist/{chunk-2TS4ALOA.js.map → chunk-P2HOVWJW.js.map} +1 -1
  254. package/dist/{chunk-5HFD36AN.js → chunk-PKZMNDZJ.js} +5 -5
  255. package/dist/{chunk-5HFD36AN.js.map → chunk-PKZMNDZJ.js.map} +1 -1
  256. package/dist/{chunk-DO6ATNLN.js → chunk-PRAHDLKT.js} +5 -5
  257. package/dist/chunk-PRAHDLKT.js.map +1 -0
  258. package/dist/{chunk-IIHIRHSW.js → chunk-QD4BNYMM.js} +18 -18
  259. package/dist/{chunk-IIHIRHSW.js.map → chunk-QD4BNYMM.js.map} +1 -1
  260. package/dist/{chunk-HEOLGDUX.mjs → chunk-QJ3SMLND.mjs} +4 -4
  261. package/dist/{chunk-HEOLGDUX.mjs.map → chunk-QJ3SMLND.mjs.map} +1 -1
  262. package/dist/{chunk-QOOLMSBY.mjs → chunk-QP56JXMH.mjs} +3 -3
  263. package/dist/{chunk-QOOLMSBY.mjs.map → chunk-QP56JXMH.mjs.map} +1 -1
  264. package/dist/{chunk-MQS6AF5K.mjs → chunk-QXPGIOU7.mjs} +3 -3
  265. package/dist/{chunk-MQS6AF5K.mjs.map → chunk-QXPGIOU7.mjs.map} +1 -1
  266. package/dist/{chunk-TR3F3CLV.js → chunk-R6UE6MC5.js} +10 -10
  267. package/dist/{chunk-TR3F3CLV.js.map → chunk-R6UE6MC5.js.map} +1 -1
  268. package/dist/{chunk-OEW2LFOV.js → chunk-RLUNEEGM.js} +4 -4
  269. package/dist/{chunk-OEW2LFOV.js.map → chunk-RLUNEEGM.js.map} +1 -1
  270. package/dist/{chunk-SFC3OWSE.mjs → chunk-RX4TVHSN.mjs} +4 -4
  271. package/dist/{chunk-SFC3OWSE.mjs.map → chunk-RX4TVHSN.mjs.map} +1 -1
  272. package/dist/{chunk-YQTE32QK.js → chunk-SG7CWD2R.js} +17 -16
  273. package/dist/chunk-SG7CWD2R.js.map +1 -0
  274. package/dist/{chunk-4C5CZJIN.mjs → chunk-SU42KCQD.mjs} +4 -4
  275. package/dist/{chunk-4C5CZJIN.mjs.map → chunk-SU42KCQD.mjs.map} +1 -1
  276. package/dist/{chunk-XEB77DQS.js → chunk-TBXWJJGT.js} +3 -3
  277. package/dist/chunk-TBXWJJGT.js.map +1 -0
  278. package/dist/{chunk-CZEIHFLW.js → chunk-TC3JSQUS.js} +4 -4
  279. package/dist/{chunk-CZEIHFLW.js.map → chunk-TC3JSQUS.js.map} +1 -1
  280. package/dist/{chunk-LB44BNJ2.js → chunk-TST2IKYO.js} +14 -14
  281. package/dist/{chunk-LB44BNJ2.js.map → chunk-TST2IKYO.js.map} +1 -1
  282. package/dist/{chunk-POJZA67T.mjs → chunk-U6F6AN23.mjs} +4 -4
  283. package/dist/{chunk-POJZA67T.mjs.map → chunk-U6F6AN23.mjs.map} +1 -1
  284. package/dist/{chunk-FTB5LAD3.js → chunk-UCBBZXJI.js} +4 -4
  285. package/dist/{chunk-FTB5LAD3.js.map → chunk-UCBBZXJI.js.map} +1 -1
  286. package/dist/{chunk-U2V542EO.mjs → chunk-UHYRVALN.mjs} +2 -2
  287. package/dist/{chunk-U2V542EO.mjs.map → chunk-UHYRVALN.mjs.map} +1 -1
  288. package/dist/{chunk-F3KA6V2Y.js → chunk-UILVPI2R.js} +3 -3
  289. package/dist/{chunk-F3KA6V2Y.js.map → chunk-UILVPI2R.js.map} +1 -1
  290. package/dist/{chunk-UHM3CCCC.js → chunk-ULSFOC3J.js} +12 -12
  291. package/dist/{chunk-UHM3CCCC.js.map → chunk-ULSFOC3J.js.map} +1 -1
  292. package/dist/{chunk-4L7OHRWB.mjs → chunk-UO6KTHBQ.mjs} +4 -4
  293. package/dist/{chunk-4L7OHRWB.mjs.map → chunk-UO6KTHBQ.mjs.map} +1 -1
  294. package/dist/{chunk-H5NBFNND.js → chunk-UZKXOZHR.js} +2 -2
  295. package/dist/chunk-UZKXOZHR.js.map +1 -0
  296. package/dist/{chunk-QXMWNIKL.mjs → chunk-WLCPBZOS.mjs} +3 -3
  297. package/dist/{chunk-QXMWNIKL.mjs.map → chunk-WLCPBZOS.mjs.map} +1 -1
  298. package/dist/{chunk-3EY5E35M.mjs → chunk-X3MY4PL7.mjs} +210 -107
  299. package/dist/chunk-X3MY4PL7.mjs.map +1 -0
  300. package/dist/{chunk-PIPERXI7.mjs → chunk-XLFTZV34.mjs} +3 -3
  301. package/dist/{chunk-PIPERXI7.mjs.map → chunk-XLFTZV34.mjs.map} +1 -1
  302. package/dist/{chunk-6CCS2EGF.js → chunk-XWKY46NU.js} +13 -13
  303. package/dist/{chunk-6CCS2EGF.js.map → chunk-XWKY46NU.js.map} +1 -1
  304. package/dist/{chunk-SEP5D4MI.js → chunk-Y2CP5J6Q.js} +2 -2
  305. package/dist/{chunk-SEP5D4MI.js.map → chunk-Y2CP5J6Q.js.map} +1 -1
  306. package/dist/{chunk-KV7XXLJV.mjs → chunk-YPGUV6BT.mjs} +3 -3
  307. package/dist/{chunk-KV7XXLJV.mjs.map → chunk-YPGUV6BT.mjs.map} +1 -1
  308. package/dist/chunk-YRCR7WY3.js +65 -0
  309. package/dist/chunk-YRCR7WY3.js.map +1 -0
  310. package/dist/{chunk-WINDP776.mjs → chunk-YTJKVWHY.mjs} +5 -5
  311. package/dist/{chunk-WINDP776.mjs.map → chunk-YTJKVWHY.mjs.map} +1 -1
  312. package/dist/chunk-ZL35JCDA.mjs +61 -0
  313. package/dist/chunk-ZL35JCDA.mjs.map +1 -0
  314. package/dist/chunk-ZR43XFUM.js +60 -0
  315. package/dist/chunk-ZR43XFUM.js.map +1 -0
  316. package/dist/{chunk-NMM27UOF.js → chunk-ZRWIMNQF.js} +3 -3
  317. package/dist/{chunk-NMM27UOF.js.map → chunk-ZRWIMNQF.js.map} +1 -1
  318. package/dist/{chunk-NR7YZQRR.js → chunk-ZYJYBE5G.js} +4 -4
  319. package/dist/{chunk-NR7YZQRR.js.map → chunk-ZYJYBE5G.js.map} +1 -1
  320. package/dist/cli/commands/setup.d.ts +2 -2
  321. package/dist/cli/commands/sync.d.ts +2 -2
  322. package/dist/cli/commands/sync.d.ts.map +1 -1
  323. package/dist/cli/index.js +76 -76
  324. package/dist/cli/index.mjs +29 -29
  325. package/dist/client/index.js +5 -5
  326. package/dist/client/index.mjs +4 -4
  327. package/dist/commerce/index.js +57 -57
  328. package/dist/commerce/index.mjs +5 -5
  329. package/dist/discover-LPN55L5B.js +21 -0
  330. package/dist/{discover-NGEPXZOD.js.map → discover-LPN55L5B.js.map} +1 -1
  331. package/dist/discover-QGA6QFSR.mjs +4 -0
  332. package/dist/{discover-CYXYKHZ2.mjs.map → discover-QGA6QFSR.mjs.map} +1 -1
  333. package/dist/engage/index.js +13 -13
  334. package/dist/engage/index.mjs +7 -7
  335. package/dist/fleet/contract.d.ts +1 -1
  336. package/dist/fleet/contract.js +12 -12
  337. package/dist/fleet/contract.mjs +1 -1
  338. package/dist/fleet/index.js +20 -20
  339. package/dist/fleet/index.mjs +5 -5
  340. package/dist/forms/index.js +16 -16
  341. package/dist/forms/index.mjs +9 -9
  342. package/dist/forms/server.js +10 -10
  343. package/dist/forms/server.mjs +8 -8
  344. package/dist/forms/static.js +7 -7
  345. package/dist/forms/static.mjs +6 -6
  346. package/dist/images/index.js +11 -11
  347. package/dist/images/index.mjs +4 -4
  348. package/dist/index.d.ts +2 -2
  349. package/dist/index.d.ts.map +1 -1
  350. package/dist/index.js +70 -70
  351. package/dist/index.mjs +12 -12
  352. package/dist/layout/client.js +10 -10
  353. package/dist/layout/client.mjs +9 -9
  354. package/dist/layout/index.js +12 -12
  355. package/dist/layout/index.mjs +10 -10
  356. package/dist/llms/SpeakableSchema.d.ts +1 -1
  357. package/dist/llms/SpeakableSchema.d.ts.map +1 -1
  358. package/dist/llms/client.js +15 -15
  359. package/dist/llms/client.mjs +1 -1
  360. package/dist/llms/generateLLMsTxt.d.ts.map +1 -1
  361. package/dist/llms/index.js +32 -31
  362. package/dist/llms/index.js.map +1 -1
  363. package/dist/llms/index.mjs +8 -7
  364. package/dist/llms/index.mjs.map +1 -1
  365. package/dist/llms/types.d.ts +7 -0
  366. package/dist/llms/types.d.ts.map +1 -1
  367. package/dist/llms/writeLLMsTxt.d.ts +6 -0
  368. package/dist/llms/writeLLMsTxt.d.ts.map +1 -1
  369. package/dist/maps/index.js +5 -5
  370. package/dist/maps/index.mjs +3 -3
  371. package/dist/og/index.js +7 -7
  372. package/dist/og/index.mjs +2 -2
  373. package/dist/og/route.d.ts +6 -6
  374. package/dist/og/route.js.map +1 -1
  375. package/dist/og/route.mjs.map +1 -1
  376. package/dist/og/wiring.d.ts +4 -4
  377. package/dist/og/wiring.d.ts.map +1 -1
  378. package/dist/proxy/index.js +5 -5
  379. package/dist/proxy/index.mjs +3 -3
  380. package/dist/redirects/index.js +10 -10
  381. package/dist/redirects/index.mjs +3 -3
  382. package/dist/redirects/not-found.js +7 -7
  383. package/dist/redirects/not-found.mjs +3 -3
  384. package/dist/robots/indexnow.d.ts +17 -0
  385. package/dist/robots/indexnow.d.ts.map +1 -0
  386. package/dist/robots/indexnow.js +39 -0
  387. package/dist/robots/indexnow.js.map +1 -0
  388. package/dist/robots/indexnow.mjs +34 -0
  389. package/dist/robots/indexnow.mjs.map +1 -0
  390. package/dist/search/contract.d.ts +1 -1
  391. package/dist/search/contract.js +3 -3
  392. package/dist/search/contract.mjs +1 -1
  393. package/dist/search/index.js +7 -7
  394. package/dist/search/index.mjs +5 -5
  395. package/dist/seo/client.js +6 -6
  396. package/dist/seo/client.mjs +5 -5
  397. package/dist/seo/index.js +47 -47
  398. package/dist/seo/index.js.map +1 -1
  399. package/dist/seo/index.mjs +12 -12
  400. package/dist/seo/index.mjs.map +1 -1
  401. package/dist/seo/register-sitemap-cli-impl.d.ts +2 -0
  402. package/dist/seo/register-sitemap-cli-impl.d.ts.map +1 -1
  403. package/dist/seo/register-sitemap-cli.d.ts +1 -0
  404. package/dist/seo/register-sitemap-cli.d.ts.map +1 -1
  405. package/dist/seo/register-sitemap-cli.js +18 -14
  406. package/dist/seo/register-sitemap-cli.mjs +11 -7
  407. package/dist/seo/server.js +21 -21
  408. package/dist/seo/server.mjs +5 -5
  409. package/dist/server/index.js +3 -3
  410. package/dist/server/index.mjs +2 -2
  411. package/dist/{server-api-A2EFCTZX.mjs → server-api-7F5P6CSH.mjs} +6 -6
  412. package/dist/{server-api-A2EFCTZX.mjs.map → server-api-7F5P6CSH.mjs.map} +1 -1
  413. package/dist/{server-api-IWTZ7C3O.js → server-api-OC5JNK6R.js} +24 -24
  414. package/dist/{server-api-IWTZ7C3O.js.map → server-api-OC5JNK6R.js.map} +1 -1
  415. package/dist/shared/build-entries.d.ts +9 -7
  416. package/dist/shared/build-entries.d.ts.map +1 -1
  417. package/dist/shared/version.d.ts +1 -1
  418. package/dist/signal/index.js +11 -11
  419. package/dist/signal/index.mjs +2 -2
  420. package/dist/sitemap/discover.d.ts +1 -1
  421. package/dist/sitemap/index.d.ts +13 -1
  422. package/dist/sitemap/index.d.ts.map +1 -1
  423. package/dist/sitemap/index.js +48 -30
  424. package/dist/sitemap/index.js.map +1 -1
  425. package/dist/sitemap/index.mjs +27 -9
  426. package/dist/sitemap/index.mjs.map +1 -1
  427. package/dist/sitemap/shared.d.ts +1 -1
  428. package/dist/sitemap/shared.d.ts.map +1 -1
  429. package/dist/sites/site-param.d.ts +2 -2
  430. package/dist/sync/index.js +17 -17
  431. package/dist/sync/index.mjs +4 -4
  432. package/dist/types.d.ts +1 -1
  433. package/dist/types.d.ts.map +1 -1
  434. package/dist/writeLLMsTxt-LYYC3ZSN.mjs +11 -0
  435. package/dist/writeLLMsTxt-XHLHJ5XY.js +20 -0
  436. package/package.json +20 -14
  437. package/dist/BlogPostFaqSection-P3GXVQPA.mjs +0 -11
  438. package/dist/BlogPostFaqSection-P3GXVQPA.mjs.map +0 -1
  439. package/dist/BlogPostFaqSection-YG5J4B4U.js +0 -13
  440. package/dist/BlogPostFaqSection-YG5J4B4U.js.map +0 -1
  441. package/dist/BlogViewTracker-Q7Y4MN4B.mjs.map +0 -1
  442. package/dist/BlogViewTracker-S3CSRBIZ.js.map +0 -1
  443. package/dist/ChatWidget-76R7NOWD.js +0 -24
  444. package/dist/ChatWidget-G3KOUDX5.mjs +0 -15
  445. package/dist/EngageWidget-HD4F2ZVE.js +0 -21
  446. package/dist/EngageWidget-UJ47PQFG.mjs +0 -12
  447. package/dist/ManagedForm-4ZGU4R3G.js +0 -25
  448. package/dist/ManagedForm-Z7QE4KYI.mjs +0 -16
  449. package/dist/ManagedNewsletterForm-22EPZB7Z.js.map +0 -1
  450. package/dist/ManagedNewsletterForm-4TVO2VL4.mjs.map +0 -1
  451. package/dist/SitemapSync-GF4OKN4Z.js +0 -18
  452. package/dist/SitemapSync-WIUEYOBS.mjs +0 -9
  453. package/dist/blog/AuthorCard.d.ts.map +0 -1
  454. package/dist/blog/AuthorPage.d.ts.map +0 -1
  455. package/dist/blog/BlogFAQ.d.ts.map +0 -1
  456. package/dist/blog/BlogLayout.d.ts.map +0 -1
  457. package/dist/blog/BlogList.d.ts.map +0 -1
  458. package/dist/blog/BlogPost.d.ts.map +0 -1
  459. package/dist/blog/BlogPostFaqSection.d.ts +0 -3
  460. package/dist/blog/BlogPostFaqSection.d.ts.map +0 -1
  461. package/dist/blog/BlogSidebar.d.ts.map +0 -1
  462. package/dist/blog/BlogViewTracker.d.ts.map +0 -1
  463. package/dist/blog/ClusterLandingPage.d.ts.map +0 -1
  464. package/dist/blog/ClusterNavigation.d.ts.map +0 -1
  465. package/dist/blog/ManagedNewsletterForm.d.ts.map +0 -1
  466. package/dist/blog/NewsletterShell.d.ts.map +0 -1
  467. package/dist/blog/NewsletterWidget.d.ts.map +0 -1
  468. package/dist/blog/RelatedPosts.d.ts.map +0 -1
  469. package/dist/blog/ServiceCallout.d.ts.map +0 -1
  470. package/dist/blog/TableOfContents.d.ts.map +0 -1
  471. package/dist/blog/artwork.d.ts +0 -14
  472. package/dist/blog/artwork.d.ts.map +0 -1
  473. package/dist/blog/author-schema.d.ts.map +0 -1
  474. package/dist/blog/author-social.d.ts.map +0 -1
  475. package/dist/blog/blog-styles.d.ts.map +0 -1
  476. package/dist/blog/blog-tables.d.ts.map +0 -1
  477. package/dist/blog/excerpt.d.ts.map +0 -1
  478. package/dist/blog/index.d.ts.map +0 -1
  479. package/dist/blog/index.js.map +0 -1
  480. package/dist/blog/index.mjs.map +0 -1
  481. package/dist/blog/processBlogHtml.d.ts.map +0 -1
  482. package/dist/blog/public-reads.d.ts +0 -27
  483. package/dist/blog/public-reads.d.ts.map +0 -1
  484. package/dist/blog/reading-time.d.ts.map +0 -1
  485. package/dist/blog/routes.d.ts.map +0 -1
  486. package/dist/blog/server-core.d.ts.map +0 -1
  487. package/dist/blog/server-ui.d.ts +0 -29
  488. package/dist/blog/server-ui.d.ts.map +0 -1
  489. package/dist/blog/server-ui.js +0 -267
  490. package/dist/blog/server-ui.js.map +0 -1
  491. package/dist/blog/server-ui.mjs +0 -203
  492. package/dist/blog/server-ui.mjs.map +0 -1
  493. package/dist/blog/server.d.ts.map +0 -1
  494. package/dist/blog/server.js +0 -207
  495. package/dist/blog/server.mjs +0 -10
  496. package/dist/blog/types.d.ts.map +0 -1
  497. package/dist/blog/widget-styles.d.ts +0 -9
  498. package/dist/blog/widget-styles.d.ts.map +0 -1
  499. package/dist/blog-styles-6EY3BS5Y.js.map +0 -1
  500. package/dist/blog-styles-7AKRUMV7.mjs.map +0 -1
  501. package/dist/blog-tables-DWDEWA3S.js +0 -17
  502. package/dist/blog-tables-DWDEWA3S.js.map +0 -1
  503. package/dist/blog-tables-QGIHOESZ.mjs +0 -4
  504. package/dist/blog-tables-QGIHOESZ.mjs.map +0 -1
  505. package/dist/chunk-3EY5E35M.mjs.map +0 -1
  506. package/dist/chunk-4LYW6PMU.mjs.map +0 -1
  507. package/dist/chunk-4TLFZ6NR.mjs.map +0 -1
  508. package/dist/chunk-5B4J3IBX.mjs +0 -164
  509. package/dist/chunk-5B4J3IBX.mjs.map +0 -1
  510. package/dist/chunk-5FETEH3Y.mjs +0 -6
  511. package/dist/chunk-75A4KY4X.mjs.map +0 -1
  512. package/dist/chunk-7R6H3GQ4.js +0 -8
  513. package/dist/chunk-7TC3GRXQ.js.map +0 -1
  514. package/dist/chunk-AJBOEV4I.mjs.map +0 -1
  515. package/dist/chunk-AK6JZ3OE.mjs.map +0 -1
  516. package/dist/chunk-FHPW7XVH.js +0 -175
  517. package/dist/chunk-FHPW7XVH.js.map +0 -1
  518. package/dist/chunk-G2WC3CXW.js.map +0 -1
  519. package/dist/chunk-GHECYDSZ.mjs.map +0 -1
  520. package/dist/chunk-GQF6QAQS.js.map +0 -1
  521. package/dist/chunk-H5NBFNND.js.map +0 -1
  522. package/dist/chunk-HX6ZG3PP.js.map +0 -1
  523. package/dist/chunk-JLVCWC6A.mjs.map +0 -1
  524. package/dist/chunk-K4TCEAUX.js.map +0 -1
  525. package/dist/chunk-LMEUYRRR.js.map +0 -1
  526. package/dist/chunk-MWKI6ZIS.mjs.map +0 -1
  527. package/dist/chunk-O237KKCX.mjs.map +0 -1
  528. package/dist/chunk-Q2NBD54V.js.map +0 -1
  529. package/dist/chunk-R7GHP3FY.js.map +0 -1
  530. package/dist/chunk-W2U5SLMY.js.map +0 -1
  531. package/dist/chunk-XEB77DQS.js.map +0 -1
  532. package/dist/chunk-Y5QZPUL3.mjs.map +0 -1
  533. package/dist/chunk-YFX3Q56O.mjs.map +0 -1
  534. package/dist/chunk-YN7QHLWF.js.map +0 -1
  535. package/dist/chunk-YQTE32QK.js.map +0 -1
  536. package/dist/chunk-ZPLN537Q.mjs.map +0 -1
  537. package/dist/chunk-ZVI7MBVF.mjs.map +0 -1
  538. package/dist/discover-CYXYKHZ2.mjs +0 -4
  539. package/dist/discover-NGEPXZOD.js +0 -21
  540. package/dist/writeLLMsTxt-CQ7A4AAL.js +0 -19
  541. package/dist/writeLLMsTxt-NX5MSPYX.mjs +0 -10
  542. /package/dist/{blog → articles}/server.js.map +0 -0
  543. /package/dist/{blog → articles}/server.mjs.map +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":["sanitizeFleetVersion","resolveAnalyticsTarget","buildFleetHeartbeatPayload","SITE_KIT_VERSION","sonorFetch","useDeferredActivation","useRef","useEffect"],"mappings":"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","file":"chunk-TR3F3CLV.js","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the shared analytics send gate and `sonorFetch`, which stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions extends AnalyticsGateOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n const target = resolveAnalyticsTarget(opts)\n if (!target) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${target.apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { apiKey: target.apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps extends AnalyticsGateOptions {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n allowInFrame = false,\n allowLocalhost = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const target = resolveAnalyticsTarget({ apiUrl, apiKey, allowInFrame, allowLocalhost, debug })\n if (!target) return // Gate before stamping session storage.\n const { apiUrl: url, apiKey: key } = target\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n allowInFrame,\n allowLocalhost,\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug, allowInFrame, allowLocalhost])\n\n return null\n}\n"]}
1
+ {"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":["sanitizeFleetVersion","resolveAnalyticsTarget","buildFleetHeartbeatPayload","SITE_KIT_VERSION","sonorFetch","useDeferredActivation","useRef","useEffect"],"mappings":"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","file":"chunk-R6UE6MC5.js","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the shared analytics send gate and `sonorFetch`, which stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions extends AnalyticsGateOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n const target = resolveAnalyticsTarget(opts)\n if (!target) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${target.apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { apiKey: target.apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps extends AnalyticsGateOptions {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n allowInFrame = false,\n allowLocalhost = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const target = resolveAnalyticsTarget({ apiUrl, apiKey, allowInFrame, allowLocalhost, debug })\n if (!target) return // Gate before stamping session storage.\n const { apiUrl: url, apiKey: key } = target\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n allowInFrame,\n allowLocalhost,\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug, allowInFrame, allowLocalhost])\n\n return null\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkCUNZAD46_js = require('./chunk-CUNZAD46.js');
4
- var chunkR7GHP3FY_js = require('./chunk-R7GHP3FY.js');
4
+ var chunkJONWE26J_js = require('./chunk-JONWE26J.js');
5
5
 
6
6
  // src/og/template.ts
7
7
  function fitScript() {
@@ -288,7 +288,7 @@ function paramCardImage(path, options = {}) {
288
288
  }
289
289
  function codeCardImage(path, options = {}) {
290
290
  const base = bareRoute(path);
291
- const url = chunkR7GHP3FY_js.applyTrailingSlash(`${base === "/" ? "" : base}/opengraph-image`, chunkR7GHP3FY_js.resolveTrailingSlash(options.trailingSlash));
291
+ const url = chunkJONWE26J_js.applyTrailingSlash(`${base === "/" ? "" : base}/opengraph-image`, chunkJONWE26J_js.resolveTrailingSlash(options.trailingSlash));
292
292
  return {
293
293
  url,
294
294
  width: chunkCUNZAD46_js.OG_WIDTH,
@@ -332,5 +332,5 @@ exports.paramCardImage = paramCardImage;
332
332
  exports.paramCardKeys = paramCardKeys;
333
333
  exports.paramCardUrl = paramCardUrl;
334
334
  exports.unmatchedCardKeys = unmatchedCardKeys;
335
- //# sourceMappingURL=chunk-OEW2LFOV.js.map
336
- //# sourceMappingURL=chunk-OEW2LFOV.js.map
335
+ //# sourceMappingURL=chunk-RLUNEEGM.js.map
336
+ //# sourceMappingURL=chunk-RLUNEEGM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/og/template.ts","../src/og/pages.ts"],"names":["TITLE_MAX_PX","TITLE_MIN_PX","OG_HEIGHT","OG_REPORT_ATTR","OG_WIDTH","barTextColor","applyTrailingSlash","resolveTrailingSlash"],"mappings":";;;;;;AAsCA,SAAS,SAAA,GAAoB;AAC3B,EAAA,OAAO,CAAA;AAAA;AAAA,sCAAA,EAE+BA,6BAAY,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAA,EAmBjCA,6BAAY,CAAA;AAAA,oBAAA,EACTC,6BAAY,CAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,iCAAA,EAQCA,6BAAY,CAAA;AAAA;AAAA;AAAA;AAAA,4BAAA,EAIjBA,6BAAY,CAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAAA,EA2BLC,0BAAS,CAAA;AAAA,kDAAA,EACMA,0BAAS,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCAAA,EAOlBC,+BAAc,CAAA;AAAA;AAAA,SAAA,CAAA;AAGzD;AAEA,SAAS,IAAI,CAAA,EAAmB;AAC9B,EAAA,OAAO,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AAC5E;AAEA,SAAS,SAAS,MAAA,EAA8B;AAC9C,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,EAAO,MAAA,GACxB,OAAO,KAAA,GACP,CAAC,EAAE,MAAA,EAAQ,kBAAA,EAAoB,OAAA,EAAS,CAAC,GAAG,GAAG,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,MACd,GAAA,CAAI,CAAA,CAAA,KAAK,UAAU,CAAA,CAAE,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,UAAU,CAAA,CAAE,OAAA,IAAW,CAAC,GAAG,CAAA,EAAG,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,CAAA,CACvF,IAAA,CAAK,GAAG,CAAA;AACX,EAAA,OAAO,iDAAiD,QAAQ,CAAA,iCAAA,CAAA;AAClE;AAEA,SAAS,cAAc,MAAA,EAA8B;AACnD,EAAA,OAAO,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA,EAAG,MAAA,IAAU,kBAAA;AACtC;AAEA,SAAS,WAAW,MAAA,EAA8B;AAChD,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,IAAS,EAAC;AAC/B,EAAA,OAAO,KAAA,CAAM,MAAA,GAAS,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,SAAS,CAAC,CAAA,CAAE,MAAA,GAAS,aAAA,CAAc,MAAM,CAAA;AACjF;AAEA,SAAS,MAAM,KAAA,EAAwB;AACrC,EAAA,OAAO,KAAA,CAAM,SAAS,KAAA,CAAM,IAAA;AAC9B;AAEA,SAAS,iBAAiB,MAAA,EAA8B;AACtD,EAAA,IAAI,CAAC,MAAA,CAAO,UAAA,EAAY,OAAO,EAAA;AAC/B,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,GAAO,CAAA,EAAG,KAAA,KAAU,MAAA,CAAO,UAAA;AAC1C,EAAA,OAAO;AAAA,4BAAA,EACqB,GAAA,CAAI,KAAK,CAAC,CAAA,qBAAA,EAAwB,IAAI,CAAA;AAAA,EAAA,EAChE,QAAQ,CAAA,qCAAA,EAAwC,GAAA,CAAI,KAAK,CAAC,aAAa,EAAE,CAAA,CAAA;AAC7E;AAEA,SAAS,OAAA,CAAQ,SAAoB,KAAA,EAAwB;AAC3D,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,EAAK,MAAA,EAAQ,OAAO,EAAA;AACjC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAClB,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,MAAA,EAAS,GAAA,CAAI,CAAC,CAAC,CAAA,OAAA,CAAS,CAAA,CACjC,IAAA,CAAK,2BAA2B,CAAA;AACnC,EAAA,OAAO,sCAAsC,GAAA,CAAI,KAAA,CAAM,MAAM,CAAC,KAAK,IAAI,CAAA,MAAA,CAAA;AACzE;AAEA,SAAS,SAAS,MAAA,EAA8B;AAC9C,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAM,GAAI,MAAA;AAC3B,EAAA,MAAM,YAAY,GAAA,CAAI,OAAA,CAAQ,KAAK,CAAA,CAAE,OAAA,CAAQ,OAAO,MAAM,CAAA;AAK1D,EAAA,MAAM,IAAA,GACJ,MAAA,CAAO,IAAA,IAAA,CAAS,MAAA,CAAO,WAAW,MAAA,CAAO,IAAA,GAAO,YAAA,GAAe,QAAA,CAAA,MAAe,UAC1E,CAAA,uBAAA,EAA0B,GAAA,CAAI,MAAA,CAAO,IAAI,CAAC,CAAA,EAAA,CAAA,GAC1C,EAAA;AACN,EAAA,OAAO;AAAA;AAAA,MAAA,EAED,IAAI;AAAA,MAAA,EACJ,OAAA,CAAQ,SAAS,CAAA,oBAAA,EAAuB,GAAA,CAAI,QAAQ,MAAM,CAAC,WAAW,EAAE;AAAA,UAAA,EACpE,SAAS,CAAA;AAAA,MAAA,EACb,OAAA,CAAQ,QAAA,GAAW,CAAA,8BAAA,EAAiC,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,CAAA,EAAA,EAAK,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAC,WAAW,EAAE;AAAA,MAAA,EAC5G,OAAA,CAAQ,IAAA,GAAO,CAAA,sCAAA,EAAyC,GAAA,CAAI,WAAW,MAAM,CAAC,CAAC,CAAA,WAAA,EAAc,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAC,WAAW,EAAE;AAAA,UAAA,CAAA;AAEnI;AAEO,SAAS,YAAY,MAAA,EAA8B;AACxD,EAAA,IAAI,MAAA,CAAO,IAAA,EAAM,OAAO,MAAA,CAAO,IAAA;AAC/B,EAAA,IAAI,OAAO,QAAA,EAAU;AACnB,IAAA,OAAO,MAAA,CAAO,SAAS,MAAA,EAAQ;AAAA,MAC7B,GAAA;AAAA,MACA,QAAA,EAAU,SAAS,MAAM,CAAA;AAAA,MACzB,KAAA,EAAOC,yBAAA;AAAA,MACP,MAAA,EAAQF;AAAA,KACT,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAM,SAAS,IAAA,GAAO,YAAA,GAAe,UAAS,GAAI,MAAA;AACjE,EAAA,MAAM,OAAA,GAAU,cAAc,MAAM,CAAA;AAEpC,EAAA,MAAM,KAAA,GAAQ,IAAA,GACV,CAAA,qCAAA,EAAwC,GAAA,CAAI,KAAA,CAAM,OAAO,CAAC,CAAA,YAAA,EAAe,GAAA,CAAI,IAAI,CAAC,CAAA,QAAA,CAAA,GAClF,EAAA;AAEJ,EAAA,MAAM,QAAQ,MAAA,CAAO,KAAA,GACjB,wCAAwC,MAAA,CAAO,KAAA,CAAM,SAAS,GAAG,CAAA,iBAAA,EAAoB,OAAO,KAAA,CAAM,MAAA,IAAU,EAAE,CAAA,cAAA,EAAiB,GAAA,CAAI,OAAO,KAAA,CAAM,GAAG,CAAC,CAAA,QAAA,CAAA,GACpJ,EAAA;AAEJ,EAAA,MAAM,IAAA,GACJ,MAAA,KAAW,UAAA,GACP,CAAA,2BAAA,EAA8B,KAAK,CAAA,EAAG,QAAA,CAAS,MAAM,CAAC,CAAA,MAAA,CAAA,GACtD,MAAA,KAAW,QAAA,GACT,CAAA,yBAAA,EAA4B,SAAS,MAAM,CAAC,CAAA,MAAA,CAAA,GAC5C,MAAA,KAAW,OAAA,GACT,CAAA,kBAAA,EAAqB,QAAA,CAAS,MAAM,CAAC,CAAA,EAAG,KAAK,CAAA,MAAA,CAAA,GAC7C,CAAA,kBAAA,EAAqB,KAAA,IAAS,KAAK,CAAA,EAAG,QAAA,CAAS,MAAM,CAAC,CAAA,MAAA,CAAA;AAEhE,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA,EAIP,QAAA,CAAS,MAAM,CAAC;AAAA;AAAA;AAAA,oBAAA,EAGIE,yBAAQ,cAAcF,0BAAS,CAAA;AAAA,uCAAA,EACZ,MAAM,EAAE,CAAA,eAAA,EAAkB,OAAO,CAAA,oBAAA,EAAuB,MAAM,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,qGAAA,EAIJ,MAAA,CAAO,OAAA,CAAQ,GAAA,EAAK,MAAA,GAAS,SAAS,GAAG,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mGAAA,EAY3C,MAAM,MAAM,CAAA;AAAA;AAAA,4DAAA,EAEnDF,6BAAY,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+FAAA,EAKuBK,6BAAA,CAAa,KAAK,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlH,gBAAA,CAAiB,MAAM,CAAC;AAAA,EACxB,IAAI;AAAA,EACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,EAAS,KAAK,CAAC;AAAA,EAC9B,WAAW;AAAA,cAAA,CAAA;AAEb;;;AChNO,IAAM,OAAA,GAAU;AAGvB,SAAS,aAAa,IAAA,EAAwB;AAC5C,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AACvC;AAQA,IAAM,eAAA,GAAkB,uBAAA;AAGxB,IAAM,iBAAA,GAAoB,qDAAA;AAG1B,IAAM,kBAAA,GAAqB,wBAAA;AAapB,SAAS,gBAAA,CAAiB,OAAe,KAAA,EAAwB;AACtE,EAAA,MAAM,QAAA,GAAW,MAAM,IAAA,EAAK;AAE5B,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA,CAAE,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA,CAAE,IAAA,EAAK,IAAK,QAAA;AAC/F,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,KAAA,MAAW,KAAK,IAAA,CAAK,QAAA,CAAS,eAAe,CAAA,EAAG,IAAA,GAAO,EAAE,KAAA,IAAS,IAAA;AAClE,EAAA,MAAM,OAAA,GAAU,OAAO,CAAA,GAAI,IAAA,CAAK,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,IAAA,EAAK,GAAI,IAAA;AAGxD,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,IAAI,SAAS,OAAA,CAAQ,WAAA,OAAkB,KAAA,CAAM,WAAA,IAAe,OAAO,IAAA;AACnE,EAAA,OAAO,OAAA;AACT;AAUO,SAAS,cAAc,IAAA,EAAsB;AAClD,EAAA,MAAM,IAAA,GAAO,aAAa,IAAI,CAAA,CAAE,OAAO,CAAA,CAAA,KAAK,CAAA,KAAM,OAAO,CAAA,CAAE,GAAA,EAAI;AAC/D,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,OAAA,CAAQ,OAAA,EAAS,CAAA,CAAA,KAAK,CAAA,CAAE,WAAA,EAAa,CAAA,CACrC,IAAA,EAAK;AACV;AAOO,SAAS,cAAA,CAAe,MAAc,QAAA,EAAuC;AAClF,EAAA,MAAM,KAAA,GAAQ,aAAa,IAAI,CAAA;AAM/B,EAAA,MAAM,OAAA,GAAA,CAAW,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,KAAM,OAAA,GAAU,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,MAAA;AAAA,IACjF,OAAK,CAAA,KAAM;AAAA,GACb;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,MAAA,EAAQ,OAAO,QAAA;AAC5B,EAAA,OAAO,QAAQ,IAAA,CAAK,KAAK,CAAA,CAAE,OAAA,CAAQ,UAAU,GAAG,CAAA;AAClD;AASO,SAAS,aAAA,CAAc,IAAA,EAA0B,GAAA,GAAM,GAAA,EAAyB;AACrF,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,GAAG,EAAE,IAAA,EAAK;AAC5C,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,GAAA,EAAK,OAAO,IAAA;AAK/B,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAA,GAAM,CAAC,CAAA,CAAE,KAAA,CAAM,kBAAkB,CAAA,GAAI,CAAC,CAAA;AACrE,EAAA,IAAI,YAAY,QAAA,CAAS,MAAA,IAAU,EAAA,EAAI,OAAO,SAAS,IAAA,EAAK;AAgB5D,EAAA,OAAO,MAAA;AACT;AAUA,SAAS,iBAAA,CAAkB,QAA4B,KAAA,EAAwB;AAC7E,EAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AACpB,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAc,CAAA,CAAE,WAAA,GAAc,OAAA,CAAQ,aAAA,EAAe,GAAG,CAAA,CAAE,IAAA,EAAK;AAC7E,EAAA,MAAM,CAAA,GAAI,KAAK,MAAM,CAAA;AACrB,EAAA,MAAM,CAAA,GAAI,KAAK,KAAK,CAAA;AACpB,EAAA,IAAI,CAAC,GAAG,OAAO,IAAA;AACf,EAAA,IAAI,CAAA,CAAE,SAAS,CAAC,CAAA,IAAK,EAAE,QAAA,CAAS,CAAC,GAAG,OAAO,IAAA;AAE3C,EAAA,MAAM,aAAa,IAAI,GAAA,CAAI,CAAA,CAAE,KAAA,CAAM,GAAG,CAAC,CAAA;AACvC,EAAA,OAAO,CAAA,CAAE,MAAM,GAAG,CAAA,CAAE,MAAM,CAAA,CAAA,KAAK,UAAA,CAAW,GAAA,CAAI,CAAC,CAAC,CAAA;AAClD;AAcO,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,IAAI,CAAC,QAAA,CAAS,MAAA,EAAQ,OAAO,MAAA;AAC7B,EAAA,OAAO,SACJ,GAAA,CAAI,CAAA,CAAA,KAAM,MAAM,OAAA,GAAU,KAAA,GAAQ,CAAE,CAAA,CACpC,IAAA,CAAK,GAAG,CAAA,CACR,OAAA,CAAQ,kBAAkB,GAAG,CAAA,CAC7B,QAAQ,UAAA,EAAY,EAAE,EACtB,WAAA,EAAY;AACjB;AAYO,SAAS,iBAAA,CACd,OACA,KAAA,EACU;AACV,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,EAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAC,CAAA,EAAG,WAAA,CAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7D,EAAA,MAAM,WAAW,KAAA,CAAM,MAAA,CAAO,OAAK,CAAA,CAAE,QAAA,CAAS,OAAO,CAAC,CAAA;AACtD,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA;AAAA,IACxB,CAAA,GAAA,KAAO,CAAC,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA,IAAK,CAAC,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,KAAW,mBAAA,CAAoB,GAAA,EAAK,OAAO,CAAC;AAAA,GACvF;AACF;AAGA,SAAS,UAAU,IAAA,EAAsB;AACvC,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,OAAO,SAAS,MAAA,GAAS,CAAA,CAAA,EAAI,SAAS,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,GAAK,GAAA;AACtD;AAQO,SAAS,mBAAA,CAAoB,MAAc,OAAA,EAA0B;AAC1E,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,MAAM,KAAA,GAAQ,aAAa,OAAO,CAAA;AAClC,EAAA,IAAI,QAAA,CAAS,SAAS,OAAO,CAAA,IAAK,SAAS,MAAA,KAAW,KAAA,CAAM,QAAQ,OAAO,KAAA;AAC3E,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAC,GAAA,EAAK,CAAA,KAAM,QAAQ,OAAA,IAAW,GAAA,KAAQ,QAAA,CAAS,CAAC,CAAC,CAAA;AACvE;AAiBO,SAAS,aAAA,CACd,OACA,KAAA,EAC0C;AAC1C,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,EAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAC,CAAA,EAAG,WAAA,CAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7D,EAAA,MAAM,WAAW,KAAA,CAAM,MAAA,CAAO,OAAK,CAAA,CAAE,QAAA,CAAS,OAAO,CAAC,CAAA;AACtD,EAAA,MAAM,QAAkD,EAAC;AACzD,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAG;AACpC,IAAA,IAAI,KAAA,CAAM,IAAI,GAAG,CAAA,IAAK,CAAC,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AAC5C,IAAA,MAAM,UAAU,QAAA,CAAS,IAAA,CAAK,OAAK,mBAAA,CAAoB,GAAA,EAAK,CAAC,CAAC,CAAA;AAC9D,IAAA,IAAI,OAAA,QAAe,IAAA,CAAK,EAAE,MAAM,SAAA,CAAU,GAAG,CAAA,EAAG,OAAA,EAAS,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,KAAA;AACT;AAOO,IAAM,cAAA,GAAiB;AAYvB,SAAS,aAAa,IAAA,EAAsB;AACjD,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,OAAO,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA,EAAI,QAAA,CAAS,SAAS,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA,GAAI,MAAM,CAAA,IAAA,CAAA;AAC5E;AA0BO,SAAS,cAAA,CACd,IAAA,EACA,OAAA,GAAkE,EAAC,EACpC;AAC/B,EAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAA,GAAO,UAAU,IAAI,CAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,MAAA,CAAO,KAAA,IAAS,EAAC;AACvC,IAAA,IAAI,CAAC,KAAA,CAAM,IAAI,CAAA,IAAK,CAAC,MAAM,CAAA,EAAG,IAAI,CAAA,CAAA,CAAG,CAAA,EAAG,OAAO,MAAA;AAAA,EACjD;AACA,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,aAAa,IAAI,CAAA;AAAA,IACtB,KAAA,EAAOD,yBAAA;AAAA,IACP,MAAA,EAAQF,0BAAA;AAAA,IACR,IAAA,EAAM,YAAA;AAAA,IACN,GAAI,OAAA,CAAQ,GAAA,IAAO,EAAE,GAAA,EAAK,QAAQ,GAAA;AAAI,GACxC;AACF;AAgBO,SAAS,aAAA,CACd,IAAA,EACA,OAAA,GAA2E,EAAC,EACzD;AACnB,EAAA,MAAM,IAAA,GAAO,UAAU,IAAI,CAAA;AAC3B,EAAA,MAAM,GAAA,GAAMI,mCAAA,CAAmB,CAAA,EAAG,IAAA,KAAS,GAAA,GAAM,EAAA,GAAK,IAAI,CAAA,gBAAA,CAAA,EAAoBC,qCAAA,CAAqB,OAAA,CAAQ,aAAa,CAAC,CAAA;AACzH,EAAA,OAAO;AAAA,IACL,GAAA;AAAA,IACA,KAAA,EAAOH,yBAAA;AAAA,IACP,MAAA,EAAQF,0BAAA;AAAA,IACR,IAAA,EAAM,QAAQ,WAAA,IAAe,WAAA;AAAA,IAC7B,GAAI,OAAA,CAAQ,GAAA,IAAO,EAAE,GAAA,EAAK,QAAQ,GAAA;AAAI,GACxC;AACF;AAQO,SAAS,cAAA,CACd,IAAA,EACA,KAAA,EACA,KAAA,EACc;AACd,EAAA,MAAM,WACJ,IAAA,CAAK,KAAA,GAAQ,MAAM,IAAI,CAAA,IAAK,KAAK,KAAA,GAAQ,CAAA,EAAG,KAAA,CAAM,IAAI,GAAG,CAAA,IAAK,IAAA,CAAK,QAAQ,WAAA,CAAY,KAAA,CAAM,IAAI,CAAC,CAAA;AACpG,EAAA,MAAM,MAAA,GAAS,MAAM,IAAA,KAAS,GAAA;AAY9B,EAAA,MAAM,OAAA,GAAwB,SAC1B,EAAE,GAAG,MAAM,KAAA,EAAO,MAAA,MACjB,MAAM;AACL,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,GAAQ,gBAAA,CAAiB,KAAA,CAAM,OAAO,KAAK,CAAA,GAAI,aAAA,CAAc,KAAA,CAAM,IAAI,CAAA;AAC3F,IAAA,MAAM,SAAS,cAAA,CAAe,KAAA,CAAM,IAAA,EAAM,IAAA,CAAK,QAAQ,MAAM,CAAA;AAC7D,IAAA,OAAO;AAAA,MACL,GAAG,IAAA;AAAA;AAAA,MAEH,KAAA,EAAO,MAAA;AAAA,MACP,OAAA,EAAS;AAAA,QACP,GAAG,IAAA,CAAK,OAAA;AAAA,QACR,MAAA,EAAQ,iBAAA,CAAkB,MAAA,EAAQ,KAAK,IAAI,MAAA,GAAY,MAAA;AAAA,QACvD,KAAA;AAAA,QACA,UAAU,aAAA,CAAc,KAAA,CAAM,WAAW,CAAA,IAAK,KAAK,OAAA,CAAQ;AAAA;AAC7D,KACF;AAAA,EACF,CAAA,GAAG;AAEP,EAAA,IAAI,CAAC,UAAU,OAAO,OAAA;AACtB,EAAA,OAAO;AAAA,IACL,GAAG,OAAA;AAAA,IACH,GAAG,QAAA;AAAA,IACH,SAAS,EAAE,GAAG,QAAQ,OAAA,EAAS,GAAG,SAAS,OAAA;AAAQ,GACrD;AACF","file":"chunk-OEW2LFOV.js","sourcesContent":["/**\n * OG card HTML template builder.\n *\n * Turns an OgCardConfig into a self-contained 1200x630 HTML document rendered\n * by headless Chrome. Layout rules baked in from the Ludlow card:\n * - content held inside the middle ~80% (platforms crop toward 2:1 and the\n * bottom bar is the first casualty)\n * - script/hand faces never below 40px — under that they turn to mush at\n * iMessage thumbnail width (~300px)\n * - the plate + title must survive alone if everything else is cropped\n */\n\nimport type { OgCardConfig, OgContent, OgTheme } from './config'\nimport { barTextColor } from './contrast'\nimport {\n OG_WIDTH,\n OG_HEIGHT,\n TITLE_MAX_PX,\n TITLE_MIN_PX,\n OG_REPORT_ATTR,\n} from './config'\n\n/**\n * The in-page fitter. Runs inside the render, after webfonts settle, and does\n * the two things a Node-side template cannot:\n *\n * 1. FITS THE TITLE. The h1 opens at TITLE_MAX_PX and steps down until the\n * copy column fits the frame. Before this the size was fixed, so title\n * length silently decided whether a card worked — a long one did not\n * shrink, it pushed the kicker off the top and the subtitle under the\n * bottom bar, and the CLI still reported success.\n * 2. MEASURES WHAT IS LEFT. Anything still clipped is written to the report\n * attribute, which `sonor-setup og` reads back through --dump-dom and\n * turns into a failure. Real measurement, not a character-count estimate.\n *\n * Fonts matter: measuring before `document.fonts.ready` measures the fallback\n * face and produces a size that is wrong for the face that actually renders.\n */\nfunction fitScript(): string {\n return `<script>\n(async function () {\n var report = { fits: true, titlePx: ${TITLE_MAX_PX}, issues: [] };\n function push(msg) { report.fits = false; report.issues.push(msg); }\n try {\n try { await document.fonts.ready; } catch (e) {}\n var h1 = document.querySelector('h1');\n var copy = document.querySelector('.copy');\n var wrap = document.querySelector('.wrap');\n var bar = document.querySelector('.bar');\n\n if (h1 && copy && wrap) {\n var cs = getComputedStyle(wrap);\n var avail = wrap.clientHeight - parseFloat(cs.paddingTop || '0') - parseFloat(cs.paddingBottom || '0');\n\n // Fit BOTH axes. Height alone is not enough: a single long word\n // (\"WORKBENCHES\") cannot wrap, so it keeps overflowing the column\n // sideways at a size that fits vertically. Shrinking for either\n // condition is what makes an arbitrary managed title safe to render.\n var tooTall = function () { return copy.scrollHeight > avail; };\n var tooWide = function () { return h1.scrollWidth > copy.clientWidth + 1; };\n var size = ${TITLE_MAX_PX};\n while (size > ${TITLE_MIN_PX} && (tooTall() || tooWide())) {\n size -= 2;\n h1.style.fontSize = size + 'px';\n }\n report.titlePx = size;\n\n if (tooTall()) {\n push('title/subtitle block overflows the card by ' + Math.ceil(copy.scrollHeight - avail) +\n 'px even at the ' + ${TITLE_MIN_PX} + 'px legibility floor - shorten content.title or content.subtitle');\n }\n if (tooWide()) {\n push('the longest title word still overflows the copy column by ' + Math.ceil(h1.scrollWidth - copy.clientWidth) +\n 'px at the ' + ${TITLE_MIN_PX} + 'px floor - shorten it or add a line break');\n }\n }\n\n // The bar is checked on BOTH axes. Width alone missed a real failure: a\n // long segment wrapped INSIDE its span, so scrollWidth never exceeded\n // clientWidth while the text grew past the bar's fixed height and spilled\n // over the subtitle. Segments are nowrap now, which turns that into\n // horizontal overflow the width check sees, and the height check is the\n // belt-and-braces for anything else that grows the bar.\n if (bar) {\n if (bar.scrollWidth > bar.clientWidth + 1) {\n push('bottom bar content overflows by ' + Math.ceil(bar.scrollWidth - bar.clientWidth) +\n 'px - drop a content.bar segment or shorten one');\n }\n if (bar.scrollHeight > bar.clientHeight + 1) {\n push('bottom bar content is ' + Math.ceil(bar.scrollHeight - bar.clientHeight) +\n 'px taller than the bar - a segment is wrapping; shorten it');\n }\n }\n\n // Nothing may sit outside the frame platforms actually crop to.\n var named = [['kicker', '.kicker'], ['title', 'h1'], ['subtitle', '.sub'], ['hand', '.hand'], ['bar', '.bar']];\n for (var i = 0; i < named.length; i++) {\n var el = document.querySelector(named[i][1]);\n if (!el) continue;\n var r = el.getBoundingClientRect();\n if (r.top < -1 || r.bottom > ${OG_HEIGHT} + 1) {\n push(named[i][0] + ' sits outside the ' + ${OG_HEIGHT} + 'px frame (top ' +\n Math.round(r.top) + ', bottom ' + Math.round(r.bottom) + ')');\n }\n }\n } catch (e) {\n push('fitter failed: ' + (e && e.message ? e.message : String(e)));\n }\n document.documentElement.setAttribute('${OG_REPORT_ATTR}', JSON.stringify(report));\n})();\n</script>`\n}\n\nfunction esc(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction fontLink(config: OgCardConfig): string {\n const fonts = config.fonts?.length\n ? config.fonts\n : [{ family: 'Barlow Condensed', weights: [800] }]\n const families = fonts\n .map(f => `family=${f.family.replace(/ /g, '+')}:wght@${(f.weights || [700]).join(';')}`)\n .join('&')\n return `<link href=\"https://fonts.googleapis.com/css2?${families}&display=block\" rel=\"stylesheet\">`\n}\n\nfunction displayFamily(config: OgCardConfig): string {\n return config.fonts?.[0]?.family || 'Barlow Condensed'\n}\n\nfunction handFamily(config: OgCardConfig): string {\n const fonts = config.fonts || []\n return fonts.length > 1 ? fonts[fonts.length - 1].family : displayFamily(config)\n}\n\nfunction muted(theme: OgTheme): string {\n return theme.muted || theme.text\n}\n\nfunction backgroundLayers(config: OgCardConfig): string {\n if (!config.background) return ''\n const { image, blur = 0, scrim } = config.background\n return `\n <div class=\"bg\"><img src=\"${esc(image)}\" style=\"filter:blur(${blur}px)\"></div>\n ${scrim ? `<div class=\"scrim\" style=\"background:${esc(scrim)}\"></div>` : ''}`\n}\n\nfunction barHtml(content: OgContent, theme: OgTheme): string {\n if (!content.bar?.length) return ''\n const segs = content.bar\n .map(s => `<span>${esc(s)}</span>`)\n .join('<span class=\"dot\"></span>')\n return `<div class=\"bar\" style=\"background:${esc(theme.accent)}\">${segs}</div>`\n}\n\nfunction copyHtml(config: OgCardConfig): string {\n const { content, theme } = config\n const titleHtml = esc(content.title).replace(/\\n/g, '<br>')\n // In `split` the photo owns the right pane, so a configured logo has nowhere\n // to go as a plate. It used to be dropped silently. Render it small above the\n // kicker instead: the brand mark still appears, and the plate invariant that\n // split has no `.plate` is preserved.\n const mark =\n config.logo && (config.layout ?? (config.logo ? 'plate-left' : 'banner')) === 'split'\n ? `<img class=\"mark\" src=\"${esc(config.logo)}\">`\n : ''\n return `\n <div class=\"copy\">\n ${mark}\n ${content.kicker ? `<div class=\"kicker\">${esc(content.kicker)}</div>` : ''}\n <h1>${titleHtml}</h1>\n ${content.subtitle ? `<div class=\"sub\" style=\"color:${esc(muted(theme))}\">${esc(content.subtitle)}</div>` : ''}\n ${content.hand ? `<div class=\"hand\" style=\"font-family:'${esc(handFamily(config))}',cursive\">${esc(content.hand)}</div>` : ''}\n </div>`\n}\n\nexport function buildOgHtml(config: OgCardConfig): string {\n if (config.html) return config.html\n if (config.template) {\n return config.template(config, {\n esc,\n fontLink: fontLink(config),\n width: OG_WIDTH,\n height: OG_HEIGHT,\n })\n }\n\n const { theme, logo, layout = logo ? 'plate-left' : 'banner' } = config\n const display = displayFamily(config)\n\n const plate = logo\n ? `<div class=\"plate\" style=\"background:${esc(theme.surface)}\"><img src=\"${esc(logo)}\"></div>`\n : ''\n\n const photo = config.photo\n ? `<div class=\"photo\" style=\"flex-basis:${config.photo.width ?? 430}px;border-radius:${config.photo.radius ?? 24}px\"><img src=\"${esc(config.photo.src)}\"></div>`\n : ''\n\n const body =\n layout === 'centered'\n ? `<div class=\"wrap centered\">${plate}${copyHtml(config)}</div>`\n : layout === 'banner'\n ? `<div class=\"wrap banner\">${copyHtml(config)}</div>`\n : layout === 'split'\n ? `<div class=\"wrap\">${copyHtml(config)}${photo}</div>`\n : `<div class=\"wrap\">${photo || plate}${copyHtml(config)}</div>`\n\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n${fontLink(config)}\n<style>\n * { margin:0; padding:0; box-sizing:border-box; }\n html,body { width:${OG_WIDTH}px; height:${OG_HEIGHT}px; overflow:hidden; }\n body { position:relative; background:${theme.bg}; font-family:'${display}',sans-serif; color:${theme.text}; }\n .bg { position:absolute; inset:0; }\n .bg img { width:100%; height:100%; object-fit:cover; transform:scale(1.05); }\n .scrim { position:absolute; inset:0; }\n .wrap { position:relative; height:100%; display:flex; align-items:center; gap:52px; padding:0 68px ${config.content.bar?.length ? '74px' : '0'}; }\n .wrap.centered { flex-direction:column; justify-content:center; text-align:center; gap:28px; }\n .wrap.banner { padding-left:90px; }\n .plate { border-radius:28px; padding:26px; flex:0 0 auto; }\n .plate img { display:block; width:250px; height:auto; }\n .centered .plate img { width:180px; }\n .photo { flex:0 0 auto; height:76%; overflow:hidden; box-shadow:0 8px 40px rgba(0,0,0,.25); }\n .photo img { width:100%; height:100%; object-fit:cover; display:block; }\n .copy { flex:1 1 auto; min-width:0; }\n /* split-layout brand mark: the photo owns the right pane, so the logo rides\n above the kicker rather than being dropped. */\n .mark { display:block; height:44px; width:auto; margin-bottom:20px; }\n .kicker { font-weight:800; text-transform:uppercase; letter-spacing:.22em; font-size:24px; color:${theme.accent}; margin-bottom:14px; }\n /* Opening size only — the in-page fitter steps this down to fit the frame. */\n h1 { font-weight:800; text-transform:uppercase; font-size:${TITLE_MAX_PX}px; line-height:.9; letter-spacing:-.01em; }\n .sub { margin-top:22px; font-size:29px; font-weight:500; line-height:1.32; }\n /* Never below 40px: script faces are illegible at thumbnail width under that. */\n .hand { font-size:42px; margin-top:14px; transform:rotate(-1.5deg); display:inline-block; }\n .bar { position:absolute; left:0; right:0; bottom:0; height:74px; display:flex; align-items:center; justify-content:center; gap:26px;\n font-weight:800; text-transform:uppercase; letter-spacing:.1em; font-size:31px; color:${barTextColor(theme)}; }\n /* nowrap so an over-long segment overflows the bar sideways, where the\n fitter's width check sees it, instead of silently wrapping to a second\n line and spilling over the copy above. */\n .bar > span { white-space:nowrap; }\n .bar .dot { width:10px; height:10px; border-radius:50%; background:currentColor; opacity:.55; flex:0 0 auto; }\n</style></head>\n<body>\n${backgroundLayers(config)}\n${body}\n${barHtml(config.content, theme)}\n${fitScript()}\n</body></html>`\n}\n","/**\n * @sonordev/site-kit/og — per-page card derivation.\n *\n * The site-wide card answers \"what is this site\". A per-page card answers\n * \"what is THIS page\", which is what someone pasting a deep link is actually\n * sharing. Every static route gets one by default.\n *\n * Copy comes from Sonor's managed metadata when a key is configured, because\n * that is the same copy the page's <title> and description already use — the\n * card and the SERP entry then say the same thing. Without a key (or when a\n * lookup fails) the path itself is titled, which is worse copy but still a\n * real, page-specific card rather than the site default.\n *\n * Everything else — theme, fonts, logo, photo, bar — is inherited from the\n * site's own og.config.ts, so the whole set reads as one family.\n */\n\nimport type { OgCardConfig } from './config'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\n// Single source of truth for Next's trailingSlash URL shape (pure string logic).\nimport { applyTrailingSlash, resolveTrailingSlash } from '../sitemap/shared'\n\n/** A route to build a card for. */\nexport interface OgPageRoute {\n /** URL path, e.g. `/services/garage`. */\n path: string\n /** Managed title, if Sonor had one. */\n title?: string\n /** Managed meta description, if Sonor had one. */\n description?: string\n}\n\n/**\n * The stand-in for a dynamic segment in a route path.\n *\n * A dynamic route has no single URL, so its card is keyed by a pattern with one\n * `*` per dynamic level: `/services/[slug]` is `/services/*`, and each further\n * dynamic level adds another star. The depth is what keeps those patterns\n * apart — they are different pages and deserve different cards.\n *\n * (Nested patterns are spelled out in prose rather than written literally in\n * these block comments: a star followed by a slash closes the comment. See\n * src/og/README.md.)\n */\nexport const DYNAMIC = '*'\n\n/** `/services/garage/` -> `['services', 'garage']`. */\nfunction pathSegments(path: string): string[] {\n return path.split('/').filter(Boolean)\n}\n\n/**\n * A separator between a title and its brand: a pipe with or without spaces\n * around it, or a dash (`-`, `–`, `—`) with spaces on both sides. A dash with\n * no spaces is part of a word (\"Walk-In Closets\"), never a separator; the old\n * pattern treated it as one and cut that title to \"Walk\".\n */\nconst TITLE_SEPARATOR = /\\s*\\|\\s*|\\s+[–—-]\\s+/g\n\n/** `, abbeyglenapts.com` at the very end: a domain appended as if it were the brand. */\nconst TRAILING_HOSTNAME = /,\\s*(?:https?:\\/\\/)?(?:[a-z0-9-]+\\.)+[a-z]{2,}\\/?$/i\n\n/** A separator the title ends on, with nothing after it (\"Contact Us |\"). */\nconst DANGLING_SEPARATOR = /(?:\\s*\\||\\s+[–—-])\\s*$/\n\n/**\n * Trailing brand suffix on a managed title (` | Acme`, ` - Acme`, ` — Acme`).\n * Managed titles carry the brand because a SERP entry needs it; a card does\n * not — the logo, the photo and the bar are already saying whose it is, and\n * every character spent on the brand is one not spent on the page.\n *\n * Also drops the two broken suffixes managed titles turn up with, both found\n * on 2026-09-15 and each worked around by hand in a site's `cards`: a\n * dangling separator with no brand after it (reinhart's \"About Us |\", Green\n * Bay's), and the domain after a comma (\"Privacy Policy, abbeyglenapts.com\").\n */\nexport function stripBrandSuffix(title: string, brand?: string): string {\n const original = title.trim()\n // The junk first, so \"About | Acme |\" still loses its brand after it.\n const base = original.replace(DANGLING_SEPARATOR, '').replace(TRAILING_HOSTNAME, '').trim() || original\n let last = -1\n for (const m of base.matchAll(TITLE_SEPARATOR)) last = m.index ?? last\n const cleaned = last > 0 ? base.slice(0, last).trim() : base\n // Only accept the strip if something survives it, so a title that IS the\n // brand (\"Acme\") is left alone rather than reduced to nothing.\n if (!cleaned) return base\n if (brand && cleaned.toLowerCase() === brand.toLowerCase()) return base\n return cleaned\n}\n\n/**\n * `/services/walk-in-closets` -> `Walk In Closets`. The no-managed-data path.\n *\n * `*` segments (a dynamic route's card, `/services/*`) are skipped rather than\n * titled: one file covers every param, so the honest title is the section the\n * params belong to. Without this, an unmanaged dynamic segment rendered a card\n * whose headline was literally `*`.\n */\nexport function titleFromPath(path: string): string {\n const last = pathSegments(path).filter(s => s !== DYNAMIC).pop()\n if (!last) return 'Home'\n return last\n .replace(/[-_]+/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase())\n .trim()\n}\n\n/**\n * The kicker for a page card: its parent section, so a deep link reads\n * \"SERVICES / Walk In Closets\" rather than repeating the site name on every\n * card. Falls back to the base card's kicker at the top level.\n */\nexport function kickerFromPath(path: string, fallback?: string): string | undefined {\n const parts = pathSegments(path)\n // When the leaf is a `*`, the stars ARE the leaf, so the section is every\n // static segment — `/services/*` and `/services/*/*` both read \"services\",\n // never \"services / *\". A static leaf under a dynamic segment\n // (`/properties/*/about`) is the page itself, so the section is what's\n // above it, minus the stars: \"properties\".\n const section = (parts[parts.length - 1] === DYNAMIC ? parts : parts.slice(0, -1)).filter(\n s => s !== DYNAMIC,\n )\n if (!section.length) return fallback\n return section.join(' / ').replace(/[-_]+/g, ' ')\n}\n\n/**\n * Keep a card subtitle to two readable lines, never a full meta description.\n *\n * A meta description is written for a SERP row and runs to ~155 characters; at\n * card size that is four lines crowding the bottom bar. This trims to the first\n * sentence when there is one, and otherwise cuts on a word boundary.\n */\nexport function clampSubtitle(text: string | undefined, max = 100): string | undefined {\n if (!text) return undefined\n const flat = text.replace(/\\s+/g, ' ').trim()\n if (!flat) return undefined\n if (flat.length <= max) return flat\n\n // A whole sentence reads as written rather than as truncated. The floor is\n // absolute rather than a ratio of `max` (a ratio rejected a clean\n // 48-character sentence) — but not so low it accepts a stray \"Inc.\".\n const sentence = flat.slice(0, max + 1).match(/^.*[.!?](?=\\s|$)/)?.[0]\n if (sentence && sentence.length >= 30) return sentence.trim()\n\n /*\n * Otherwise: refuse.\n *\n * A managed description is 150+ characters of prose written for a SERP row,\n * and at card size NO truncation of it looks deliberate. Both attempts\n * proved it on real cards: a word cut gave \"across Cincinnati and…\", and\n * cutting at the last comma gave \"home offices designed, built\" — worse,\n * because with no ellipsis it looks complete and merely ungrammatical.\n *\n * Returning undefined lets the caller fall back to the site's own subtitle,\n * which is short, hand-written and always fits. A clean generic line beats a\n * mangled specific one, and the TITLE is already page-specific — which is\n * the signal that actually matters on a card.\n */\n return undefined\n}\n\n/**\n * Is the kicker already saying what the title says?\n *\n * Managed titles are written for search and usually carry the section and the\n * city, so the home page came out as kicker \"Custom Closets · Cincinnati\" above\n * title \"CUSTOM CLOSETS CINCINNATI TRI-STATE\" — a stutter that also cost a line\n * of the frame. When either contains the other, the title wins.\n */\nfunction kickerIsRedundant(kicker: string | undefined, title: string): boolean {\n if (!kicker) return false\n const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n const k = norm(kicker)\n const t = norm(title)\n if (!k) return true\n if (t.includes(k) || k.includes(t)) return true\n // Or every word of the kicker already appears in the title.\n const titleWords = new Set(t.split(' '))\n return k.split(' ').every(w => titleWords.has(w))\n}\n\n/**\n * File-safe name for a route's card: `/services/garage` -> `services-garage`,\n * `/` -> `home`. Deterministic, so the generator and anything reading the\n * output agree without a manifest.\n *\n * A `*` (dynamic) segment becomes `any`, one per level: `/services/*` is\n * `services-any`, and a second dynamic level is `services-any-any`. The naive\n * punctuation strip collapsed every one of those to `services` — the SAME slug\n * as the real `/services` route — so a `cards` override keyed by slug could\n * silently land on the wrong card. (A literal `/services/any` route would still collide;\n * key those by path, which is always unambiguous.)\n */\nexport function slugForPath(path: string): string {\n const segments = pathSegments(path)\n if (!segments.length) return 'home'\n return segments\n .map(s => (s === DYNAMIC ? 'any' : s))\n .join('-')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase()\n}\n\n/**\n * The `cards` keys in og.config.ts that match none of the routes being\n * rendered.\n *\n * `pageCardConfig` looks an override up by exact path or exact slug and\n * silently derives the card when neither hits, so a mistyped or stale key — or\n * one written for a route shape the generator does not produce (a two-star\n * services pattern, before nested dynamic segments got their own key) — cost a\n * hand-written card with nothing said about it. The caller reports these.\n */\nexport function unmatchedCardKeys(\n cards: Record<string, unknown> | undefined,\n paths: string[],\n): string[] {\n if (!cards) return []\n const known = new Set(paths.flatMap(p => [p, slugForPath(p)]))\n const patterns = paths.filter(p => p.includes(DYNAMIC))\n return Object.keys(cards).filter(\n key => !known.has(key) && !patterns.some(pattern => matchesRoutePattern(key, pattern)),\n )\n}\n\n/** `/floor-plans/1-bedroom/` -> `/floor-plans/1-bedroom`. */\nfunction bareRoute(path: string): string {\n const segments = pathSegments(path)\n return segments.length ? `/${segments.join('/')}` : '/'\n}\n\n/**\n * Does a concrete URL fall under a dynamic route pattern? One `*` matches\n * exactly one segment: `/properties/tall-pines/about` is under\n * `/properties/*` + `/about`, and `/floor-plans/1-bedroom` under\n * `/floor-plans/*`. A key that is itself a pattern matches nothing.\n */\nexport function matchesRoutePattern(path: string, pattern: string): boolean {\n const concrete = pathSegments(path)\n const shape = pathSegments(pattern)\n if (concrete.includes(DYNAMIC) || concrete.length !== shape.length) return false\n return shape.every((seg, i) => seg === DYNAMIC || seg === concrete[i])\n}\n\n/**\n * The `cards` keys that name one page under a dynamic route: a card per\n * floor plan, per community, per sub-page of a community. Keyed by the URL\n * itself (`/floor-plans/1-bedroom`), which any site can build from the same\n * data its `generateStaticParams` reads.\n *\n * A static file in a dynamic segment covers every param of the route, so\n * these can't be `opengraph-image` files. `sonor-setup og` renders each one to\n * `public/_og/` (see paramCardUrl), and the page declares it with\n * `paramCardImage`. The MDG property sites needed a card per floor plan and\n * per community and rendered them with their own script until this existed.\n *\n * Returns each key with the pattern it falls under. Keys that are a real\n * static route or a pattern are left to the route cards.\n */\nexport function paramCardKeys(\n cards: Record<string, unknown> | undefined,\n paths: string[],\n): Array<{ path: string; pattern: string }> {\n if (!cards) return []\n const known = new Set(paths.flatMap(p => [p, slugForPath(p)]))\n const patterns = paths.filter(p => p.includes(DYNAMIC))\n const found: Array<{ path: string; pattern: string }> = []\n for (const key of Object.keys(cards)) {\n if (known.has(key) || !key.startsWith('/')) continue\n const pattern = patterns.find(p => matchesRoutePattern(key, p))\n if (pattern) found.push({ path: bareRoute(key), pattern })\n }\n return found\n}\n\n/**\n * Public directory (under `public/`) that `sonor-setup og` owns for per-URL\n * cards. Generated; the command clears it on every run, so keep nothing else\n * in it.\n */\nexport const PARAM_CARD_DIR = '_og'\n\n/**\n * Where the per-URL card for a page is served: `/floor-plans/1-bedroom` ->\n * `/_og/floor-plans/1-bedroom.jpg`. Single source of truth for the file the\n * CLI writes and the URL `paramCardImage` declares.\n *\n * It ends in `.jpg` on purpose. Next serves a code-based `opengraph-image`\n * route without an extension, and `trailingSlash: true` answers that URL with\n * a 308 before the card, which some preview bots don't follow. A static file\n * with an extension is never redirected.\n */\nexport function paramCardUrl(path: string): string {\n const segments = pathSegments(path)\n return `/${PARAM_CARD_DIR}/${segments.length ? segments.join('/') : 'home'}.jpg`\n}\n\n/** An `openGraph.images` entry. */\nexport interface OgImageDescriptor {\n url: string\n width: number\n height: number\n type: string\n alt?: string\n}\n\n/**\n * The page's per-URL card, for its metadata:\n *\n * ```ts\n * const card = paramCardImage(`/floor-plans/${slug}`, { config: ogConfig })\n * return {\n * openGraph: { ...(card && { images: [card] }) },\n * twitter: { card: 'summary_large_image', ...(card && { images: [card.url] }) },\n * }\n * ```\n *\n * Pass `config` (the site's og.config) and it answers undefined for a page\n * with no `cards` entry, so the page keeps its segment's static card. Without\n * `config` it always answers, so only call it for URLs you gave a card.\n */\nexport function paramCardImage(\n path: string,\n options: { config?: Pick<OgCardConfig, 'cards'>; alt?: string } = {},\n): OgImageDescriptor | undefined {\n if (options.config) {\n const bare = bareRoute(path)\n const cards = options.config.cards ?? {}\n if (!cards[bare] && !cards[`${bare}/`]) return undefined\n }\n return {\n url: paramCardUrl(path),\n width: OG_WIDTH,\n height: OG_HEIGHT,\n type: 'image/jpeg',\n ...(options.alt && { alt: options.alt }),\n }\n}\n\n/**\n * A code-based card (`opengraph-image.tsx`) declared at the URL the site\n * actually serves it from.\n *\n * Next wires such a card in as `<path>/opengraph-image`, with no extension.\n * With `trailingSlash: true` that URL 308-redirects to the slashed one, so\n * every share preview starts with a redirect, and bots that don't follow one\n * show no card (the MDG property sites, 2026-09-15). Declaring the slashed URL\n * gets a 200 first time. Declared images beat the file convention, so this\n * replaces Next's own entry rather than adding a second.\n *\n * `trailingSlash` defaults to the site's next.config. Without it, this is the\n * URL Next would have used anyway.\n */\nexport function codeCardImage(\n path: string,\n options: { trailingSlash?: boolean; alt?: string; contentType?: string } = {},\n): OgImageDescriptor {\n const base = bareRoute(path)\n const url = applyTrailingSlash(`${base === '/' ? '' : base}/opengraph-image`, resolveTrailingSlash(options.trailingSlash))\n return {\n url,\n width: OG_WIDTH,\n height: OG_HEIGHT,\n type: options.contentType ?? 'image/png',\n ...(options.alt && { alt: options.alt }),\n }\n}\n\n/**\n * Build a page's card config by layering its copy over the site's card.\n *\n * `cards` in og.config.ts wins over everything derived here, so a site can\n * hand-write the two or three cards that matter and let the rest be generated.\n */\nexport function pageCardConfig(\n base: OgCardConfig,\n route: OgPageRoute,\n brand?: string,\n): OgCardConfig {\n const override =\n base.cards?.[route.path] ?? base.cards?.[`${route.path}/`] ?? base.cards?.[slugForPath(route.path)]\n const isHome = route.path === '/'\n\n /*\n * The home page keeps the card the site actually wrote.\n *\n * og.config.ts `content` IS the home page's card — it is the one route whose\n * subject is the whole site, and it is hand-written. Deriving it from the\n * managed <title> instead replaced a crafted headline (\"Built by brothers\")\n * with the SEO string (\"Custom Closets Cincinnati Tri-State\"), which repeated\n * the kicker and ran to four lines. Every other route has no hand-written\n * card, so deriving is a clear gain there.\n */\n const derived: OgCardConfig = isHome\n ? { ...base, cards: undefined }\n : (() => {\n const title = route.title ? stripBrandSuffix(route.title, brand) : titleFromPath(route.path)\n const kicker = kickerFromPath(route.path, base.content.kicker)\n return {\n ...base,\n // `cards` is a build-time instruction, not part of a rendered card.\n cards: undefined,\n content: {\n ...base.content,\n kicker: kickerIsRedundant(kicker, title) ? undefined : kicker,\n title,\n subtitle: clampSubtitle(route.description) ?? base.content.subtitle,\n },\n }\n })()\n\n if (!override) return derived\n return {\n ...derived,\n ...override,\n content: { ...derived.content, ...override.content },\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/og/template.ts","../src/og/pages.ts"],"names":["TITLE_MAX_PX","TITLE_MIN_PX","OG_HEIGHT","OG_REPORT_ATTR","OG_WIDTH","barTextColor","applyTrailingSlash","resolveTrailingSlash"],"mappings":"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","file":"chunk-RLUNEEGM.js","sourcesContent":["/**\n * OG card HTML template builder.\n *\n * Turns an OgCardConfig into a self-contained 1200x630 HTML document rendered\n * by headless Chrome. Layout rules baked in from the Ludlow card:\n * - content held inside the middle ~80% (platforms crop toward 2:1 and the\n * bottom bar is the first casualty)\n * - script/hand faces never below 40px — under that they turn to mush at\n * iMessage thumbnail width (~300px)\n * - the plate + title must survive alone if everything else is cropped\n */\n\nimport type { OgCardConfig, OgContent, OgTheme } from './config'\nimport { barTextColor } from './contrast'\nimport {\n OG_WIDTH,\n OG_HEIGHT,\n TITLE_MAX_PX,\n TITLE_MIN_PX,\n OG_REPORT_ATTR,\n} from './config'\n\n/**\n * The in-page fitter. Runs inside the render, after webfonts settle, and does\n * the two things a Node-side template cannot:\n *\n * 1. FITS THE TITLE. The h1 opens at TITLE_MAX_PX and steps down until the\n * copy column fits the frame. Before this the size was fixed, so title\n * length silently decided whether a card worked — a long one did not\n * shrink, it pushed the kicker off the top and the subtitle under the\n * bottom bar, and the CLI still reported success.\n * 2. MEASURES WHAT IS LEFT. Anything still clipped is written to the report\n * attribute, which `sonor-setup og` reads back through --dump-dom and\n * turns into a failure. Real measurement, not a character-count estimate.\n *\n * Fonts matter: measuring before `document.fonts.ready` measures the fallback\n * face and produces a size that is wrong for the face that actually renders.\n */\nfunction fitScript(): string {\n return `<script>\n(async function () {\n var report = { fits: true, titlePx: ${TITLE_MAX_PX}, issues: [] };\n function push(msg) { report.fits = false; report.issues.push(msg); }\n try {\n try { await document.fonts.ready; } catch (e) {}\n var h1 = document.querySelector('h1');\n var copy = document.querySelector('.copy');\n var wrap = document.querySelector('.wrap');\n var bar = document.querySelector('.bar');\n\n if (h1 && copy && wrap) {\n var cs = getComputedStyle(wrap);\n var avail = wrap.clientHeight - parseFloat(cs.paddingTop || '0') - parseFloat(cs.paddingBottom || '0');\n\n // Fit BOTH axes. Height alone is not enough: a single long word\n // (\"WORKBENCHES\") cannot wrap, so it keeps overflowing the column\n // sideways at a size that fits vertically. Shrinking for either\n // condition is what makes an arbitrary managed title safe to render.\n var tooTall = function () { return copy.scrollHeight > avail; };\n var tooWide = function () { return h1.scrollWidth > copy.clientWidth + 1; };\n var size = ${TITLE_MAX_PX};\n while (size > ${TITLE_MIN_PX} && (tooTall() || tooWide())) {\n size -= 2;\n h1.style.fontSize = size + 'px';\n }\n report.titlePx = size;\n\n if (tooTall()) {\n push('title/subtitle block overflows the card by ' + Math.ceil(copy.scrollHeight - avail) +\n 'px even at the ' + ${TITLE_MIN_PX} + 'px legibility floor - shorten content.title or content.subtitle');\n }\n if (tooWide()) {\n push('the longest title word still overflows the copy column by ' + Math.ceil(h1.scrollWidth - copy.clientWidth) +\n 'px at the ' + ${TITLE_MIN_PX} + 'px floor - shorten it or add a line break');\n }\n }\n\n // The bar is checked on BOTH axes. Width alone missed a real failure: a\n // long segment wrapped INSIDE its span, so scrollWidth never exceeded\n // clientWidth while the text grew past the bar's fixed height and spilled\n // over the subtitle. Segments are nowrap now, which turns that into\n // horizontal overflow the width check sees, and the height check is the\n // belt-and-braces for anything else that grows the bar.\n if (bar) {\n if (bar.scrollWidth > bar.clientWidth + 1) {\n push('bottom bar content overflows by ' + Math.ceil(bar.scrollWidth - bar.clientWidth) +\n 'px - drop a content.bar segment or shorten one');\n }\n if (bar.scrollHeight > bar.clientHeight + 1) {\n push('bottom bar content is ' + Math.ceil(bar.scrollHeight - bar.clientHeight) +\n 'px taller than the bar - a segment is wrapping; shorten it');\n }\n }\n\n // Nothing may sit outside the frame platforms actually crop to.\n var named = [['kicker', '.kicker'], ['title', 'h1'], ['subtitle', '.sub'], ['hand', '.hand'], ['bar', '.bar']];\n for (var i = 0; i < named.length; i++) {\n var el = document.querySelector(named[i][1]);\n if (!el) continue;\n var r = el.getBoundingClientRect();\n if (r.top < -1 || r.bottom > ${OG_HEIGHT} + 1) {\n push(named[i][0] + ' sits outside the ' + ${OG_HEIGHT} + 'px frame (top ' +\n Math.round(r.top) + ', bottom ' + Math.round(r.bottom) + ')');\n }\n }\n } catch (e) {\n push('fitter failed: ' + (e && e.message ? e.message : String(e)));\n }\n document.documentElement.setAttribute('${OG_REPORT_ATTR}', JSON.stringify(report));\n})();\n</script>`\n}\n\nfunction esc(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction fontLink(config: OgCardConfig): string {\n const fonts = config.fonts?.length\n ? config.fonts\n : [{ family: 'Barlow Condensed', weights: [800] }]\n const families = fonts\n .map(f => `family=${f.family.replace(/ /g, '+')}:wght@${(f.weights || [700]).join(';')}`)\n .join('&')\n return `<link href=\"https://fonts.googleapis.com/css2?${families}&display=block\" rel=\"stylesheet\">`\n}\n\nfunction displayFamily(config: OgCardConfig): string {\n return config.fonts?.[0]?.family || 'Barlow Condensed'\n}\n\nfunction handFamily(config: OgCardConfig): string {\n const fonts = config.fonts || []\n return fonts.length > 1 ? fonts[fonts.length - 1].family : displayFamily(config)\n}\n\nfunction muted(theme: OgTheme): string {\n return theme.muted || theme.text\n}\n\nfunction backgroundLayers(config: OgCardConfig): string {\n if (!config.background) return ''\n const { image, blur = 0, scrim } = config.background\n return `\n <div class=\"bg\"><img src=\"${esc(image)}\" style=\"filter:blur(${blur}px)\"></div>\n ${scrim ? `<div class=\"scrim\" style=\"background:${esc(scrim)}\"></div>` : ''}`\n}\n\nfunction barHtml(content: OgContent, theme: OgTheme): string {\n if (!content.bar?.length) return ''\n const segs = content.bar\n .map(s => `<span>${esc(s)}</span>`)\n .join('<span class=\"dot\"></span>')\n return `<div class=\"bar\" style=\"background:${esc(theme.accent)}\">${segs}</div>`\n}\n\nfunction copyHtml(config: OgCardConfig): string {\n const { content, theme } = config\n const titleHtml = esc(content.title).replace(/\\n/g, '<br>')\n // In `split` the photo owns the right pane, so a configured logo has nowhere\n // to go as a plate. It used to be dropped silently. Render it small above the\n // kicker instead: the brand mark still appears, and the plate invariant that\n // split has no `.plate` is preserved.\n const mark =\n config.logo && (config.layout ?? (config.logo ? 'plate-left' : 'banner')) === 'split'\n ? `<img class=\"mark\" src=\"${esc(config.logo)}\">`\n : ''\n return `\n <div class=\"copy\">\n ${mark}\n ${content.kicker ? `<div class=\"kicker\">${esc(content.kicker)}</div>` : ''}\n <h1>${titleHtml}</h1>\n ${content.subtitle ? `<div class=\"sub\" style=\"color:${esc(muted(theme))}\">${esc(content.subtitle)}</div>` : ''}\n ${content.hand ? `<div class=\"hand\" style=\"font-family:'${esc(handFamily(config))}',cursive\">${esc(content.hand)}</div>` : ''}\n </div>`\n}\n\nexport function buildOgHtml(config: OgCardConfig): string {\n if (config.html) return config.html\n if (config.template) {\n return config.template(config, {\n esc,\n fontLink: fontLink(config),\n width: OG_WIDTH,\n height: OG_HEIGHT,\n })\n }\n\n const { theme, logo, layout = logo ? 'plate-left' : 'banner' } = config\n const display = displayFamily(config)\n\n const plate = logo\n ? `<div class=\"plate\" style=\"background:${esc(theme.surface)}\"><img src=\"${esc(logo)}\"></div>`\n : ''\n\n const photo = config.photo\n ? `<div class=\"photo\" style=\"flex-basis:${config.photo.width ?? 430}px;border-radius:${config.photo.radius ?? 24}px\"><img src=\"${esc(config.photo.src)}\"></div>`\n : ''\n\n const body =\n layout === 'centered'\n ? `<div class=\"wrap centered\">${plate}${copyHtml(config)}</div>`\n : layout === 'banner'\n ? `<div class=\"wrap banner\">${copyHtml(config)}</div>`\n : layout === 'split'\n ? `<div class=\"wrap\">${copyHtml(config)}${photo}</div>`\n : `<div class=\"wrap\">${photo || plate}${copyHtml(config)}</div>`\n\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n${fontLink(config)}\n<style>\n * { margin:0; padding:0; box-sizing:border-box; }\n html,body { width:${OG_WIDTH}px; height:${OG_HEIGHT}px; overflow:hidden; }\n body { position:relative; background:${theme.bg}; font-family:'${display}',sans-serif; color:${theme.text}; }\n .bg { position:absolute; inset:0; }\n .bg img { width:100%; height:100%; object-fit:cover; transform:scale(1.05); }\n .scrim { position:absolute; inset:0; }\n .wrap { position:relative; height:100%; display:flex; align-items:center; gap:52px; padding:0 68px ${config.content.bar?.length ? '74px' : '0'}; }\n .wrap.centered { flex-direction:column; justify-content:center; text-align:center; gap:28px; }\n .wrap.banner { padding-left:90px; }\n .plate { border-radius:28px; padding:26px; flex:0 0 auto; }\n .plate img { display:block; width:250px; height:auto; }\n .centered .plate img { width:180px; }\n .photo { flex:0 0 auto; height:76%; overflow:hidden; box-shadow:0 8px 40px rgba(0,0,0,.25); }\n .photo img { width:100%; height:100%; object-fit:cover; display:block; }\n .copy { flex:1 1 auto; min-width:0; }\n /* split-layout brand mark: the photo owns the right pane, so the logo rides\n above the kicker rather than being dropped. */\n .mark { display:block; height:44px; width:auto; margin-bottom:20px; }\n .kicker { font-weight:800; text-transform:uppercase; letter-spacing:.22em; font-size:24px; color:${theme.accent}; margin-bottom:14px; }\n /* Opening size only — the in-page fitter steps this down to fit the frame. */\n h1 { font-weight:800; text-transform:uppercase; font-size:${TITLE_MAX_PX}px; line-height:.9; letter-spacing:-.01em; }\n .sub { margin-top:22px; font-size:29px; font-weight:500; line-height:1.32; }\n /* Never below 40px: script faces are illegible at thumbnail width under that. */\n .hand { font-size:42px; margin-top:14px; transform:rotate(-1.5deg); display:inline-block; }\n .bar { position:absolute; left:0; right:0; bottom:0; height:74px; display:flex; align-items:center; justify-content:center; gap:26px;\n font-weight:800; text-transform:uppercase; letter-spacing:.1em; font-size:31px; color:${barTextColor(theme)}; }\n /* nowrap so an over-long segment overflows the bar sideways, where the\n fitter's width check sees it, instead of silently wrapping to a second\n line and spilling over the copy above. */\n .bar > span { white-space:nowrap; }\n .bar .dot { width:10px; height:10px; border-radius:50%; background:currentColor; opacity:.55; flex:0 0 auto; }\n</style></head>\n<body>\n${backgroundLayers(config)}\n${body}\n${barHtml(config.content, theme)}\n${fitScript()}\n</body></html>`\n}\n","/**\n * @sonordev/site-kit/og — per-page card derivation.\n *\n * The site-wide card answers \"what is this site\". A per-page card answers\n * \"what is THIS page\", which is what someone pasting a deep link is actually\n * sharing. Every static route gets one by default.\n *\n * Copy comes from Sonor's managed metadata when a key is configured, because\n * that is the same copy the page's <title> and description already use — the\n * card and the SERP entry then say the same thing. Without a key (or when a\n * lookup fails) the path itself is titled, which is worse copy but still a\n * real, page-specific card rather than the site default.\n *\n * Everything else — theme, fonts, logo, photo, bar — is inherited from the\n * site's own og.config.ts, so the whole set reads as one family.\n */\n\nimport type { OgCardConfig } from './config'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\n// Single source of truth for Next's trailingSlash URL shape (pure string logic).\nimport { applyTrailingSlash, resolveTrailingSlash } from '../sitemap/shared'\n\n/** A route to build a card for. */\nexport interface OgPageRoute {\n /** URL path, e.g. `/services/garage`. */\n path: string\n /** Managed title, if Sonor had one. */\n title?: string\n /** Managed meta description, if Sonor had one. */\n description?: string\n}\n\n/**\n * The stand-in for a dynamic segment in a route path.\n *\n * A dynamic route has no single URL, so its card is keyed by a pattern with one\n * `*` per dynamic level: `/services/[slug]` is `/services/*`, and each further\n * dynamic level adds another star. The depth is what keeps those patterns\n * apart — they are different pages and deserve different cards.\n *\n * (Nested patterns are spelled out in prose rather than written literally in\n * these block comments: a star followed by a slash closes the comment. See\n * src/og/README.md.)\n */\nexport const DYNAMIC = '*'\n\n/** `/services/garage/` -> `['services', 'garage']`. */\nfunction pathSegments(path: string): string[] {\n return path.split('/').filter(Boolean)\n}\n\n/**\n * A separator between a title and its brand: a pipe with or without spaces\n * around it, or a dash (`-`, `–`, `—`) with spaces on both sides. A dash with\n * no spaces is part of a word (\"Walk-In Closets\"), never a separator; the old\n * pattern treated it as one and cut that title to \"Walk\".\n */\nconst TITLE_SEPARATOR = /\\s*\\|\\s*|\\s+[–—-]\\s+/g\n\n/** `, abbeyglenapts.com` at the very end: a domain appended as if it were the brand. */\nconst TRAILING_HOSTNAME = /,\\s*(?:https?:\\/\\/)?(?:[a-z0-9-]+\\.)+[a-z]{2,}\\/?$/i\n\n/** A separator the title ends on, with nothing after it (\"Contact Us |\"). */\nconst DANGLING_SEPARATOR = /(?:\\s*\\||\\s+[–—-])\\s*$/\n\n/**\n * Trailing brand suffix on a managed title (` | Acme`, ` - Acme`, ` — Acme`).\n * Managed titles carry the brand because a SERP entry needs it; a card does\n * not — the logo, the photo and the bar are already saying whose it is, and\n * every character spent on the brand is one not spent on the page.\n *\n * Also drops the two broken suffixes managed titles turn up with, both found\n * on 2026-09-15 and each worked around by hand in a site's `cards`: a\n * dangling separator with no brand after it (reinhart's \"About Us |\", Green\n * Bay's), and the domain after a comma (\"Privacy Policy, abbeyglenapts.com\").\n */\nexport function stripBrandSuffix(title: string, brand?: string): string {\n const original = title.trim()\n // The junk first, so \"About | Acme |\" still loses its brand after it.\n const base = original.replace(DANGLING_SEPARATOR, '').replace(TRAILING_HOSTNAME, '').trim() || original\n let last = -1\n for (const m of base.matchAll(TITLE_SEPARATOR)) last = m.index ?? last\n const cleaned = last > 0 ? base.slice(0, last).trim() : base\n // Only accept the strip if something survives it, so a title that IS the\n // brand (\"Acme\") is left alone rather than reduced to nothing.\n if (!cleaned) return base\n if (brand && cleaned.toLowerCase() === brand.toLowerCase()) return base\n return cleaned\n}\n\n/**\n * `/services/walk-in-closets` -> `Walk In Closets`. The no-managed-data path.\n *\n * `*` segments (a dynamic route's card, `/services/*`) are skipped rather than\n * titled: one file covers every param, so the honest title is the section the\n * params belong to. Without this, an unmanaged dynamic segment rendered a card\n * whose headline was literally `*`.\n */\nexport function titleFromPath(path: string): string {\n const last = pathSegments(path).filter(s => s !== DYNAMIC).pop()\n if (!last) return 'Home'\n return last\n .replace(/[-_]+/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase())\n .trim()\n}\n\n/**\n * The kicker for a page card: its parent section, so a deep link reads\n * \"SERVICES / Walk In Closets\" rather than repeating the site name on every\n * card. Falls back to the base card's kicker at the top level.\n */\nexport function kickerFromPath(path: string, fallback?: string): string | undefined {\n const parts = pathSegments(path)\n // When the leaf is a `*`, the stars ARE the leaf, so the section is every\n // static segment — `/services/*` and `/services/*/*` both read \"services\",\n // never \"services / *\". A static leaf under a dynamic segment\n // (`/properties/*/about`) is the page itself, so the section is what's\n // above it, minus the stars: \"properties\".\n const section = (parts[parts.length - 1] === DYNAMIC ? parts : parts.slice(0, -1)).filter(\n s => s !== DYNAMIC,\n )\n if (!section.length) return fallback\n return section.join(' / ').replace(/[-_]+/g, ' ')\n}\n\n/**\n * Keep a card subtitle to two readable lines, never a full meta description.\n *\n * A meta description is written for a SERP row and runs to ~155 characters; at\n * card size that is four lines crowding the bottom bar. This trims to the first\n * sentence when there is one, and otherwise cuts on a word boundary.\n */\nexport function clampSubtitle(text: string | undefined, max = 100): string | undefined {\n if (!text) return undefined\n const flat = text.replace(/\\s+/g, ' ').trim()\n if (!flat) return undefined\n if (flat.length <= max) return flat\n\n // A whole sentence reads as written rather than as truncated. The floor is\n // absolute rather than a ratio of `max` (a ratio rejected a clean\n // 48-character sentence) — but not so low it accepts a stray \"Inc.\".\n const sentence = flat.slice(0, max + 1).match(/^.*[.!?](?=\\s|$)/)?.[0]\n if (sentence && sentence.length >= 30) return sentence.trim()\n\n /*\n * Otherwise: refuse.\n *\n * A managed description is 150+ characters of prose written for a SERP row,\n * and at card size NO truncation of it looks deliberate. Both attempts\n * proved it on real cards: a word cut gave \"across Cincinnati and…\", and\n * cutting at the last comma gave \"home offices designed, built\" — worse,\n * because with no ellipsis it looks complete and merely ungrammatical.\n *\n * Returning undefined lets the caller fall back to the site's own subtitle,\n * which is short, hand-written and always fits. A clean generic line beats a\n * mangled specific one, and the TITLE is already page-specific — which is\n * the signal that actually matters on a card.\n */\n return undefined\n}\n\n/**\n * Is the kicker already saying what the title says?\n *\n * Managed titles are written for search and usually carry the section and the\n * city, so the home page came out as kicker \"Custom Closets · Cincinnati\" above\n * title \"CUSTOM CLOSETS CINCINNATI TRI-STATE\" — a stutter that also cost a line\n * of the frame. When either contains the other, the title wins.\n */\nfunction kickerIsRedundant(kicker: string | undefined, title: string): boolean {\n if (!kicker) return false\n const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n const k = norm(kicker)\n const t = norm(title)\n if (!k) return true\n if (t.includes(k) || k.includes(t)) return true\n // Or every word of the kicker already appears in the title.\n const titleWords = new Set(t.split(' '))\n return k.split(' ').every(w => titleWords.has(w))\n}\n\n/**\n * File-safe name for a route's card: `/services/garage` -> `services-garage`,\n * `/` -> `home`. Deterministic, so the generator and anything reading the\n * output agree without a manifest.\n *\n * A `*` (dynamic) segment becomes `any`, one per level: `/services/*` is\n * `services-any`, and a second dynamic level is `services-any-any`. The naive\n * punctuation strip collapsed every one of those to `services` — the SAME slug\n * as the real `/services` route — so a `cards` override keyed by slug could\n * silently land on the wrong card. (A literal `/services/any` route would still collide;\n * key those by path, which is always unambiguous.)\n */\nexport function slugForPath(path: string): string {\n const segments = pathSegments(path)\n if (!segments.length) return 'home'\n return segments\n .map(s => (s === DYNAMIC ? 'any' : s))\n .join('-')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase()\n}\n\n/**\n * The `cards` keys in og.config.ts that match none of the routes being\n * rendered.\n *\n * `pageCardConfig` looks an override up by exact path or exact slug and\n * silently derives the card when neither hits, so a mistyped or stale key — or\n * one written for a route shape the generator does not produce (a two-star\n * services pattern, before nested dynamic segments got their own key) — cost a\n * hand-written card with nothing said about it. The caller reports these.\n */\nexport function unmatchedCardKeys(\n cards: Record<string, unknown> | undefined,\n paths: string[],\n): string[] {\n if (!cards) return []\n const known = new Set(paths.flatMap(p => [p, slugForPath(p)]))\n const patterns = paths.filter(p => p.includes(DYNAMIC))\n return Object.keys(cards).filter(\n key => !known.has(key) && !patterns.some(pattern => matchesRoutePattern(key, pattern)),\n )\n}\n\n/** `/floor-plans/1-bedroom/` -> `/floor-plans/1-bedroom`. */\nfunction bareRoute(path: string): string {\n const segments = pathSegments(path)\n return segments.length ? `/${segments.join('/')}` : '/'\n}\n\n/**\n * Does a concrete URL fall under a dynamic route pattern? One `*` matches\n * exactly one segment: `/properties/tall-pines/about` is under\n * `/properties/*` + `/about`, and `/floor-plans/1-bedroom` under\n * `/floor-plans/*`. A key that is itself a pattern matches nothing.\n */\nexport function matchesRoutePattern(path: string, pattern: string): boolean {\n const concrete = pathSegments(path)\n const shape = pathSegments(pattern)\n if (concrete.includes(DYNAMIC) || concrete.length !== shape.length) return false\n return shape.every((seg, i) => seg === DYNAMIC || seg === concrete[i])\n}\n\n/**\n * The `cards` keys that name one page under a dynamic route: a card per\n * floor plan, per community, per sub-page of a community. Keyed by the URL\n * itself (`/floor-plans/1-bedroom`), which any site can build from the same\n * data its `generateStaticParams` reads.\n *\n * A static file in a dynamic segment covers every param of the route, so\n * these can't be `opengraph-image` files. `sonor-setup og` renders each one to\n * `public/_og/` (see paramCardUrl), and the page declares it with\n * `paramCardImage`. The MDG property sites needed a card per floor plan and\n * per community and rendered them with their own script until this existed.\n *\n * Returns each key with the pattern it falls under. Keys that are a real\n * static route or a pattern are left to the route cards.\n */\nexport function paramCardKeys(\n cards: Record<string, unknown> | undefined,\n paths: string[],\n): Array<{ path: string; pattern: string }> {\n if (!cards) return []\n const known = new Set(paths.flatMap(p => [p, slugForPath(p)]))\n const patterns = paths.filter(p => p.includes(DYNAMIC))\n const found: Array<{ path: string; pattern: string }> = []\n for (const key of Object.keys(cards)) {\n if (known.has(key) || !key.startsWith('/')) continue\n const pattern = patterns.find(p => matchesRoutePattern(key, p))\n if (pattern) found.push({ path: bareRoute(key), pattern })\n }\n return found\n}\n\n/**\n * Public directory (under `public/`) that `sonor-setup og` owns for per-URL\n * cards. Generated; the command clears it on every run, so keep nothing else\n * in it.\n */\nexport const PARAM_CARD_DIR = '_og'\n\n/**\n * Where the per-URL card for a page is served: `/floor-plans/1-bedroom` ->\n * `/_og/floor-plans/1-bedroom.jpg`. Single source of truth for the file the\n * CLI writes and the URL `paramCardImage` declares.\n *\n * It ends in `.jpg` on purpose. Next serves a code-based `opengraph-image`\n * route without an extension, and `trailingSlash: true` answers that URL with\n * a 308 before the card, which some preview bots don't follow. A static file\n * with an extension is never redirected.\n */\nexport function paramCardUrl(path: string): string {\n const segments = pathSegments(path)\n return `/${PARAM_CARD_DIR}/${segments.length ? segments.join('/') : 'home'}.jpg`\n}\n\n/** An `openGraph.images` entry. */\nexport interface OgImageDescriptor {\n url: string\n width: number\n height: number\n type: string\n alt?: string\n}\n\n/**\n * The page's per-URL card, for its metadata:\n *\n * ```ts\n * const card = paramCardImage(`/floor-plans/${slug}`, { config: ogConfig })\n * return {\n * openGraph: { ...(card && { images: [card] }) },\n * twitter: { card: 'summary_large_image', ...(card && { images: [card.url] }) },\n * }\n * ```\n *\n * Pass `config` (the site's og.config) and it answers undefined for a page\n * with no `cards` entry, so the page keeps its segment's static card. Without\n * `config` it always answers, so only call it for URLs you gave a card.\n */\nexport function paramCardImage(\n path: string,\n options: { config?: Pick<OgCardConfig, 'cards'>; alt?: string } = {},\n): OgImageDescriptor | undefined {\n if (options.config) {\n const bare = bareRoute(path)\n const cards = options.config.cards ?? {}\n if (!cards[bare] && !cards[`${bare}/`]) return undefined\n }\n return {\n url: paramCardUrl(path),\n width: OG_WIDTH,\n height: OG_HEIGHT,\n type: 'image/jpeg',\n ...(options.alt && { alt: options.alt }),\n }\n}\n\n/**\n * A code-based card (`opengraph-image.tsx`) declared at the URL the site\n * actually serves it from.\n *\n * Next wires such a card in as `<path>/opengraph-image`, with no extension.\n * With `trailingSlash: true` that URL 308-redirects to the slashed one, so\n * every share preview starts with a redirect, and bots that don't follow one\n * show no card (the MDG property sites, 2026-09-15). Declaring the slashed URL\n * gets a 200 first time. Declared images beat the file convention, so this\n * replaces Next's own entry rather than adding a second.\n *\n * `trailingSlash` defaults to the site's next.config. Without it, this is the\n * URL Next would have used anyway.\n */\nexport function codeCardImage(\n path: string,\n options: { trailingSlash?: boolean; alt?: string; contentType?: string } = {},\n): OgImageDescriptor {\n const base = bareRoute(path)\n const url = applyTrailingSlash(`${base === '/' ? '' : base}/opengraph-image`, resolveTrailingSlash(options.trailingSlash))\n return {\n url,\n width: OG_WIDTH,\n height: OG_HEIGHT,\n type: options.contentType ?? 'image/png',\n ...(options.alt && { alt: options.alt }),\n }\n}\n\n/**\n * Build a page's card config by layering its copy over the site's card.\n *\n * `cards` in og.config.ts wins over everything derived here, so a site can\n * hand-write the two or three cards that matter and let the rest be generated.\n */\nexport function pageCardConfig(\n base: OgCardConfig,\n route: OgPageRoute,\n brand?: string,\n): OgCardConfig {\n const override =\n base.cards?.[route.path] ?? base.cards?.[`${route.path}/`] ?? base.cards?.[slugForPath(route.path)]\n const isHome = route.path === '/'\n\n /*\n * The home page keeps the card the site actually wrote.\n *\n * og.config.ts `content` IS the home page's card — it is the one route whose\n * subject is the whole site, and it is hand-written. Deriving it from the\n * managed <title> instead replaced a crafted headline (\"Built by brothers\")\n * with the SEO string (\"Custom Closets Cincinnati Tri-State\"), which repeated\n * the kicker and ran to four lines. Every other route has no hand-written\n * card, so deriving is a clear gain there.\n */\n const derived: OgCardConfig = isHome\n ? { ...base, cards: undefined }\n : (() => {\n const title = route.title ? stripBrandSuffix(route.title, brand) : titleFromPath(route.path)\n const kicker = kickerFromPath(route.path, base.content.kicker)\n return {\n ...base,\n // `cards` is a build-time instruction, not part of a rendered card.\n cards: undefined,\n content: {\n ...base.content,\n kicker: kickerIsRedundant(kicker, title) ? undefined : kicker,\n title,\n subtitle: clampSubtitle(route.description) ?? base.content.subtitle,\n },\n }\n })()\n\n if (!override) return derived\n return {\n ...derived,\n ...override,\n content: { ...derived.content, ...override.content },\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { resolveSiteHost } from './chunk-JLVCWC6A.mjs';
2
- import { configureSiteCredential, currentCredential } from './chunk-NCDF2RK4.mjs';
1
+ import { resolveSiteHost } from './chunk-KZVSB5WE.mjs';
2
+ import { configureSiteCredential, currentCredential } from './chunk-L63DFM4R.mjs';
3
3
 
4
4
  // src/shared/publishCredential.ts
5
5
  function publishSiteCredential({
@@ -19,5 +19,5 @@ function publishSiteCredential({
19
19
  }
20
20
 
21
21
  export { publishSiteCredential };
22
- //# sourceMappingURL=chunk-SFC3OWSE.mjs.map
23
- //# sourceMappingURL=chunk-SFC3OWSE.mjs.map
22
+ //# sourceMappingURL=chunk-RX4TVHSN.mjs.map
23
+ //# sourceMappingURL=chunk-RX4TVHSN.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/publishCredential.ts"],"names":[],"mappings":";;;;AAkEO,SAAS,qBAAA,CAAsB;AAAA,EACpC,MAAA;AAAA,EACA,MAAA,GAAS,sBAAA;AAAA,EACT,IAAA;AAAA,EACA,KAAA,GAAQ,KAAA;AAAA,EACR;AACF,CAAA,EAAuC;AACrC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAKnC,EAAA,uBAAA,CAAwB,EAAE,UAAA,EAAY,MAAA,EAAQ,MAAA,EAAQ,CAAA;AACrD,EAAC,OAAe,oBAAA,GAAuB,MAAA;AACvC,EAAC,MAAA,CAAe,oBAAA,GAAuB,iBAAA,CAAkB,MAAM,CAAA;AAC/D,EAAC,OAAe,kBAAA,GAAqB,KAAA;AACrC,EAAC,MAAA,CAAe,iBAAA,GAAoB,IAAA,IAAQ,eAAA,EAAgB;AAG7D,EAAA,IAAI,cAAA,EAAiB,MAAA,CAAe,uBAAA,GAA0B,cAAA;AAChE","file":"chunk-SFC3OWSE.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared — Client credential + window-global publishing\n *\n * SINGLE SOURCE OF TRUTH for seeding the browser-side token manager and the\n * `window.__SITE_KIT_*` globals that every client module reads. Used by:\n *\n * - `SiteKitClientProviders` (core `SiteKitLayout`)\n * - `SiteKitCredential` (`@sonordev/site-kit/client`) — the public component\n * for layouts that are NOT core `SiteKitLayout`, e.g.\n * `@sonordev/agency-site-kit`'s `AgencySiteKitLayout`.\n *\n * Do not re-fork this. Before it existed, agency sites hand-rolled their own\n * \"set the globals on mount\" component (upforge's `UptradeSiteKitInit`), and\n * because those copies had no access to the token manager they published the\n * RAW `sonor_` key into the client bundle — the exact exposure the mint exists\n * to prevent. Any layout that puts a credential on `window` goes through here.\n *\n * Client-safe: no `server-only` guard, SSR-safe (every write is window-gated).\n * The minting half is server-only and lives in `../server/mint-site-token`.\n */\n// Leaf modules, NOT '../sites/contract' — see sites/normalize.ts. Importing the\n// full contract here shipped its JSON schema to every browser that loads a\n// credential-publishing entry.\nimport { resolveSiteHost } from '../sites/resolve'\nimport { configureSiteCredential, currentCredential } from './siteToken'\n\n/**\n * Re-exported so the client-side callers below and every existing importer keep\n * a single name for this. The implementation lives in `../sites/resolve` — the\n * ONE resolver shared by the browser (this file) and the build-time sitemap /\n * llms.txt path, so both tag the same host.\n */\nexport { resolveSiteHost }\n\nexport interface PublishSiteCredentialOptions {\n /**\n * The credential the layout minted server-side. In 3.2+ this is a short-lived\n * `st1.` token that `sonorFetch` refreshes transparently. A raw `sonor_` key\n * still works (2.x kit, or the mint-failed fallback) but should never be\n * routed here from a client component — it would ship the secret to the\n * browser.\n */\n apiKey?: string\n apiUrl?: string\n /** Pre-resolved sub-site host. Omit to resolve via `resolveSiteHost`. */\n site?: string\n debug?: boolean\n /**\n * Referrer-restricted Google Maps BROWSER key for client map components.\n * Resolved server-side by SiteKitLayout (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY /\n * GOOGLE_MAPS_BROWSER_KEY) because maps/api.ts runs client-side, where a bare\n * process.env read is dead code unless the app's bundler happens to inline it.\n * Never route a server secret here.\n */\n mapsBrowserKey?: string\n}\n\n/**\n * Seed the token manager and publish the `window.__SITE_KIT_*` globals.\n *\n * Called during RENDER, not in an effect: client modules read these globals in\n * their own mount effects, and effects run child-first, so an effect-based\n * publish would land after the modules that need it.\n *\n * No-op on the server.\n */\nexport function publishSiteCredential({\n apiKey,\n apiUrl = 'https://api.sonor.io',\n site,\n debug = false,\n mapsBrowserKey,\n}: PublishSiteCredentialOptions): void {\n if (typeof window === 'undefined') return\n\n // Seed the client token manager. The window global is set to the freshest\n // token held — a prior page this session may already have refreshed past the\n // seed the layout injected. In raw-key mode this is a passthrough.\n configureSiteCredential({ credential: apiKey, apiUrl })\n ;(window as any).__SITE_KIT_API_URL__ = apiUrl\n ;(window as any).__SITE_KIT_API_KEY__ = currentCredential(apiKey)\n ;(window as any).__SITE_KIT_DEBUG__ = debug\n ;(window as any).__SITE_KIT_SITE__ = site ?? resolveSiteHost()\n // maps/api.ts falls back to this global when the Sonor platform hasn't\n // provisioned a browser key. Don't clobber a value another layout already set.\n if (mapsBrowserKey) (window as any).__GOOGLE_MAPS_API_KEY__ = mapsBrowserKey\n}\n"]}
1
+ {"version":3,"sources":["../src/shared/publishCredential.ts"],"names":[],"mappings":";;;;AAkEO,SAAS,qBAAA,CAAsB;AAAA,EACpC,MAAA;AAAA,EACA,MAAA,GAAS,sBAAA;AAAA,EACT,IAAA;AAAA,EACA,KAAA,GAAQ,KAAA;AAAA,EACR;AACF,CAAA,EAAuC;AACrC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAKnC,EAAA,uBAAA,CAAwB,EAAE,UAAA,EAAY,MAAA,EAAQ,MAAA,EAAQ,CAAA;AACrD,EAAC,OAAe,oBAAA,GAAuB,MAAA;AACvC,EAAC,MAAA,CAAe,oBAAA,GAAuB,iBAAA,CAAkB,MAAM,CAAA;AAC/D,EAAC,OAAe,kBAAA,GAAqB,KAAA;AACrC,EAAC,MAAA,CAAe,iBAAA,GAAoB,IAAA,IAAQ,eAAA,EAAgB;AAG7D,EAAA,IAAI,cAAA,EAAiB,MAAA,CAAe,uBAAA,GAA0B,cAAA;AAChE","file":"chunk-RX4TVHSN.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared — Client credential + window-global publishing\n *\n * SINGLE SOURCE OF TRUTH for seeding the browser-side token manager and the\n * `window.__SITE_KIT_*` globals that every client module reads. Used by:\n *\n * - `SiteKitClientProviders` (core `SiteKitLayout`)\n * - `SiteKitCredential` (`@sonordev/site-kit/client`) — the public component\n * for layouts that are NOT core `SiteKitLayout`, e.g.\n * `@sonordev/agency-site-kit`'s `AgencySiteKitLayout`.\n *\n * Do not re-fork this. Before it existed, agency sites hand-rolled their own\n * \"set the globals on mount\" component (upforge's `UptradeSiteKitInit`), and\n * because those copies had no access to the token manager they published the\n * RAW `sonor_` key into the client bundle — the exact exposure the mint exists\n * to prevent. Any layout that puts a credential on `window` goes through here.\n *\n * Client-safe: no `server-only` guard, SSR-safe (every write is window-gated).\n * The minting half is server-only and lives in `../server/mint-site-token`.\n */\n// Leaf modules, NOT '../sites/contract' — see sites/normalize.ts. Importing the\n// full contract here shipped its JSON schema to every browser that loads a\n// credential-publishing entry.\nimport { resolveSiteHost } from '../sites/resolve'\nimport { configureSiteCredential, currentCredential } from './siteToken'\n\n/**\n * Re-exported so the client-side callers below and every existing importer keep\n * a single name for this. The implementation lives in `../sites/resolve` — the\n * ONE resolver shared by the browser (this file) and the build-time sitemap /\n * llms.txt path, so both tag the same host.\n */\nexport { resolveSiteHost }\n\nexport interface PublishSiteCredentialOptions {\n /**\n * The credential the layout minted server-side. In 3.2+ this is a short-lived\n * `st1.` token that `sonorFetch` refreshes transparently. A raw `sonor_` key\n * still works (2.x kit, or the mint-failed fallback) but should never be\n * routed here from a client component — it would ship the secret to the\n * browser.\n */\n apiKey?: string\n apiUrl?: string\n /** Pre-resolved sub-site host. Omit to resolve via `resolveSiteHost`. */\n site?: string\n debug?: boolean\n /**\n * Referrer-restricted Google Maps BROWSER key for client map components.\n * Resolved server-side by SiteKitLayout (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY /\n * GOOGLE_MAPS_BROWSER_KEY) because maps/api.ts runs client-side, where a bare\n * process.env read is dead code unless the app's bundler happens to inline it.\n * Never route a server secret here.\n */\n mapsBrowserKey?: string\n}\n\n/**\n * Seed the token manager and publish the `window.__SITE_KIT_*` globals.\n *\n * Called during RENDER, not in an effect: client modules read these globals in\n * their own mount effects, and effects run child-first, so an effect-based\n * publish would land after the modules that need it.\n *\n * No-op on the server.\n */\nexport function publishSiteCredential({\n apiKey,\n apiUrl = 'https://api.sonor.io',\n site,\n debug = false,\n mapsBrowserKey,\n}: PublishSiteCredentialOptions): void {\n if (typeof window === 'undefined') return\n\n // Seed the client token manager. The window global is set to the freshest\n // token held — a prior page this session may already have refreshed past the\n // seed the layout injected. In raw-key mode this is a passthrough.\n configureSiteCredential({ credential: apiKey, apiUrl })\n ;(window as any).__SITE_KIT_API_URL__ = apiUrl\n ;(window as any).__SITE_KIT_API_KEY__ = currentCredential(apiKey)\n ;(window as any).__SITE_KIT_DEBUG__ = debug\n ;(window as any).__SITE_KIT_SITE__ = site ?? resolveSiteHost()\n // maps/api.ts falls back to this global when the Sonor platform hasn't\n // provisioned a browser key. Don't clobber a value another layout already set.\n if (mapsBrowserKey) (window as any).__GOOGLE_MAPS_API_KEY__ = mapsBrowserKey\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- // src/blog/blog-tables.ts
4
- var tableScrollClass = "sk-blog-table-scroll";
3
+ // src/articles/article-tables.ts
4
+ var tableScrollClass = "sk-article-table-scroll";
5
+ var legacyTableScrollClass = "sk-blog-table-scroll";
5
6
  var voidElements = /* @__PURE__ */ new Set([
6
7
  "area",
7
8
  "base",
@@ -27,7 +28,7 @@ function hasScrollClass(tag) {
27
28
  const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
28
29
  for (const attribute of attributes.matchAll(pattern)) {
29
30
  if (attribute[1].toLowerCase() !== "class") continue;
30
- return (attribute[2] ?? attribute[3] ?? attribute[4] ?? "").split(/\s+/).includes(tableScrollClass);
31
+ return (attribute[2] ?? attribute[3] ?? attribute[4] ?? "").split(/\s+/).some((name) => name === tableScrollClass || name === legacyTableScrollClass);
31
32
  }
32
33
  return false;
33
34
  }
@@ -43,7 +44,7 @@ function tableLabel(table, number) {
43
44
  const summary = labelText(caption || headers).slice(0, 180);
44
45
  return escapeLabel(`Table ${number}${summary ? `: ${summary}` : ""}`);
45
46
  }
46
- function wrapBlogTables(html) {
47
+ function wrapArticleTables(html) {
47
48
  const tags = htmlTags();
48
49
  const stack = [];
49
50
  const tables = [];
@@ -87,10 +88,10 @@ function wrapBlogTables(html) {
87
88
  }
88
89
  return output + html.slice(cursor);
89
90
  }
90
- var blogTableCss = (
91
+ var articleTableCss = (
91
92
  /* css */
92
93
  `
93
- .sk-blog-content .sk-blog-table-scroll {
94
+ .sk-article-content .sk-article-table-scroll {
94
95
  box-sizing: border-box;
95
96
  min-width: 0;
96
97
  max-width: 100%;
@@ -103,27 +104,27 @@ var blogTableCss = (
103
104
  scrollbar-color: var(--sk-primary, #2563eb) var(--sk-bg-elevated, #f8fafc);
104
105
  overscroll-behavior-x: contain;
105
106
  }
106
- .sk-blog-content .sk-blog-table-scroll:focus-visible {
107
+ .sk-article-content .sk-article-table-scroll:focus-visible {
107
108
  outline: 2px solid var(--sk-border-focus, var(--sk-primary, #2563eb));
108
109
  outline-offset: 3px;
109
110
  }
110
- .sk-blog-content .sk-blog-table-scroll::-webkit-scrollbar { height: 10px; }
111
- .sk-blog-content .sk-blog-table-scroll::-webkit-scrollbar-track {
111
+ .sk-article-content .sk-article-table-scroll::-webkit-scrollbar { height: 10px; }
112
+ .sk-article-content .sk-article-table-scroll::-webkit-scrollbar-track {
112
113
  background: var(--sk-bg-elevated, #f8fafc);
113
114
  border-radius: var(--sk-radius, 0.5rem);
114
115
  }
115
- .sk-blog-content .sk-blog-table-scroll::-webkit-scrollbar-thumb {
116
+ .sk-article-content .sk-article-table-scroll::-webkit-scrollbar-thumb {
116
117
  background: var(--sk-primary, #2563eb);
117
118
  background-clip: padding-box;
118
119
  border: 2px solid var(--sk-bg-elevated, #f8fafc);
119
120
  border-radius: var(--sk-radius, 0.5rem);
120
121
  }
121
- .sk-blog-content .sk-blog-table-scroll > table {
122
+ .sk-article-content .sk-article-table-scroll > table {
122
123
  display: table;
123
124
  margin: 0;
124
125
  overflow: visible;
125
126
  }
126
- .sk-blog-content .sk-blog-table-scroll caption {
127
+ .sk-article-content .sk-article-table-scroll caption {
127
128
  padding: 0.625rem 0.875rem;
128
129
  text-align: start;
129
130
  color: var(--sk-text-secondary, #475569);
@@ -131,7 +132,7 @@ var blogTableCss = (
131
132
  `.trim()
132
133
  );
133
134
 
134
- exports.blogTableCss = blogTableCss;
135
- exports.wrapBlogTables = wrapBlogTables;
136
- //# sourceMappingURL=chunk-YQTE32QK.js.map
137
- //# sourceMappingURL=chunk-YQTE32QK.js.map
135
+ exports.articleTableCss = articleTableCss;
136
+ exports.wrapArticleTables = wrapArticleTables;
137
+ //# sourceMappingURL=chunk-SG7CWD2R.js.map
138
+ //# sourceMappingURL=chunk-SG7CWD2R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/articles/article-tables.ts"],"names":["frame"],"mappings":"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","file":"chunk-SG7CWD2R.js","sourcesContent":["/**\n * Server-rendered table scrolling. Keep the author's table markup intact so\n * captions, header associations, links and table semantics survive unchanged.\n */\n\nconst tableScrollClass = 'sk-article-table-scroll'\n/** The pre-6.0 name, still recognized in stored HTML so an already-wrapped table isn't wrapped twice. */\nconst legacyTableScrollClass = 'sk-blog-table-scroll'\nconst voidElements = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',\n 'param', 'source', 'track', 'wbr',\n])\nconst preservedElements = new Set(['pre', 'code', 'script', 'style', 'textarea', 'xmp', 'template'])\n\n/** Match complete tags, including quoted `>` characters, without reserializing HTML. */\nfunction htmlTags(): RegExp {\n return /<!--[\\s\\S]*?(?:-->|$)|<\\/?[a-z][\\w:-]*(?=[\\s/>])(?:[^\"'<>]|\"[^\"]*\"|'[^']*')*>/gi\n}\n\nfunction hasScrollClass(tag: string): boolean {\n const attributes = tag.replace(/^<[\\w:-]+/, '').replace(/\\/?>$/, '')\n const pattern = /([^\\s=/>]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g\n for (const attribute of attributes.matchAll(pattern)) {\n if (attribute[1].toLowerCase() !== 'class') continue\n return (attribute[2] ?? attribute[3] ?? attribute[4] ?? '').split(/\\s+/).some((name) => name === tableScrollClass || name === legacyTableScrollClass)\n }\n return false\n}\n\nfunction labelText(html: string): string {\n // Preserve existing character references in the eventual attribute. Escaping\n // them a second time would announce \"amp\" instead of the author's text.\n return html\n .replace(/<!--[\\s\\S]*?(?:-->|$)/g, '')\n .replace(htmlTags(), ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction escapeLabel(text: string): string {\n return text\n .replace(/&(?!(?:#\\d+|#x[\\da-f]+|[a-z][\\da-z]+);)/gi, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\nfunction tableLabel(table: string, number: number): string {\n const caption = table.match(/<caption\\b(?:[^\"'<>]|\"[^\"]*\"|'[^']*')*>([\\s\\S]*?)<\\/caption\\s*>/i)?.[1]\n const headers = [...table.matchAll(/<th\\b(?:[^\"'<>]|\"[^\"]*\"|'[^']*')*>([\\s\\S]*?)<\\/th\\s*>/gi)]\n .slice(0, 4)\n .map((match) => labelText(match[1]))\n .filter(Boolean)\n .join(', ')\n const summary = labelText(caption || headers).slice(0, 180)\n return escapeLabel(`Table ${number}${summary ? `: ${summary}` : ''}`)\n}\n\ninterface ElementFrame {\n tag: string\n scrollRegion: boolean\n table?: { start: number; number: number }\n}\n\n/**\n * Single source of truth for default article table wrappers. Safe to call twice;\n * already wrapped tables and HTML examples are left alone. This is a layout\n * transform, not an HTML sanitizer: content still follows the host's trust policy.\n */\nexport function wrapArticleTables(html: string): string {\n const tags = htmlTags()\n const stack: ElementFrame[] = []\n const tables: Array<{ start: number; end: number; number: number }> = []\n let tableNumber = 0\n let token: RegExpExecArray | null\n\n while ((token = tags.exec(html))) {\n const tag = token[0]\n if (tag.startsWith('<!--')) continue\n const tagName = /^<\\/?([\\w:-]+)/.exec(tag)![1].toLowerCase()\n\n if (tag.startsWith('</')) {\n const index = stack.map((frame) => frame.tag).lastIndexOf(tagName)\n if (index < 0) continue\n const frame = stack[index]\n if (frame.table) tables.push({ ...frame.table, end: tags.lastIndex })\n stack.length = index\n continue\n }\n\n if (preservedElements.has(tagName)) {\n // Code examples and raw text can contain literal HTML. Skip to the end\n // without interpreting any of those tags as article content.\n const closingTag = new RegExp(`</${tagName}\\\\s*>`, 'gi')\n closingTag.lastIndex = tags.lastIndex\n if (!closingTag.exec(html)) break\n tags.lastIndex = closingTag.lastIndex\n continue\n }\n\n if (voidElements.has(tagName) || /\\/\\s*>$/.test(tag)) continue\n const frame: ElementFrame = { tag: tagName, scrollRegion: hasScrollClass(tag) }\n if (tagName === 'table' && !stack.some((ancestor) => ancestor.tag === 'table')) {\n tableNumber += 1\n if (!stack.some((ancestor) => ancestor.scrollRegion)) {\n frame.table = { start: token.index, number: tableNumber }\n }\n }\n stack.push(frame)\n }\n\n let output = ''\n let cursor = 0\n for (const { start, end, number } of tables) {\n const table = html.slice(start, end)\n output += `${html.slice(cursor, start)}<div class=\"${tableScrollClass}\" role=\"region\" tabindex=\"0\" aria-label=\"${tableLabel(table, number)}\">${table}</div>`\n cursor = end\n }\n return output + html.slice(cursor)\n}\n\n/** Native overflow only when content needs it; small tables retain their layout. */\nexport const articleTableCss = /* css */ `\n.sk-article-content .sk-article-table-scroll {\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n overflow-x: auto;\n margin: 1.5em 0;\n border: 1px solid var(--sk-surface-border, #e2e8f0);\n border-radius: var(--sk-radius, 0.5rem);\n background: var(--sk-bg-elevated, #f8fafc);\n scrollbar-width: thin;\n scrollbar-color: var(--sk-primary, #2563eb) var(--sk-bg-elevated, #f8fafc);\n overscroll-behavior-x: contain;\n}\n.sk-article-content .sk-article-table-scroll:focus-visible {\n outline: 2px solid var(--sk-border-focus, var(--sk-primary, #2563eb));\n outline-offset: 3px;\n}\n.sk-article-content .sk-article-table-scroll::-webkit-scrollbar { height: 10px; }\n.sk-article-content .sk-article-table-scroll::-webkit-scrollbar-track {\n background: var(--sk-bg-elevated, #f8fafc);\n border-radius: var(--sk-radius, 0.5rem);\n}\n.sk-article-content .sk-article-table-scroll::-webkit-scrollbar-thumb {\n background: var(--sk-primary, #2563eb);\n background-clip: padding-box;\n border: 2px solid var(--sk-bg-elevated, #f8fafc);\n border-radius: var(--sk-radius, 0.5rem);\n}\n.sk-article-content .sk-article-table-scroll > table {\n display: table;\n margin: 0;\n overflow: visible;\n}\n.sk-article-content .sk-article-table-scroll caption {\n padding: 0.625rem 0.875rem;\n text-align: start;\n color: var(--sk-text-secondary, #475569);\n}\n`.trim()\n"]}
@@ -1,5 +1,5 @@
1
- import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-NCDF2RK4.mjs';
2
- import { SITE_KIT_VERSION } from './chunk-5FETEH3Y.mjs';
1
+ import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-L63DFM4R.mjs';
2
+ import { SITE_KIT_VERSION } from './chunk-7MAHP735.mjs';
3
3
 
4
4
  // src/shared/fetchWithRetry.ts
5
5
  function backoffDelay(attempt, response) {
@@ -127,5 +127,5 @@ function sonorBeacon(url, payload, apiKey) {
127
127
  }
128
128
 
129
129
  export { sonorBeacon, sonorFetch };
130
- //# sourceMappingURL=chunk-4C5CZJIN.mjs.map
131
- //# sourceMappingURL=chunk-4C5CZJIN.mjs.map
130
+ //# sourceMappingURL=chunk-SU42KCQD.mjs.map
131
+ //# sourceMappingURL=chunk-SU42KCQD.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":[],"mappings":"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","file":"chunk-4C5CZJIN.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":[],"mappings":";;;;AAsBA,SAAS,YAAA,CAAa,SAAiB,QAAA,EAA6B;AAElE,EAAA,IAAI,QAAA,EAAU,WAAW,GAAA,EAAK;AAC5B,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA;AACjD,IAAA,MAAM,OAAA,GAAU,MAAA,GAAS,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA,GAAI,GAAA;AAChD,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,IAAK,OAAA,GAAU,CAAA,EAAG,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,GAAA,EAAM,GAAK,CAAA;AAAA,EAClF;AAEA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,EAAM,GAAA,GAAM,KAAK,OAAO,CAAA;AAC1C;AASA,eAAsB,cAAA,CACpB,GAAA,EACA,IAAA,EACA,OAAA,GAA6B,EAAC,EACX;AACnB,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,CAAA;AACnC,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,IAAA;AACvC,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,OAAA,EAAS,OAAA,EAAA,EAAW;AACnD,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAC5D,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AACxE,MAAA,YAAA,CAAa,KAAK,CAAA;AAElB,MAAA,IAAA,CAAK,SAAS,MAAA,KAAW,GAAA,IAAO,SAAS,MAAA,IAAU,GAAA,KAAQ,UAAU,OAAA,EAAS;AAC5E,QAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,SAAS,YAAA,CAAa,OAAA,EAAS,QAAQ,CAAC,CAAC,CAAA;AACnF,QAAA;AAAA,MACF;AACA,MAAA,OAAO,QAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,SAAA,GAAY,KAAA;AAEZ,MAAA,IAAI,UAAU,OAAA,EAAS;AACrB,QAAA,MAAM,IAAI,QAAQ,CAAC,OAAA,KAAY,WAAW,OAAA,EAAS,YAAA,CAAa,OAAO,CAAC,CAAC,CAAA;AACzE,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,YAAqB,KAAA,GACvB,SAAA,GACA,IAAI,MAAM,mEAAmE,CAAA;AACnF;;;AC7BA,IAAM,gBAAgB,CAAA,GAAI,GAAA;AAM1B,IAAM,iBAAA,GAAoB,wBAAA;AAC1B,IAAM,cAAmC,MAAM;AAC7C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,2BAAW,GAAA,EAAI;AAClD,EAAA,MAAM,CAAA,GAAI,MAAA;AACV,EAAA,OAAQ,CAAA,CAAA,iBAAA,CAAA,KAAA,CAAA,CAAA,iBAAA,CAAA,uBAA6B,GAAA,EAAI,CAAA;AAC3C,CAAA,GAAG;AACH,IAAI,cAAA,GAAiB,KAAA;AAGd,SAAS,aAAa,UAAA,EAA8B;AACzD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,EAAA,GAAK,oBAAoB,UAAU,CAAA;AACzC,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAC/B,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,IAAI,IAAA,CAAK,GAAA,EAAI,IAAK,KAAA,EAAO;AACvB,IAAA,UAAA,CAAW,OAAO,EAAE,CAAA;AACpB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,YAAY,UAAA,EAA2B;AAC9C,EAAA,UAAA,CAAW,IAAI,mBAAA,CAAoB,UAAU,GAAG,IAAA,CAAK,GAAA,KAAQ,aAAa,CAAA;AAC1E,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,cAAA,GAAiB,IAAA;AAGjB,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KACF;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,GAAA,EACA,IAAA,GAAoB,EAAC,EACrB,OAAA,GAA6B,EAAC,EACX;AAGnB,EAAA,IAAI,aAAa,OAAA,CAAQ,MAAA;AACzB,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,WAAA,EAAY,EAAG;AAC7C,IAAA,UAAA,GAAa,MAAM,kBAAA,CAAmB,OAAA,CAAQ,MAAM,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,UAAU,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AAC9C,EAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,gBAAgB,CAAA;AACjD,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,IAAI,aAAY,EAAG;AASjB,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AACnC,MAAA,OAAA,CAAQ,OAAO,eAAe,CAAA;AAAA,IAChC,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,EAAG;AAEpC,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AAAA,IACrC;AAAA,EACF;AAEA,EAAA,IAAI,YAAA,CAAa,UAAU,CAAA,EAAG;AAG5B,IAAA,OAAO,IAAI,SAAS,IAAA,EAAM,EAAE,QAAQ,GAAA,EAAK,UAAA,EAAY,qBAAqB,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,QAAA,GAAW,MAAM,cAAA,CAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAQ,EAAG,OAAO,CAAA;AAKtE,EAAA,IAAA,CACG,QAAA,CAAS,MAAA,KAAW,GAAA,IAAO,QAAA,CAAS,MAAA,KAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,WAAA,CAAY,UAAU,CAAA,EACtB;AACA,IAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,EAAiB;AACzC,IAAA,IAAI,SAAA,IAAa,cAAc,UAAA,EAAY;AACzC,MAAA,MAAM,eAAe,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AACnD,MAAA,YAAA,CAAa,GAAA,CAAI,qBAAqB,gBAAgB,CAAA;AACtD,MAAA,YAAA,CAAa,GAAA,CAAI,aAAa,SAAS,CAAA;AAGvC,MAAA,YAAA,CAAa,OAAO,eAAe,CAAA;AACnC,MAAA,UAAA,GAAa,SAAA;AACb,MAAA,QAAA,GAAW,MAAM,eAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAS,YAAA,EAAa,EAAG,OAAO,CAAA;AAAA,IAClF;AAAA,EACF;AAEA,EAAA,IAAA,CACG,QAAA,CAAS,WAAW,GAAA,IAAO,QAAA,CAAS,WAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,UAAA,EACA;AACA,IAAA,WAAA,CAAY,UAAU,CAAA;AAAA,EACxB;AAEA,EAAA,OAAO,QAAA;AACT;AAYO,SAAS,WAAA,CAAY,GAAA,EAAa,OAAA,EAAkB,MAAA,EAAuB;AAChF,EAAA,MAAM,UAAA,GAAa,kBAAkB,MAAM,CAAA;AAC3C,EAAA,MAAM,OAAO,OAAO,OAAA,KAAY,WAAW,OAAA,GAAU,IAAA,CAAK,UAAU,OAAO,CAAA;AAC3E,EAAA,IAAI;AACF,IAAA,KAAK,MAAM,GAAA,EAAK;AAAA,MACd,MAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,IAAA;AAAA,MACX,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,mBAAA,EAAqB,gBAAA;AAAA,QACrB,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe;AAAC,OAClD;AAAA,MACA;AAAA,KACD,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAEf,CAAC,CAAA;AAAA,EACH,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI;AACF,MAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,SAAA,CAAU,UAAA,EAAY;AAC5D,QAAA,SAAA,CAAU,UAAA,CAAW,GAAA,EAAK,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,kBAAA,EAAoB,CAAC,CAAA;AAAA,MAC1E;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF","file":"chunk-SU42KCQD.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
@@ -50,7 +50,7 @@ function createSpeakableSchema(type, name, url, speakable, additionalProperties
50
50
  var DEFAULT_SPEAKABLE_SELECTORS = {
51
51
  /** Standard page elements */
52
52
  page: ["h1", 'meta[name="description"]', ".page-summary"],
53
- /** Article/blog post elements */
53
+ /** Article/article elements */
54
54
  article: ["h1", ".article-summary", ".article-intro", 'meta[name="description"]'],
55
55
  /** Service page elements */
56
56
  service: ["h1", ".service-overview", ".key-benefits", 'meta[name="description"]'],
@@ -464,5 +464,5 @@ exports.DEFAULT_SPEAKABLE_SELECTORS = DEFAULT_SPEAKABLE_SELECTORS;
464
464
  exports.SpeakableSchema = SpeakableSchema;
465
465
  exports.createSpeakableSchema = createSpeakableSchema;
466
466
  exports.getSpeakableSelectorsForPage = getSpeakableSelectorsForPage;
467
- //# sourceMappingURL=chunk-XEB77DQS.js.map
468
- //# sourceMappingURL=chunk-XEB77DQS.js.map
467
+ //# sourceMappingURL=chunk-TBXWJJGT.js.map
468
+ //# sourceMappingURL=chunk-TBXWJJGT.js.map