@sonordev/site-kit 5.1.0 → 5.3.0

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 (246) hide show
  1. package/README.md +15 -0
  2. package/agent-manifest.json +23 -2
  3. package/dist/AnalyticsProvider-2YV4ZVZD.js +31 -0
  4. package/dist/{AnalyticsProvider-V2U3TQMH.js.map → AnalyticsProvider-2YV4ZVZD.js.map} +1 -1
  5. package/dist/AnalyticsProvider-XSPROVHM.mjs +10 -0
  6. package/dist/{AnalyticsProvider-2LCSA6G3.mjs.map → AnalyticsProvider-XSPROVHM.mjs.map} +1 -1
  7. package/dist/{BlogViewTracker-COTPD7WL.js → BlogViewTracker-BQBJTL5L.js} +6 -6
  8. package/dist/{BlogViewTracker-COTPD7WL.js.map → BlogViewTracker-BQBJTL5L.js.map} +1 -1
  9. package/dist/{BlogViewTracker-HG25ONRP.mjs → BlogViewTracker-FEAXIUEW.mjs} +5 -5
  10. package/dist/{BlogViewTracker-HG25ONRP.mjs.map → BlogViewTracker-FEAXIUEW.mjs.map} +1 -1
  11. package/dist/ChatWidget-B2DI2OWH.mjs +11 -0
  12. package/dist/{ChatWidget-RWO7R7EJ.mjs.map → ChatWidget-B2DI2OWH.mjs.map} +1 -1
  13. package/dist/ChatWidget-GPBFJ4A3.js +20 -0
  14. package/dist/{ChatWidget-4QUJYFXU.js.map → ChatWidget-GPBFJ4A3.js.map} +1 -1
  15. package/dist/EngageWidget-JXJSFONK.js +18 -0
  16. package/dist/{EngageWidget-RSOXB3YM.js.map → EngageWidget-JXJSFONK.js.map} +1 -1
  17. package/dist/EngageWidget-KF4HU4YA.mjs +9 -0
  18. package/dist/{EngageWidget-LF4AC4QN.mjs.map → EngageWidget-KF4HU4YA.mjs.map} +1 -1
  19. package/dist/{FileField-S2GUM3QB.js → FileField-22BB34L3.js} +6 -6
  20. package/dist/{FileField-S2GUM3QB.js.map → FileField-22BB34L3.js.map} +1 -1
  21. package/dist/{FileField-YJ7DZXC3.mjs → FileField-WI6SADBA.mjs} +5 -5
  22. package/dist/{FileField-YJ7DZXC3.mjs.map → FileField-WI6SADBA.mjs.map} +1 -1
  23. package/dist/{FormSpotlight-HLH5LQKI.mjs → FormSpotlight-6FZV6JOA.mjs} +3 -3
  24. package/dist/{FormSpotlight-HLH5LQKI.mjs.map → FormSpotlight-6FZV6JOA.mjs.map} +1 -1
  25. package/dist/{FormSpotlight-7WAXLXZA.js → FormSpotlight-QCIN67NB.js} +15 -15
  26. package/dist/{FormSpotlight-7WAXLXZA.js.map → FormSpotlight-QCIN67NB.js.map} +1 -1
  27. package/dist/{FormStage-Q5JW75AF.mjs → FormStage-P3IIHVCC.mjs} +3 -3
  28. package/dist/{FormStage-Q5JW75AF.mjs.map → FormStage-P3IIHVCC.mjs.map} +1 -1
  29. package/dist/{FormStage-LEZYERJB.js → FormStage-SBQQZDW6.js} +13 -13
  30. package/dist/{FormStage-LEZYERJB.js.map → FormStage-SBQQZDW6.js.map} +1 -1
  31. package/dist/ManagedForm-6LII4DED.js +23 -0
  32. package/dist/{ManagedForm-H4SEFSAF.js.map → ManagedForm-6LII4DED.js.map} +1 -1
  33. package/dist/ManagedForm-O3JRRWZ6.mjs +14 -0
  34. package/dist/{ManagedForm-U3NFIVEC.mjs.map → ManagedForm-O3JRRWZ6.mjs.map} +1 -1
  35. package/dist/{ManagedNewsletterForm-YKYW7B6P.mjs → ManagedNewsletterForm-FFFF5UEX.mjs} +7 -7
  36. package/dist/{ManagedNewsletterForm-YKYW7B6P.mjs.map → ManagedNewsletterForm-FFFF5UEX.mjs.map} +1 -1
  37. package/dist/{ManagedNewsletterForm-KT2NGUNW.js → ManagedNewsletterForm-Z7NYZPAN.js} +8 -8
  38. package/dist/{ManagedNewsletterForm-KT2NGUNW.js.map → ManagedNewsletterForm-Z7NYZPAN.js.map} +1 -1
  39. package/dist/{SignalCore-2BH5PUGS.mjs → SignalCore-IUOSVYUE.mjs} +5 -5
  40. package/dist/{SignalCore-2BH5PUGS.mjs.map → SignalCore-IUOSVYUE.mjs.map} +1 -1
  41. package/dist/{SignalCore-JM4ODFL2.js → SignalCore-WL5WKQEI.js} +11 -11
  42. package/dist/{SignalCore-JM4ODFL2.js.map → SignalCore-WL5WKQEI.js.map} +1 -1
  43. package/dist/SitemapSync-2O5UM4GM.mjs +8 -0
  44. package/dist/{SitemapSync-5CXS2JGB.mjs.map → SitemapSync-2O5UM4GM.mjs.map} +1 -1
  45. package/dist/SitemapSync-LA67GKKS.js +17 -0
  46. package/dist/{SitemapSync-J4UMJRTO.js.map → SitemapSync-LA67GKKS.js.map} +1 -1
  47. package/dist/analytics/index.js +9 -9
  48. package/dist/analytics/index.mjs +4 -4
  49. package/dist/blog/index.js +20 -20
  50. package/dist/blog/index.mjs +3 -3
  51. package/dist/blog/server-ui.js +14 -14
  52. package/dist/blog/server-ui.mjs +2 -2
  53. package/dist/{chunk-MCXHQHIX.js → chunk-2OZ2SMZH.js} +4 -4
  54. package/dist/{chunk-MCXHQHIX.js.map → chunk-2OZ2SMZH.js.map} +1 -1
  55. package/dist/{chunk-FMOOQKZU.js → chunk-3DQXXYCS.js} +8 -8
  56. package/dist/{chunk-FMOOQKZU.js.map → chunk-3DQXXYCS.js.map} +1 -1
  57. package/dist/chunk-3PTKHSF7.mjs +6 -0
  58. package/dist/{chunk-4SQ6PROR.mjs.map → chunk-3PTKHSF7.mjs.map} +1 -1
  59. package/dist/{chunk-HPIY7AR3.js → chunk-4YJFC6FC.js} +14 -14
  60. package/dist/{chunk-HPIY7AR3.js.map → chunk-4YJFC6FC.js.map} +1 -1
  61. package/dist/{chunk-44ZTBQH4.js → chunk-5I7UEXJL.js} +3 -3
  62. package/dist/{chunk-44ZTBQH4.js.map → chunk-5I7UEXJL.js.map} +1 -1
  63. package/dist/{chunk-ACWGG4VW.js → chunk-6ABEAO2K.js} +9 -9
  64. package/dist/{chunk-ACWGG4VW.js.map → chunk-6ABEAO2K.js.map} +1 -1
  65. package/dist/{chunk-OTHKWMDB.mjs → chunk-6RYCAW5B.mjs} +3 -3
  66. package/dist/{chunk-OTHKWMDB.mjs.map → chunk-6RYCAW5B.mjs.map} +1 -1
  67. package/dist/{chunk-CYQB5GLU.mjs → chunk-7MQR4KDL.mjs} +4 -4
  68. package/dist/{chunk-CYQB5GLU.mjs.map → chunk-7MQR4KDL.mjs.map} +1 -1
  69. package/dist/{chunk-CKPFETIO.js → chunk-7NMI7M56.js} +4 -4
  70. package/dist/{chunk-CKPFETIO.js.map → chunk-7NMI7M56.js.map} +1 -1
  71. package/dist/{chunk-6YKNDQYJ.mjs → chunk-7OCQ4646.mjs} +3 -3
  72. package/dist/{chunk-6YKNDQYJ.mjs.map → chunk-7OCQ4646.mjs.map} +1 -1
  73. package/dist/chunk-AEW7EFYU.js +147 -0
  74. package/dist/chunk-AEW7EFYU.js.map +1 -0
  75. package/dist/{chunk-CBMHJPR7.js → chunk-B7FHRTBA.js} +5 -5
  76. package/dist/{chunk-CBMHJPR7.js.map → chunk-B7FHRTBA.js.map} +1 -1
  77. package/dist/{chunk-2XHEUWHV.mjs → chunk-BAFGZQWP.mjs} +4 -4
  78. package/dist/{chunk-2XHEUWHV.mjs.map → chunk-BAFGZQWP.mjs.map} +1 -1
  79. package/dist/chunk-BIZT7AYK.js +8 -0
  80. package/dist/{chunk-YPEX4RDE.js.map → chunk-BIZT7AYK.js.map} +1 -1
  81. package/dist/{chunk-ROJ25QM3.mjs → chunk-D2D3YKWT.mjs} +3 -3
  82. package/dist/{chunk-ROJ25QM3.mjs.map → chunk-D2D3YKWT.mjs.map} +1 -1
  83. package/dist/{chunk-5W56B52Z.mjs → chunk-DXFZA34N.mjs} +3 -3
  84. package/dist/{chunk-5W56B52Z.mjs.map → chunk-DXFZA34N.mjs.map} +1 -1
  85. package/dist/{chunk-ADOGOLYO.mjs → chunk-F4DZH4E5.mjs} +4 -4
  86. package/dist/{chunk-ADOGOLYO.mjs.map → chunk-F4DZH4E5.mjs.map} +1 -1
  87. package/dist/{chunk-VWVUYL5J.mjs → chunk-FDLFXHUU.mjs} +4 -4
  88. package/dist/{chunk-VWVUYL5J.mjs.map → chunk-FDLFXHUU.mjs.map} +1 -1
  89. package/dist/{chunk-TECD4AP7.mjs → chunk-GCY6VPBG.mjs} +3 -3
  90. package/dist/{chunk-TECD4AP7.mjs.map → chunk-GCY6VPBG.mjs.map} +1 -1
  91. package/dist/{chunk-EZLK2OQL.js → chunk-GF7CQMBL.js} +7 -7
  92. package/dist/{chunk-EZLK2OQL.js.map → chunk-GF7CQMBL.js.map} +1 -1
  93. package/dist/{chunk-SXNKLHOX.mjs → chunk-HHX72A2B.mjs} +3 -3
  94. package/dist/{chunk-SXNKLHOX.mjs.map → chunk-HHX72A2B.mjs.map} +1 -1
  95. package/dist/{chunk-ZFZMN4LT.mjs → chunk-ITV76G3L.mjs} +39 -3
  96. package/dist/chunk-ITV76G3L.mjs.map +1 -0
  97. package/dist/{chunk-WZMM57R2.js → chunk-JJ6AI6LW.js} +5 -5
  98. package/dist/{chunk-WZMM57R2.js.map → chunk-JJ6AI6LW.js.map} +1 -1
  99. package/dist/{chunk-UMWVTIIZ.mjs → chunk-K447AIIB.mjs} +8 -8
  100. package/dist/{chunk-UMWVTIIZ.mjs.map → chunk-K447AIIB.mjs.map} +1 -1
  101. package/dist/{chunk-2EJX56YJ.js → chunk-K6YQ73IX.js} +8 -8
  102. package/dist/{chunk-2EJX56YJ.js.map → chunk-K6YQ73IX.js.map} +1 -1
  103. package/dist/{chunk-NIJVVW4G.mjs → chunk-KC2FCI5V.mjs} +3 -3
  104. package/dist/{chunk-NIJVVW4G.mjs.map → chunk-KC2FCI5V.mjs.map} +1 -1
  105. package/dist/{chunk-BUDV7QHH.mjs → chunk-KHHMU4UH.mjs} +4 -4
  106. package/dist/{chunk-BUDV7QHH.mjs.map → chunk-KHHMU4UH.mjs.map} +1 -1
  107. package/dist/{chunk-OAJVITNO.mjs → chunk-KIGIAA6Q.mjs} +3 -3
  108. package/dist/{chunk-OAJVITNO.mjs.map → chunk-KIGIAA6Q.mjs.map} +1 -1
  109. package/dist/{chunk-IQLYFQKZ.js → chunk-KMNIW4K4.js} +15 -15
  110. package/dist/{chunk-IQLYFQKZ.js.map → chunk-KMNIW4K4.js.map} +1 -1
  111. package/dist/{chunk-DTNNZJYM.mjs → chunk-LCRWCOPI.mjs} +4 -4
  112. package/dist/{chunk-DTNNZJYM.mjs.map → chunk-LCRWCOPI.mjs.map} +1 -1
  113. package/dist/{chunk-GYAH4KFC.js → chunk-LFBMFACZ.js} +40 -4
  114. package/dist/chunk-LFBMFACZ.js.map +1 -0
  115. package/dist/chunk-MLXUMVBK.mjs +142 -0
  116. package/dist/chunk-MLXUMVBK.mjs.map +1 -0
  117. package/dist/{chunk-BVLGJ2CZ.mjs → chunk-MOVMDHGE.mjs} +3 -3
  118. package/dist/{chunk-BVLGJ2CZ.mjs.map → chunk-MOVMDHGE.mjs.map} +1 -1
  119. package/dist/{chunk-WMNRTCYY.js → chunk-NQLE3V3T.js} +11 -11
  120. package/dist/{chunk-WMNRTCYY.js.map → chunk-NQLE3V3T.js.map} +1 -1
  121. package/dist/{chunk-EKTMXHBI.mjs → chunk-O2KN2SC7.mjs} +3 -3
  122. package/dist/{chunk-EKTMXHBI.mjs.map → chunk-O2KN2SC7.mjs.map} +1 -1
  123. package/dist/{chunk-BGBJSMXT.js → chunk-OQBDJRVP.js} +6 -6
  124. package/dist/{chunk-BGBJSMXT.js.map → chunk-OQBDJRVP.js.map} +1 -1
  125. package/dist/{chunk-TAM2D54C.js → chunk-PEIWCJXS.js} +14 -14
  126. package/dist/{chunk-TAM2D54C.js.map → chunk-PEIWCJXS.js.map} +1 -1
  127. package/dist/{chunk-CGQO7MFO.js → chunk-RE54UKET.js} +14 -14
  128. package/dist/{chunk-CGQO7MFO.js.map → chunk-RE54UKET.js.map} +1 -1
  129. package/dist/{chunk-VIIGOCX4.js → chunk-RMLRZOVE.js} +15 -15
  130. package/dist/{chunk-VIIGOCX4.js.map → chunk-RMLRZOVE.js.map} +1 -1
  131. package/dist/{chunk-DKRXPXOR.js → chunk-ULGTZ2VU.js} +3 -3
  132. package/dist/{chunk-DKRXPXOR.js.map → chunk-ULGTZ2VU.js.map} +1 -1
  133. package/dist/{chunk-ACLBRIYD.js → chunk-VTEIXKBA.js} +3 -3
  134. package/dist/{chunk-ACLBRIYD.js.map → chunk-VTEIXKBA.js.map} +1 -1
  135. package/dist/{chunk-NSTY7ZZA.mjs → chunk-W7QMCG2A.mjs} +7 -7
  136. package/dist/{chunk-NSTY7ZZA.mjs.map → chunk-W7QMCG2A.mjs.map} +1 -1
  137. package/dist/{chunk-E3GHVAMK.js → chunk-WZ6QNL2D.js} +18 -18
  138. package/dist/{chunk-E3GHVAMK.js.map → chunk-WZ6QNL2D.js.map} +1 -1
  139. package/dist/{chunk-5G2ZRQMO.mjs → chunk-X4H2RAIQ.mjs} +3 -3
  140. package/dist/{chunk-5G2ZRQMO.mjs.map → chunk-X4H2RAIQ.mjs.map} +1 -1
  141. package/dist/{chunk-GZVSGQKT.js → chunk-X6SAM7CR.js} +3 -3
  142. package/dist/{chunk-GZVSGQKT.js.map → chunk-X6SAM7CR.js.map} +1 -1
  143. package/dist/{chunk-ADBLBGCQ.mjs → chunk-XRONA2QL.mjs} +3 -3
  144. package/dist/{chunk-ADBLBGCQ.mjs.map → chunk-XRONA2QL.mjs.map} +1 -1
  145. package/dist/{chunk-VCNPTLK2.js → chunk-XYZZJZGS.js} +12 -12
  146. package/dist/{chunk-VCNPTLK2.js.map → chunk-XYZZJZGS.js.map} +1 -1
  147. package/dist/{chunk-JEI3CSY4.js → chunk-XZI6KUB3.js} +4 -4
  148. package/dist/{chunk-JEI3CSY4.js.map → chunk-XZI6KUB3.js.map} +1 -1
  149. package/dist/{chunk-A3VGZAZQ.mjs → chunk-YDIINUWD.mjs} +3 -3
  150. package/dist/{chunk-A3VGZAZQ.mjs.map → chunk-YDIINUWD.mjs.map} +1 -1
  151. package/dist/{chunk-FZISCIJV.mjs → chunk-YUOVAWCL.mjs} +4 -4
  152. package/dist/{chunk-FZISCIJV.mjs.map → chunk-YUOVAWCL.mjs.map} +1 -1
  153. package/dist/cli/index.js +3 -3
  154. package/dist/cli/index.mjs +1 -1
  155. package/dist/client/index.js +4 -4
  156. package/dist/client/index.mjs +3 -3
  157. package/dist/commerce/index.js +56 -56
  158. package/dist/commerce/index.mjs +5 -5
  159. package/dist/engage/index.js +13 -13
  160. package/dist/engage/index.mjs +7 -7
  161. package/dist/fleet/index.js +8 -8
  162. package/dist/fleet/index.mjs +4 -4
  163. package/dist/forms/ga4-identity.d.ts +31 -0
  164. package/dist/forms/ga4-identity.d.ts.map +1 -0
  165. package/dist/forms/ga4-identity.test.d.ts +2 -0
  166. package/dist/forms/ga4-identity.test.d.ts.map +1 -0
  167. package/dist/forms/index.js +22 -22
  168. package/dist/forms/index.mjs +9 -9
  169. package/dist/forms/server.js +10 -10
  170. package/dist/forms/server.mjs +8 -8
  171. package/dist/forms/static.js +7 -7
  172. package/dist/forms/static.mjs +6 -6
  173. package/dist/forms/submitForm.d.ts.map +1 -1
  174. package/dist/forms/types.d.ts +13 -0
  175. package/dist/forms/types.d.ts.map +1 -1
  176. package/dist/images/index.js +11 -11
  177. package/dist/images/index.mjs +4 -4
  178. package/dist/index.js +63 -63
  179. package/dist/index.mjs +9 -9
  180. package/dist/layout/client.js +8 -8
  181. package/dist/layout/client.mjs +7 -7
  182. package/dist/layout/index.js +10 -10
  183. package/dist/layout/index.mjs +8 -8
  184. package/dist/maps/index.js +5 -5
  185. package/dist/maps/index.mjs +3 -3
  186. package/dist/motion/Parallax.d.ts +42 -0
  187. package/dist/motion/Parallax.d.ts.map +1 -0
  188. package/dist/motion/Reveal.d.ts +59 -0
  189. package/dist/motion/Reveal.d.ts.map +1 -0
  190. package/dist/motion/ScrollScene.d.ts +42 -0
  191. package/dist/motion/ScrollScene.d.ts.map +1 -0
  192. package/dist/motion/engine.d.ts +74 -0
  193. package/dist/motion/engine.d.ts.map +1 -0
  194. package/dist/motion/failsafe.d.ts +20 -0
  195. package/dist/motion/failsafe.d.ts.map +1 -0
  196. package/dist/motion/gsap.d.ts +84 -0
  197. package/dist/motion/gsap.d.ts.map +1 -0
  198. package/dist/motion/gsap.js +103 -0
  199. package/dist/motion/gsap.js.map +1 -0
  200. package/dist/motion/gsap.mjs +99 -0
  201. package/dist/motion/gsap.mjs.map +1 -0
  202. package/dist/motion/index.d.ts +31 -0
  203. package/dist/motion/index.d.ts.map +1 -0
  204. package/dist/motion/index.js +252 -0
  205. package/dist/motion/index.js.map +1 -0
  206. package/dist/motion/index.mjs +230 -0
  207. package/dist/motion/index.mjs.map +1 -0
  208. package/dist/motion/motion.test.d.ts +16 -0
  209. package/dist/motion/motion.test.d.ts.map +1 -0
  210. package/dist/motion/three.d.ts +105 -0
  211. package/dist/motion/three.d.ts.map +1 -0
  212. package/dist/motion/three.js +150 -0
  213. package/dist/motion/three.js.map +1 -0
  214. package/dist/motion/three.mjs +145 -0
  215. package/dist/motion/three.mjs.map +1 -0
  216. package/dist/search/index.js +4 -4
  217. package/dist/search/index.mjs +3 -3
  218. package/dist/seo/client.js +5 -5
  219. package/dist/seo/client.mjs +4 -4
  220. package/dist/seo/index.js +5 -5
  221. package/dist/seo/index.mjs +4 -4
  222. package/dist/server/index.js +3 -3
  223. package/dist/server/index.mjs +2 -2
  224. package/dist/shared/build-entries.d.ts +7 -1
  225. package/dist/shared/build-entries.d.ts.map +1 -1
  226. package/dist/shared/version.d.ts +1 -1
  227. package/dist/signal/index.js +11 -11
  228. package/dist/signal/index.mjs +2 -2
  229. package/dist/sync/index.js +16 -16
  230. package/dist/sync/index.mjs +4 -4
  231. package/package.json +28 -2
  232. package/skills/site-kit/SKILL.md +6 -0
  233. package/dist/AnalyticsProvider-2LCSA6G3.mjs +0 -10
  234. package/dist/AnalyticsProvider-V2U3TQMH.js +0 -31
  235. package/dist/ChatWidget-4QUJYFXU.js +0 -20
  236. package/dist/ChatWidget-RWO7R7EJ.mjs +0 -11
  237. package/dist/EngageWidget-LF4AC4QN.mjs +0 -9
  238. package/dist/EngageWidget-RSOXB3YM.js +0 -18
  239. package/dist/ManagedForm-H4SEFSAF.js +0 -23
  240. package/dist/ManagedForm-U3NFIVEC.mjs +0 -14
  241. package/dist/SitemapSync-5CXS2JGB.mjs +0 -8
  242. package/dist/SitemapSync-J4UMJRTO.js +0 -17
  243. package/dist/chunk-4SQ6PROR.mjs +0 -6
  244. package/dist/chunk-GYAH4KFC.js.map +0 -1
  245. package/dist/chunk-YPEX4RDE.js +0 -8
  246. package/dist/chunk-ZFZMN4LT.mjs.map +0 -1
package/README.md CHANGED
@@ -113,6 +113,16 @@ Every module has its own README in `src/<module>/README.md` with full API docs,
113
113
  | **LLMs** | `@sonordev/site-kit/llms` | llms.txt, AEO components, Speakable schema | [README](src/llms/README.md) |
114
114
  | **LLMs Contract** | `@sonordev/site-kit/llms/contract` | Shared types/sanitizers for API consumers | [README](src/llms/LLM_GEO_CONTRACT.md) |
115
115
 
116
+ ### Motion
117
+
118
+ Three tiers as three subpaths — a site only installs and ships what it imports. Server HTML always stays visible; above-the-fold content is never animated in.
119
+
120
+ | Module | Import | Purpose | Docs |
121
+ |--------|--------|---------|------|
122
+ | **Motion** | `@sonordev/site-kit/motion` | `<Reveal>`, `<Parallax>`, `<ScrollScene>` and the scroll engine — zero deps, ~2KB | [README](src/motion/README.md) |
123
+ | **Motion / GSAP** | `@sonordev/site-kit/motion/gsap` | Lazy-loaded GSAP timelines (`useGsap`) — optional peer `gsap` | [README](src/motion/README.md) |
124
+ | **Motion / three** | `@sonordev/site-kit/motion/three` | WebGL stages on the engine (`useThreeStage`) — optional peer `three` | [README](src/motion/README.md) |
125
+
116
126
  ---
117
127
 
118
128
  ## How It Works
@@ -195,6 +205,11 @@ import { handleManagedRedirects } from '@sonordev/site-kit/redirects'
195
205
  // Robots
196
206
  import { createRobots } from '@sonordev/site-kit/robots'
197
207
 
208
+ // Motion (tier 0, zero deps) — tiers 1/2 need `npm i gsap` / `npm i three`
209
+ import { Reveal, Parallax, ScrollScene, registerScene } from '@sonordev/site-kit/motion'
210
+ import { useGsap } from '@sonordev/site-kit/motion/gsap'
211
+ import { useThreeStage, canRunWebGL } from '@sonordev/site-kit/motion/three'
212
+
198
213
  // Styles (optional)
199
214
  import '@sonordev/site-kit/brand.css'
200
215
  import '@sonordev/site-kit/forms/styles.css'
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@sonordev/site-kit",
4
- "version": "5.1.0",
4
+ "version": "5.3.0",
5
5
  "description": "Machine-readable manifest for coding agents integrating a site with Sonor. See AGENTS.md (shipped) and docs/SITE-KIT-AGENT-NATIVE.md.",
6
6
  "env": {
7
7
  "required": [
@@ -377,6 +377,27 @@
377
377
  "side": "client",
378
378
  "purpose": "Google Maps via Sonor API."
379
379
  },
380
+ {
381
+ "name": "motion",
382
+ "import": "@sonordev/site-kit/motion",
383
+ "side": "client",
384
+ "purpose": "The Upforge motion standard, tier 0: zero-dependency <Reveal>, <Parallax>, <ScrollScene> and the scroll engine.",
385
+ "caution": "Opt-in per element, never a wrapper around the page. Server HTML stays fully visible; above-the-fold content is never animated in; a headless renderer that never scrolls gets the content back after 2.5s. Do not load a component containing <ScrollScene> with dynamic(ssr:false)."
386
+ },
387
+ {
388
+ "name": "motion/gsap",
389
+ "import": "@sonordev/site-kit/motion/gsap",
390
+ "side": "client",
391
+ "purpose": "Tier 1: lazy-loaded GSAP (useGsap, loadGsap, useExpandCollapse). Optional peer `gsap` — a site must `npm i gsap` before importing this subpath.",
392
+ "caution": "Below the fold only; gsap loads when the element nears the viewport, never on the LCP path. ScrollSmoother is deliberately excluded (native scroll only)."
393
+ },
394
+ {
395
+ "name": "motion/three",
396
+ "import": "@sonordev/site-kit/motion/three",
397
+ "side": "client",
398
+ "purpose": "Tier 2: three.js stages on the scroll engine (useThreeStage, canRunWebGL, loadTexture). Optional peer `three` — flagship builds only (~180KB gz).",
399
+ "caution": "Mount the scene as a dynamic(ssr:false) CHILDLESS sibling of the server-rendered stage, never wrapping it. Copy lives in the DOM, never in the canvas. canRunWebGL() false = keep the server-rendered still."
400
+ },
380
401
  {
381
402
  "name": "client",
382
403
  "import": "@sonordev/site-kit/client",
@@ -442,7 +463,7 @@
442
463
  "id": "lcp.animated-hero",
443
464
  "symptom": "LCP is late even though content is present.",
444
465
  "cause": "The above-the-fold hero is opacity:0 + JS entrance-animated, so Chrome records LCP at the tween's end (or never).",
445
- "fix": "Render hero/first-paint content fully static; gate reveal animations to below-the-fold only.",
466
+ "fix": "Render hero/first-paint content fully static; gate reveal animations to below-the-fold only. <Reveal> from @sonordev/site-kit/motion does this by construction (above-the-fold elements are never touched).",
446
467
  "check": null
447
468
  }
448
469
  ]
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ var chunk4YJFC6FC_js = require('./chunk-4YJFC6FC.js');
4
+ require('./chunk-HMFFAUCD.js');
5
+ require('./chunk-RC4WWQXR.js');
6
+ require('./chunk-VJMPIRSD.js');
7
+ require('./chunk-RMLRZOVE.js');
8
+ require('./chunk-7NMI7M56.js');
9
+ require('./chunk-BIZT7AYK.js');
10
+ require('./chunk-SNAQBZPT.js');
11
+
12
+
13
+
14
+ Object.defineProperty(exports, "AnalyticsProvider", {
15
+ enumerable: true,
16
+ get: function () { return chunk4YJFC6FC_js.AnalyticsProvider; }
17
+ });
18
+ Object.defineProperty(exports, "useAnalytics", {
19
+ enumerable: true,
20
+ get: function () { return chunk4YJFC6FC_js.useAnalytics; }
21
+ });
22
+ Object.defineProperty(exports, "useAnalyticsOptional", {
23
+ enumerable: true,
24
+ get: function () { return chunk4YJFC6FC_js.useAnalyticsOptional; }
25
+ });
26
+ Object.defineProperty(exports, "useTrackEvent", {
27
+ enumerable: true,
28
+ get: function () { return chunk4YJFC6FC_js.useTrackEvent; }
29
+ });
30
+ //# sourceMappingURL=AnalyticsProvider-2YV4ZVZD.js.map
31
+ //# sourceMappingURL=AnalyticsProvider-2YV4ZVZD.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsProvider-V2U3TQMH.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsProvider-2YV4ZVZD.js"}
@@ -0,0 +1,10 @@
1
+ export { AnalyticsProvider, useAnalytics, useAnalyticsOptional, useTrackEvent } from './chunk-KIGIAA6Q.mjs';
2
+ import './chunk-3DPXOZAU.mjs';
3
+ import './chunk-ZZQAGCSI.mjs';
4
+ import './chunk-6EDVCUAG.mjs';
5
+ import './chunk-F4DZH4E5.mjs';
6
+ import './chunk-HHX72A2B.mjs';
7
+ import './chunk-3PTKHSF7.mjs';
8
+ import './chunk-BEXTDHO4.mjs';
9
+ //# sourceMappingURL=AnalyticsProvider-XSPROVHM.mjs.map
10
+ //# sourceMappingURL=AnalyticsProvider-XSPROVHM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsProvider-2LCSA6G3.mjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"AnalyticsProvider-XSPROVHM.mjs"}
@@ -2,9 +2,9 @@
2
2
  'use strict';
3
3
 
4
4
  var chunkHGNSJXWT_js = require('./chunk-HGNSJXWT.js');
5
- var chunkVIIGOCX4_js = require('./chunk-VIIGOCX4.js');
6
- require('./chunk-CKPFETIO.js');
7
- require('./chunk-YPEX4RDE.js');
5
+ var chunkRMLRZOVE_js = require('./chunk-RMLRZOVE.js');
6
+ require('./chunk-7NMI7M56.js');
7
+ require('./chunk-BIZT7AYK.js');
8
8
  require('./chunk-SNAQBZPT.js');
9
9
  var react = require('react');
10
10
 
@@ -19,7 +19,7 @@ function trackBlogView(slug) {
19
19
  if (sessionStorage.getItem(key)) return;
20
20
  } catch {
21
21
  }
22
- void chunkVIIGOCX4_js.sonorFetch(
22
+ void chunkRMLRZOVE_js.sonorFetch(
23
23
  `${baseUrl}/public/blog/view`,
24
24
  {
25
25
  method: "POST",
@@ -47,5 +47,5 @@ function BlogViewTracker({ slug }) {
47
47
 
48
48
  exports.BlogViewTracker = BlogViewTracker;
49
49
  exports.trackBlogView = trackBlogView;
50
- //# sourceMappingURL=BlogViewTracker-COTPD7WL.js.map
51
- //# sourceMappingURL=BlogViewTracker-COTPD7WL.js.map
50
+ //# sourceMappingURL=BlogViewTracker-BQBJTL5L.js.map
51
+ //# sourceMappingURL=BlogViewTracker-BQBJTL5L.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blog/BlogViewTracker.tsx"],"names":["sonorFetch","useDeferredActivation","useEffect"],"mappings":";;;;;;;;;AAmBA,IAAM,kBAAA,GAAqB,wBAAA;AAUpB,SAAS,cAAc,IAAA,EAAoB;AAChD,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,EAAa;AAI5C,EAAA,MAAM,CAAA,GAAI,MAAA;AAIV,EAAA,MAAM,UAAU,CAAA,CAAE,oBAAA;AAClB,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,CAAA,CAAE,oBAAA,EAAsB;AAKzC,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,kBAAkB,CAAA,EAAG,IAAI,CAAA,CAAA;AACxC,EAAA,IAAI;AACF,IAAA,IAAI,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,EAAG;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAER;AAIA,EAAA,KAAKA,2BAAA;AAAA,IACH,GAAG,OAAO,CAAA,iBAAA,CAAA;AAAA,IACV;AAAA,MACE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,MAAM;AAAA,KAC/B;AAAA,IACA,EAAE,SAAS,CAAA;AAAE,GACf,CACG,IAAA,CAAK,CAAC,GAAA,KAAQ;AACb,IAAA,IAAI,IAAI,EAAA,EAAI;AACV,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,MACjC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,EAEb,CAAC,CAAA;AACL;AAEO,SAAS,eAAA,CAAgB,EAAE,IAAA,EAAK,EAAyB;AAC9D,EAAA,MAAM,KAAA,GAAQC,uCAAsB,IAAI,CAAA;AAExC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,gBAAqB,IAAI,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,KAAA,EAAO,IAAI,CAAC,CAAA;AAEhB,EAAA,OAAO,IAAA;AACT","file":"BlogViewTracker-COTPD7WL.js","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit blog — reader-side view tracking\n *\n * Counts actual readers by POSTing to /public/blog/view once per post per\n * browser session. This replaces the server-side increment that rode\n * GET posts/:slug — under ISR that GET fires on REVALIDATION, not on reads,\n * so view_count was measuring cache churn instead of people.\n *\n * Childless by design (renders null, never wraps children) and gated behind\n * the same idle deferral SiteKitLayout uses, so it stays out of the critical\n * hydration window.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nconst SESSION_KEY_PREFIX = '__sonor_blog_viewed__:'\n\nexport interface BlogViewTrackerProps {\n slug: string\n}\n\n/**\n * The effect body, exported for tests (the package's vitest runs in node, so\n * the component's mount path can't be exercised directly).\n */\nexport function trackBlogView(slug: string): void {\n if (!slug || typeof window === 'undefined') return\n\n // SiteKitLayout globals; the credential is the minted st1. token, never\n // the raw key. Without SiteKitLayout the globals are absent — no-op.\n const w = window as Window & {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n }\n const baseUrl = w.__SITE_KIT_API_URL__\n if (!baseUrl || !w.__SITE_KIT_API_KEY__) return\n\n // One view per post per session. sessionStorage can throw (private\n // windows, storage-blocked browsers) — treat a throw as \"not yet viewed\"\n // and skip writing rather than skip counting.\n const key = `${SESSION_KEY_PREFIX}${slug}`\n try {\n if (sessionStorage.getItem(key)) return\n } catch {\n /* unreadable storage — count the view, skip dedupe */\n }\n\n // retries: 0 — the increment is not idempotent, so a retried request\n // would double-count.\n void sonorFetch(\n `${baseUrl}/public/blog/view`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug }),\n },\n { retries: 0 }\n )\n .then((res) => {\n if (res.ok) {\n try {\n sessionStorage.setItem(key, '1')\n } catch {\n /* storage write blocked — worst case is a re-count next mount */\n }\n }\n })\n .catch(() => {\n /* view tracking is best-effort; never surface errors */\n })\n}\n\nexport function BlogViewTracker({ slug }: BlogViewTrackerProps) {\n const ready = useDeferredActivation(true)\n\n useEffect(() => {\n if (ready) trackBlogView(slug)\n }, [ready, slug])\n\n return null\n}\n"]}
1
+ {"version":3,"sources":["../src/blog/BlogViewTracker.tsx"],"names":["sonorFetch","useDeferredActivation","useEffect"],"mappings":";;;;;;;;;AAmBA,IAAM,kBAAA,GAAqB,wBAAA;AAUpB,SAAS,cAAc,IAAA,EAAoB;AAChD,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,EAAa;AAI5C,EAAA,MAAM,CAAA,GAAI,MAAA;AAIV,EAAA,MAAM,UAAU,CAAA,CAAE,oBAAA;AAClB,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,CAAA,CAAE,oBAAA,EAAsB;AAKzC,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,kBAAkB,CAAA,EAAG,IAAI,CAAA,CAAA;AACxC,EAAA,IAAI;AACF,IAAA,IAAI,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,EAAG;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAER;AAIA,EAAA,KAAKA,2BAAA;AAAA,IACH,GAAG,OAAO,CAAA,iBAAA,CAAA;AAAA,IACV;AAAA,MACE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,MAAM;AAAA,KAC/B;AAAA,IACA,EAAE,SAAS,CAAA;AAAE,GACf,CACG,IAAA,CAAK,CAAC,GAAA,KAAQ;AACb,IAAA,IAAI,IAAI,EAAA,EAAI;AACV,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,MACjC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,EAEb,CAAC,CAAA;AACL;AAEO,SAAS,eAAA,CAAgB,EAAE,IAAA,EAAK,EAAyB;AAC9D,EAAA,MAAM,KAAA,GAAQC,uCAAsB,IAAI,CAAA;AAExC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,gBAAqB,IAAI,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,KAAA,EAAO,IAAI,CAAC,CAAA;AAEhB,EAAA,OAAO,IAAA;AACT","file":"BlogViewTracker-BQBJTL5L.js","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit blog — reader-side view tracking\n *\n * Counts actual readers by POSTing to /public/blog/view once per post per\n * browser session. This replaces the server-side increment that rode\n * GET posts/:slug — under ISR that GET fires on REVALIDATION, not on reads,\n * so view_count was measuring cache churn instead of people.\n *\n * Childless by design (renders null, never wraps children) and gated behind\n * the same idle deferral SiteKitLayout uses, so it stays out of the critical\n * hydration window.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nconst SESSION_KEY_PREFIX = '__sonor_blog_viewed__:'\n\nexport interface BlogViewTrackerProps {\n slug: string\n}\n\n/**\n * The effect body, exported for tests (the package's vitest runs in node, so\n * the component's mount path can't be exercised directly).\n */\nexport function trackBlogView(slug: string): void {\n if (!slug || typeof window === 'undefined') return\n\n // SiteKitLayout globals; the credential is the minted st1. token, never\n // the raw key. Without SiteKitLayout the globals are absent — no-op.\n const w = window as Window & {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n }\n const baseUrl = w.__SITE_KIT_API_URL__\n if (!baseUrl || !w.__SITE_KIT_API_KEY__) return\n\n // One view per post per session. sessionStorage can throw (private\n // windows, storage-blocked browsers) — treat a throw as \"not yet viewed\"\n // and skip writing rather than skip counting.\n const key = `${SESSION_KEY_PREFIX}${slug}`\n try {\n if (sessionStorage.getItem(key)) return\n } catch {\n /* unreadable storage — count the view, skip dedupe */\n }\n\n // retries: 0 — the increment is not idempotent, so a retried request\n // would double-count.\n void sonorFetch(\n `${baseUrl}/public/blog/view`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug }),\n },\n { retries: 0 }\n )\n .then((res) => {\n if (res.ok) {\n try {\n sessionStorage.setItem(key, '1')\n } catch {\n /* storage write blocked — worst case is a re-count next mount */\n }\n }\n })\n .catch(() => {\n /* view tracking is best-effort; never surface errors */\n })\n}\n\nexport function BlogViewTracker({ slug }: BlogViewTrackerProps) {\n const ready = useDeferredActivation(true)\n\n useEffect(() => {\n if (ready) trackBlogView(slug)\n }, [ready, slug])\n\n return null\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
3
- import { sonorFetch } from './chunk-ADOGOLYO.mjs';
4
- import './chunk-SXNKLHOX.mjs';
5
- import './chunk-4SQ6PROR.mjs';
3
+ import { sonorFetch } from './chunk-F4DZH4E5.mjs';
4
+ import './chunk-HHX72A2B.mjs';
5
+ import './chunk-3PTKHSF7.mjs';
6
6
  import './chunk-BEXTDHO4.mjs';
7
7
  import { useEffect } from 'react';
8
8
 
@@ -44,5 +44,5 @@ function BlogViewTracker({ slug }) {
44
44
  }
45
45
 
46
46
  export { BlogViewTracker, trackBlogView };
47
- //# sourceMappingURL=BlogViewTracker-HG25ONRP.mjs.map
48
- //# sourceMappingURL=BlogViewTracker-HG25ONRP.mjs.map
47
+ //# sourceMappingURL=BlogViewTracker-FEAXIUEW.mjs.map
48
+ //# sourceMappingURL=BlogViewTracker-FEAXIUEW.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blog/BlogViewTracker.tsx"],"names":[],"mappings":";;;;;;;AAmBA,IAAM,kBAAA,GAAqB,wBAAA;AAUpB,SAAS,cAAc,IAAA,EAAoB;AAChD,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,EAAa;AAI5C,EAAA,MAAM,CAAA,GAAI,MAAA;AAIV,EAAA,MAAM,UAAU,CAAA,CAAE,oBAAA;AAClB,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,CAAA,CAAE,oBAAA,EAAsB;AAKzC,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,kBAAkB,CAAA,EAAG,IAAI,CAAA,CAAA;AACxC,EAAA,IAAI;AACF,IAAA,IAAI,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,EAAG;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAER;AAIA,EAAA,KAAK,UAAA;AAAA,IACH,GAAG,OAAO,CAAA,iBAAA,CAAA;AAAA,IACV;AAAA,MACE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,MAAM;AAAA,KAC/B;AAAA,IACA,EAAE,SAAS,CAAA;AAAE,GACf,CACG,IAAA,CAAK,CAAC,GAAA,KAAQ;AACb,IAAA,IAAI,IAAI,EAAA,EAAI;AACV,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,MACjC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,EAEb,CAAC,CAAA;AACL;AAEO,SAAS,eAAA,CAAgB,EAAE,IAAA,EAAK,EAAyB;AAC9D,EAAA,MAAM,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAExC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,gBAAqB,IAAI,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,KAAA,EAAO,IAAI,CAAC,CAAA;AAEhB,EAAA,OAAO,IAAA;AACT","file":"BlogViewTracker-HG25ONRP.mjs","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit blog — reader-side view tracking\n *\n * Counts actual readers by POSTing to /public/blog/view once per post per\n * browser session. This replaces the server-side increment that rode\n * GET posts/:slug — under ISR that GET fires on REVALIDATION, not on reads,\n * so view_count was measuring cache churn instead of people.\n *\n * Childless by design (renders null, never wraps children) and gated behind\n * the same idle deferral SiteKitLayout uses, so it stays out of the critical\n * hydration window.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nconst SESSION_KEY_PREFIX = '__sonor_blog_viewed__:'\n\nexport interface BlogViewTrackerProps {\n slug: string\n}\n\n/**\n * The effect body, exported for tests (the package's vitest runs in node, so\n * the component's mount path can't be exercised directly).\n */\nexport function trackBlogView(slug: string): void {\n if (!slug || typeof window === 'undefined') return\n\n // SiteKitLayout globals; the credential is the minted st1. token, never\n // the raw key. Without SiteKitLayout the globals are absent — no-op.\n const w = window as Window & {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n }\n const baseUrl = w.__SITE_KIT_API_URL__\n if (!baseUrl || !w.__SITE_KIT_API_KEY__) return\n\n // One view per post per session. sessionStorage can throw (private\n // windows, storage-blocked browsers) — treat a throw as \"not yet viewed\"\n // and skip writing rather than skip counting.\n const key = `${SESSION_KEY_PREFIX}${slug}`\n try {\n if (sessionStorage.getItem(key)) return\n } catch {\n /* unreadable storage — count the view, skip dedupe */\n }\n\n // retries: 0 — the increment is not idempotent, so a retried request\n // would double-count.\n void sonorFetch(\n `${baseUrl}/public/blog/view`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug }),\n },\n { retries: 0 }\n )\n .then((res) => {\n if (res.ok) {\n try {\n sessionStorage.setItem(key, '1')\n } catch {\n /* storage write blocked — worst case is a re-count next mount */\n }\n }\n })\n .catch(() => {\n /* view tracking is best-effort; never surface errors */\n })\n}\n\nexport function BlogViewTracker({ slug }: BlogViewTrackerProps) {\n const ready = useDeferredActivation(true)\n\n useEffect(() => {\n if (ready) trackBlogView(slug)\n }, [ready, slug])\n\n return null\n}\n"]}
1
+ {"version":3,"sources":["../src/blog/BlogViewTracker.tsx"],"names":[],"mappings":";;;;;;;AAmBA,IAAM,kBAAA,GAAqB,wBAAA;AAUpB,SAAS,cAAc,IAAA,EAAoB;AAChD,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,EAAa;AAI5C,EAAA,MAAM,CAAA,GAAI,MAAA;AAIV,EAAA,MAAM,UAAU,CAAA,CAAE,oBAAA;AAClB,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,CAAA,CAAE,oBAAA,EAAsB;AAKzC,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,kBAAkB,CAAA,EAAG,IAAI,CAAA,CAAA;AACxC,EAAA,IAAI;AACF,IAAA,IAAI,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,EAAG;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAER;AAIA,EAAA,KAAK,UAAA;AAAA,IACH,GAAG,OAAO,CAAA,iBAAA,CAAA;AAAA,IACV;AAAA,MACE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,MAAM;AAAA,KAC/B;AAAA,IACA,EAAE,SAAS,CAAA;AAAE,GACf,CACG,IAAA,CAAK,CAAC,GAAA,KAAQ;AACb,IAAA,IAAI,IAAI,EAAA,EAAI;AACV,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,MACjC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,EAEb,CAAC,CAAA;AACL;AAEO,SAAS,eAAA,CAAgB,EAAE,IAAA,EAAK,EAAyB;AAC9D,EAAA,MAAM,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAExC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,gBAAqB,IAAI,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,KAAA,EAAO,IAAI,CAAC,CAAA;AAEhB,EAAA,OAAO,IAAA;AACT","file":"BlogViewTracker-FEAXIUEW.mjs","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit blog — reader-side view tracking\n *\n * Counts actual readers by POSTing to /public/blog/view once per post per\n * browser session. This replaces the server-side increment that rode\n * GET posts/:slug — under ISR that GET fires on REVALIDATION, not on reads,\n * so view_count was measuring cache churn instead of people.\n *\n * Childless by design (renders null, never wraps children) and gated behind\n * the same idle deferral SiteKitLayout uses, so it stays out of the critical\n * hydration window.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nconst SESSION_KEY_PREFIX = '__sonor_blog_viewed__:'\n\nexport interface BlogViewTrackerProps {\n slug: string\n}\n\n/**\n * The effect body, exported for tests (the package's vitest runs in node, so\n * the component's mount path can't be exercised directly).\n */\nexport function trackBlogView(slug: string): void {\n if (!slug || typeof window === 'undefined') return\n\n // SiteKitLayout globals; the credential is the minted st1. token, never\n // the raw key. Without SiteKitLayout the globals are absent — no-op.\n const w = window as Window & {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n }\n const baseUrl = w.__SITE_KIT_API_URL__\n if (!baseUrl || !w.__SITE_KIT_API_KEY__) return\n\n // One view per post per session. sessionStorage can throw (private\n // windows, storage-blocked browsers) — treat a throw as \"not yet viewed\"\n // and skip writing rather than skip counting.\n const key = `${SESSION_KEY_PREFIX}${slug}`\n try {\n if (sessionStorage.getItem(key)) return\n } catch {\n /* unreadable storage — count the view, skip dedupe */\n }\n\n // retries: 0 — the increment is not idempotent, so a retried request\n // would double-count.\n void sonorFetch(\n `${baseUrl}/public/blog/view`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slug }),\n },\n { retries: 0 }\n )\n .then((res) => {\n if (res.ok) {\n try {\n sessionStorage.setItem(key, '1')\n } catch {\n /* storage write blocked — worst case is a re-count next mount */\n }\n }\n })\n .catch(() => {\n /* view tracking is best-effort; never surface errors */\n })\n}\n\nexport function BlogViewTracker({ slug }: BlogViewTrackerProps) {\n const ready = useDeferredActivation(true)\n\n useEffect(() => {\n if (ready) trackBlogView(slug)\n }, [ready, slug])\n\n return null\n}\n"]}
@@ -0,0 +1,11 @@
1
+ export { ChatWidget } from './chunk-BAFGZQWP.mjs';
2
+ import './chunk-KHHMU4UH.mjs';
3
+ import './chunk-ITV76G3L.mjs';
4
+ import './chunk-TMVKRQGJ.mjs';
5
+ import './chunk-6EDVCUAG.mjs';
6
+ import './chunk-F4DZH4E5.mjs';
7
+ import './chunk-HHX72A2B.mjs';
8
+ import './chunk-3PTKHSF7.mjs';
9
+ import './chunk-BEXTDHO4.mjs';
10
+ //# sourceMappingURL=ChatWidget-B2DI2OWH.mjs.map
11
+ //# sourceMappingURL=ChatWidget-B2DI2OWH.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"ChatWidget-RWO7R7EJ.mjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"ChatWidget-B2DI2OWH.mjs"}
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ var chunkKMNIW4K4_js = require('./chunk-KMNIW4K4.js');
4
+ require('./chunk-XYZZJZGS.js');
5
+ require('./chunk-LFBMFACZ.js');
6
+ require('./chunk-KHPJ3CEL.js');
7
+ require('./chunk-VJMPIRSD.js');
8
+ require('./chunk-RMLRZOVE.js');
9
+ require('./chunk-7NMI7M56.js');
10
+ require('./chunk-BIZT7AYK.js');
11
+ require('./chunk-SNAQBZPT.js');
12
+
13
+
14
+
15
+ Object.defineProperty(exports, "ChatWidget", {
16
+ enumerable: true,
17
+ get: function () { return chunkKMNIW4K4_js.ChatWidget; }
18
+ });
19
+ //# sourceMappingURL=ChatWidget-GPBFJ4A3.js.map
20
+ //# sourceMappingURL=ChatWidget-GPBFJ4A3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"ChatWidget-4QUJYFXU.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"ChatWidget-GPBFJ4A3.js"}
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var chunkOQBDJRVP_js = require('./chunk-OQBDJRVP.js');
4
+ require('./chunk-RC4WWQXR.js');
5
+ require('./chunk-VJMPIRSD.js');
6
+ require('./chunk-RMLRZOVE.js');
7
+ require('./chunk-7NMI7M56.js');
8
+ require('./chunk-BIZT7AYK.js');
9
+ require('./chunk-SNAQBZPT.js');
10
+
11
+
12
+
13
+ Object.defineProperty(exports, "EngageWidget", {
14
+ enumerable: true,
15
+ get: function () { return chunkOQBDJRVP_js.EngageWidget; }
16
+ });
17
+ //# sourceMappingURL=EngageWidget-JXJSFONK.js.map
18
+ //# sourceMappingURL=EngageWidget-JXJSFONK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"EngageWidget-RSOXB3YM.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"EngageWidget-JXJSFONK.js"}
@@ -0,0 +1,9 @@
1
+ export { EngageWidget } from './chunk-YUOVAWCL.mjs';
2
+ import './chunk-ZZQAGCSI.mjs';
3
+ import './chunk-6EDVCUAG.mjs';
4
+ import './chunk-F4DZH4E5.mjs';
5
+ import './chunk-HHX72A2B.mjs';
6
+ import './chunk-3PTKHSF7.mjs';
7
+ import './chunk-BEXTDHO4.mjs';
8
+ //# sourceMappingURL=EngageWidget-KF4HU4YA.mjs.map
9
+ //# sourceMappingURL=EngageWidget-KF4HU4YA.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"EngageWidget-LF4AC4QN.mjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"EngageWidget-KF4HU4YA.mjs"}
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkVIIGOCX4_js = require('./chunk-VIIGOCX4.js');
5
- require('./chunk-CKPFETIO.js');
6
- require('./chunk-YPEX4RDE.js');
4
+ var chunkRMLRZOVE_js = require('./chunk-RMLRZOVE.js');
5
+ require('./chunk-7NMI7M56.js');
6
+ require('./chunk-BIZT7AYK.js');
7
7
  require('./chunk-SNAQBZPT.js');
8
8
  var React = require('react');
9
9
  var jsxRuntime = require('react/jsx-runtime');
@@ -67,7 +67,7 @@ async function uploadFormFile(file) {
67
67
  }
68
68
  const body = new FormData();
69
69
  body.append("file", file);
70
- const response = await chunkVIIGOCX4_js.sonorFetch(
70
+ const response = await chunkRMLRZOVE_js.sonorFetch(
71
71
  `${apiUrl}/api/public/forms/upload`,
72
72
  {
73
73
  method: "POST",
@@ -87,5 +87,5 @@ async function uploadFormFile(file) {
87
87
  }
88
88
 
89
89
  exports.FileField = FileField;
90
- //# sourceMappingURL=FileField-S2GUM3QB.js.map
91
- //# sourceMappingURL=FileField-S2GUM3QB.js.map
90
+ //# sourceMappingURL=FileField-22BB34L3.js.map
91
+ //# sourceMappingURL=FileField-22BB34L3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/forms/FileField.tsx"],"names":["React","jsxs","Fragment","jsx","sonorFetch"],"mappings":"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","file":"FileField-S2GUM3QB.js","sourcesContent":["'use client'\n\n/**\n * File upload field — the one field type that cannot render on the server.\n *\n * It holds upload state and reads the browser-published credential globals, so\n * it lives here rather than in FormField.tsx. Pulling it out is what lets\n * FormField drop its `'use client'` directive and render inside a Server\n * Component (see StaticForm.tsx); a form WITHOUT a file field now ships no\n * field-rendering JavaScript at all.\n *\n * Uploads the chosen file to the Sonor API, which stores it and returns a\n * public URL — that URL becomes the field value, so large files never touch\n * the JSON submission body. The value stays empty until the upload succeeds,\n * so a required file field correctly blocks submission until the upload\n * completes (rather than submitting a useless bare filename).\n *\n * Note: forms containing a file field are NOT native-POST eligible (see\n * native-fallback.tsx) — a form-encoded POST cannot run this upload — so the\n * server shell renders a plain disabled-looking file input and the real\n * control arrives with the enhancement.\n */\n\nimport React from 'react'\nimport type { FormField as FormFieldType } from './types'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nexport interface FileFieldProps {\n field: FormFieldType\n inputId: string\n classPrefix: string\n baseInputStyle: React.CSSProperties\n onChange: (value: unknown) => void\n}\n\nexport function FileField({\n field,\n inputId,\n classPrefix,\n baseInputStyle,\n onChange,\n}: FileFieldProps) {\n const [status, setStatus] = React.useState<'idle' | 'uploading' | 'error'>('idle')\n const [fileName, setFileName] = React.useState('')\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0]\n if (!file) return\n setFileName(file.name)\n setStatus('uploading')\n onChange('') // clear any prior URL until this upload completes\n try {\n const url = await uploadFormFile(file)\n onChange(url)\n setStatus('idle')\n } catch (err) {\n console.error('[Forms] File upload failed:', err)\n onChange('')\n setStatus('error')\n }\n }\n\n return (\n <>\n <input\n id={inputId}\n className={`${classPrefix}__input ${classPrefix}__input--file`}\n type=\"file\"\n name={field.slug}\n required={field.is_required}\n disabled={status === 'uploading'}\n onChange={handleFile}\n style={baseInputStyle}\n />\n {status === 'uploading' && (\n <p style={{ color: 'var(--sk-text-tertiary, #6b7280)', fontSize: 14, marginTop: 4 }}>\n Uploading {fileName}…\n </p>\n )}\n {status === 'error' && (\n <p role=\"alert\" style={{ color: 'var(--sk-error, #dc2626)', fontSize: 14, marginTop: 4 }}>\n Upload failed. Please choose the file again.\n </p>\n )}\n </>\n )\n}\n\n/** Upload a single file to the Sonor API and return its stored public URL. */\nasync function uploadFormFile(file: File): Promise<string> {\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n if (!apiKey) {\n throw new Error('API key is required to upload files.')\n }\n\n const body = new FormData()\n body.append('file', file)\n\n // No Content-Type header — the browser sets the multipart boundary itself.\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/upload`,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}` },\n body,\n },\n { retries: 1, timeoutMs: 60000 },\n )\n\n if (!response.ok) {\n throw new Error(`Upload failed (${response.status})`)\n }\n const data = (await response.json().catch(() => null)) as { url?: string } | null\n if (!data?.url) {\n throw new Error('Upload did not return a URL.')\n }\n return data.url\n}\n"]}
1
+ {"version":3,"sources":["../src/forms/FileField.tsx"],"names":["React","jsxs","Fragment","jsx","sonorFetch"],"mappings":"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","file":"FileField-22BB34L3.js","sourcesContent":["'use client'\n\n/**\n * File upload field — the one field type that cannot render on the server.\n *\n * It holds upload state and reads the browser-published credential globals, so\n * it lives here rather than in FormField.tsx. Pulling it out is what lets\n * FormField drop its `'use client'` directive and render inside a Server\n * Component (see StaticForm.tsx); a form WITHOUT a file field now ships no\n * field-rendering JavaScript at all.\n *\n * Uploads the chosen file to the Sonor API, which stores it and returns a\n * public URL — that URL becomes the field value, so large files never touch\n * the JSON submission body. The value stays empty until the upload succeeds,\n * so a required file field correctly blocks submission until the upload\n * completes (rather than submitting a useless bare filename).\n *\n * Note: forms containing a file field are NOT native-POST eligible (see\n * native-fallback.tsx) — a form-encoded POST cannot run this upload — so the\n * server shell renders a plain disabled-looking file input and the real\n * control arrives with the enhancement.\n */\n\nimport React from 'react'\nimport type { FormField as FormFieldType } from './types'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nexport interface FileFieldProps {\n field: FormFieldType\n inputId: string\n classPrefix: string\n baseInputStyle: React.CSSProperties\n onChange: (value: unknown) => void\n}\n\nexport function FileField({\n field,\n inputId,\n classPrefix,\n baseInputStyle,\n onChange,\n}: FileFieldProps) {\n const [status, setStatus] = React.useState<'idle' | 'uploading' | 'error'>('idle')\n const [fileName, setFileName] = React.useState('')\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0]\n if (!file) return\n setFileName(file.name)\n setStatus('uploading')\n onChange('') // clear any prior URL until this upload completes\n try {\n const url = await uploadFormFile(file)\n onChange(url)\n setStatus('idle')\n } catch (err) {\n console.error('[Forms] File upload failed:', err)\n onChange('')\n setStatus('error')\n }\n }\n\n return (\n <>\n <input\n id={inputId}\n className={`${classPrefix}__input ${classPrefix}__input--file`}\n type=\"file\"\n name={field.slug}\n required={field.is_required}\n disabled={status === 'uploading'}\n onChange={handleFile}\n style={baseInputStyle}\n />\n {status === 'uploading' && (\n <p style={{ color: 'var(--sk-text-tertiary, #6b7280)', fontSize: 14, marginTop: 4 }}>\n Uploading {fileName}…\n </p>\n )}\n {status === 'error' && (\n <p role=\"alert\" style={{ color: 'var(--sk-error, #dc2626)', fontSize: 14, marginTop: 4 }}>\n Upload failed. Please choose the file again.\n </p>\n )}\n </>\n )\n}\n\n/** Upload a single file to the Sonor API and return its stored public URL. */\nasync function uploadFormFile(file: File): Promise<string> {\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n if (!apiKey) {\n throw new Error('API key is required to upload files.')\n }\n\n const body = new FormData()\n body.append('file', file)\n\n // No Content-Type header — the browser sets the multipart boundary itself.\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/upload`,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}` },\n body,\n },\n { retries: 1, timeoutMs: 60000 },\n )\n\n if (!response.ok) {\n throw new Error(`Upload failed (${response.status})`)\n }\n const data = (await response.json().catch(() => null)) as { url?: string } | null\n if (!data?.url) {\n throw new Error('Upload did not return a URL.')\n }\n return data.url\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
- import { sonorFetch } from './chunk-ADOGOLYO.mjs';
3
- import './chunk-SXNKLHOX.mjs';
4
- import './chunk-4SQ6PROR.mjs';
2
+ import { sonorFetch } from './chunk-F4DZH4E5.mjs';
3
+ import './chunk-HHX72A2B.mjs';
4
+ import './chunk-3PTKHSF7.mjs';
5
5
  import './chunk-BEXTDHO4.mjs';
6
6
  import React from 'react';
7
7
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
@@ -81,5 +81,5 @@ async function uploadFormFile(file) {
81
81
  }
82
82
 
83
83
  export { FileField };
84
- //# sourceMappingURL=FileField-YJ7DZXC3.mjs.map
85
- //# sourceMappingURL=FileField-YJ7DZXC3.mjs.map
84
+ //# sourceMappingURL=FileField-WI6SADBA.mjs.map
85
+ //# sourceMappingURL=FileField-WI6SADBA.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/forms/FileField.tsx"],"names":[],"mappings":"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","file":"FileField-YJ7DZXC3.mjs","sourcesContent":["'use client'\n\n/**\n * File upload field — the one field type that cannot render on the server.\n *\n * It holds upload state and reads the browser-published credential globals, so\n * it lives here rather than in FormField.tsx. Pulling it out is what lets\n * FormField drop its `'use client'` directive and render inside a Server\n * Component (see StaticForm.tsx); a form WITHOUT a file field now ships no\n * field-rendering JavaScript at all.\n *\n * Uploads the chosen file to the Sonor API, which stores it and returns a\n * public URL — that URL becomes the field value, so large files never touch\n * the JSON submission body. The value stays empty until the upload succeeds,\n * so a required file field correctly blocks submission until the upload\n * completes (rather than submitting a useless bare filename).\n *\n * Note: forms containing a file field are NOT native-POST eligible (see\n * native-fallback.tsx) — a form-encoded POST cannot run this upload — so the\n * server shell renders a plain disabled-looking file input and the real\n * control arrives with the enhancement.\n */\n\nimport React from 'react'\nimport type { FormField as FormFieldType } from './types'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nexport interface FileFieldProps {\n field: FormFieldType\n inputId: string\n classPrefix: string\n baseInputStyle: React.CSSProperties\n onChange: (value: unknown) => void\n}\n\nexport function FileField({\n field,\n inputId,\n classPrefix,\n baseInputStyle,\n onChange,\n}: FileFieldProps) {\n const [status, setStatus] = React.useState<'idle' | 'uploading' | 'error'>('idle')\n const [fileName, setFileName] = React.useState('')\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0]\n if (!file) return\n setFileName(file.name)\n setStatus('uploading')\n onChange('') // clear any prior URL until this upload completes\n try {\n const url = await uploadFormFile(file)\n onChange(url)\n setStatus('idle')\n } catch (err) {\n console.error('[Forms] File upload failed:', err)\n onChange('')\n setStatus('error')\n }\n }\n\n return (\n <>\n <input\n id={inputId}\n className={`${classPrefix}__input ${classPrefix}__input--file`}\n type=\"file\"\n name={field.slug}\n required={field.is_required}\n disabled={status === 'uploading'}\n onChange={handleFile}\n style={baseInputStyle}\n />\n {status === 'uploading' && (\n <p style={{ color: 'var(--sk-text-tertiary, #6b7280)', fontSize: 14, marginTop: 4 }}>\n Uploading {fileName}…\n </p>\n )}\n {status === 'error' && (\n <p role=\"alert\" style={{ color: 'var(--sk-error, #dc2626)', fontSize: 14, marginTop: 4 }}>\n Upload failed. Please choose the file again.\n </p>\n )}\n </>\n )\n}\n\n/** Upload a single file to the Sonor API and return its stored public URL. */\nasync function uploadFormFile(file: File): Promise<string> {\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n if (!apiKey) {\n throw new Error('API key is required to upload files.')\n }\n\n const body = new FormData()\n body.append('file', file)\n\n // No Content-Type header — the browser sets the multipart boundary itself.\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/upload`,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}` },\n body,\n },\n { retries: 1, timeoutMs: 60000 },\n )\n\n if (!response.ok) {\n throw new Error(`Upload failed (${response.status})`)\n }\n const data = (await response.json().catch(() => null)) as { url?: string } | null\n if (!data?.url) {\n throw new Error('Upload did not return a URL.')\n }\n return data.url\n}\n"]}
1
+ {"version":3,"sources":["../src/forms/FileField.tsx"],"names":[],"mappings":"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","file":"FileField-WI6SADBA.mjs","sourcesContent":["'use client'\n\n/**\n * File upload field — the one field type that cannot render on the server.\n *\n * It holds upload state and reads the browser-published credential globals, so\n * it lives here rather than in FormField.tsx. Pulling it out is what lets\n * FormField drop its `'use client'` directive and render inside a Server\n * Component (see StaticForm.tsx); a form WITHOUT a file field now ships no\n * field-rendering JavaScript at all.\n *\n * Uploads the chosen file to the Sonor API, which stores it and returns a\n * public URL — that URL becomes the field value, so large files never touch\n * the JSON submission body. The value stays empty until the upload succeeds,\n * so a required file field correctly blocks submission until the upload\n * completes (rather than submitting a useless bare filename).\n *\n * Note: forms containing a file field are NOT native-POST eligible (see\n * native-fallback.tsx) — a form-encoded POST cannot run this upload — so the\n * server shell renders a plain disabled-looking file input and the real\n * control arrives with the enhancement.\n */\n\nimport React from 'react'\nimport type { FormField as FormFieldType } from './types'\nimport { sonorFetch } from '../shared/sonorFetch'\n\nexport interface FileFieldProps {\n field: FormFieldType\n inputId: string\n classPrefix: string\n baseInputStyle: React.CSSProperties\n onChange: (value: unknown) => void\n}\n\nexport function FileField({\n field,\n inputId,\n classPrefix,\n baseInputStyle,\n onChange,\n}: FileFieldProps) {\n const [status, setStatus] = React.useState<'idle' | 'uploading' | 'error'>('idle')\n const [fileName, setFileName] = React.useState('')\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0]\n if (!file) return\n setFileName(file.name)\n setStatus('uploading')\n onChange('') // clear any prior URL until this upload completes\n try {\n const url = await uploadFormFile(file)\n onChange(url)\n setStatus('idle')\n } catch (err) {\n console.error('[Forms] File upload failed:', err)\n onChange('')\n setStatus('error')\n }\n }\n\n return (\n <>\n <input\n id={inputId}\n className={`${classPrefix}__input ${classPrefix}__input--file`}\n type=\"file\"\n name={field.slug}\n required={field.is_required}\n disabled={status === 'uploading'}\n onChange={handleFile}\n style={baseInputStyle}\n />\n {status === 'uploading' && (\n <p style={{ color: 'var(--sk-text-tertiary, #6b7280)', fontSize: 14, marginTop: 4 }}>\n Uploading {fileName}…\n </p>\n )}\n {status === 'error' && (\n <p role=\"alert\" style={{ color: 'var(--sk-error, #dc2626)', fontSize: 14, marginTop: 4 }}>\n Upload failed. Please choose the file again.\n </p>\n )}\n </>\n )\n}\n\n/** Upload a single file to the Sonor API and return its stored public URL. */\nasync function uploadFormFile(file: File): Promise<string> {\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n if (!apiKey) {\n throw new Error('API key is required to upload files.')\n }\n\n const body = new FormData()\n body.append('file', file)\n\n // No Content-Type header — the browser sets the multipart boundary itself.\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/upload`,\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}` },\n body,\n },\n { retries: 1, timeoutMs: 60000 },\n )\n\n if (!response.ok) {\n throw new Error(`Upload failed (${response.status})`)\n }\n const data = (await response.json().catch(() => null)) as { url?: string } | null\n if (!data?.url) {\n throw new Error('Upload did not return a URL.')\n }\n return data.url\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { StageStyles, SummaryTick } from './chunk-WSPGXSBU.mjs';
3
- import { stageFirstName, isEmptyValue, useFieldControls, stageViews, stageEstimate, AUTO_ADVANCE_TYPES } from './chunk-BVLGJ2CZ.mjs';
3
+ import { stageFirstName, isEmptyValue, useFieldControls, stageViews, stageEstimate, AUTO_ADVANCE_TYPES } from './chunk-MOVMDHGE.mjs';
4
4
  import { FormField } from './chunk-LB7LHVWI.mjs';
5
5
  import './chunk-BEXTDHO4.mjs';
6
6
  import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
@@ -748,5 +748,5 @@ function FormSpotlight({
748
748
  }
749
749
 
750
750
  export { FormSpotlight, SpotStyles, groupSentence, renderCompletionTemplate };
751
- //# sourceMappingURL=FormSpotlight-HLH5LQKI.mjs.map
752
- //# sourceMappingURL=FormSpotlight-HLH5LQKI.mjs.map
751
+ //# sourceMappingURL=FormSpotlight-6FZV6JOA.mjs.map
752
+ //# sourceMappingURL=FormSpotlight-6FZV6JOA.mjs.map