@c15t/scripts 3.0.0-alpha.1 → 3.0.0-alpha.3

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 (300) hide show
  1. package/AGENTS.md +129 -59
  2. package/README.md +8 -29
  3. package/SKILL.md +33 -0
  4. package/dist/adobe-analytics.js +2 -0
  5. package/dist/ahrefs-analytics.js +2 -0
  6. package/dist/amplitude.js +2 -0
  7. package/dist/clearbit.js +2 -0
  8. package/dist/cloudflare-web-analytics.js +2 -0
  9. package/dist/cloudflare-zaraz.js +2 -0
  10. package/dist/crisp.js +2 -0
  11. package/dist/databuddy.js +2 -0
  12. package/dist/e2e-test-utils.js +2 -137
  13. package/dist/engine/compile.js +2 -89
  14. package/dist/engine/runtime.js +2 -448
  15. package/dist/events.js +2 -0
  16. package/dist/fathom-analytics.js +2 -0
  17. package/dist/front-chat.js +2 -0
  18. package/dist/google-tag-manager.js +2 -0
  19. package/dist/google-tag.js +2 -0
  20. package/dist/heap.js +2 -0
  21. package/dist/hightouch.js +2 -0
  22. package/dist/hotjar.js +2 -0
  23. package/dist/intercom.js +2 -0
  24. package/dist/klaviyo.js +2 -0
  25. package/dist/linkedin-insights.js +2 -0
  26. package/dist/logrocket.js +2 -0
  27. package/dist/matomo-analytics.js +2 -0
  28. package/dist/meta-pixel.js +2 -0
  29. package/dist/microsoft-clarity.js +2 -0
  30. package/dist/microsoft-uet.js +2 -0
  31. package/dist/mixpanel-analytics.js +2 -0
  32. package/dist/one-dollar-stats.js +2 -0
  33. package/dist/openai-pixel.js +2 -0
  34. package/dist/pinterest-tag.js +2 -0
  35. package/dist/pirsch.js +2 -0
  36. package/dist/plausible-analytics.js +2 -0
  37. package/dist/posthog.js +2 -0
  38. package/dist/promptwatch.js +2 -0
  39. package/dist/reddit-pixel.js +2 -0
  40. package/dist/registry.js +2 -392
  41. package/dist/resolve.js +2 -33
  42. package/dist/rudderstack.js +2 -0
  43. package/dist/rybbit-analytics.js +2 -0
  44. package/dist/segment.js +2 -0
  45. package/dist/snapchat-pixel.js +2 -0
  46. package/dist/tiktok-pixel.js +2 -0
  47. package/dist/types.js +2 -16
  48. package/dist/umami-analytics.js +2 -0
  49. package/dist/vendors/_shared/attributes.js +2 -14
  50. package/dist/vendors/_shared/google-consent.js +2 -27
  51. package/dist/vendors/_shared/install-builders.js +2 -21
  52. package/dist/vendors/_shared/required-id.js +2 -0
  53. package/dist/vendors/_shared/script-url.js +2 -28
  54. package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
  55. package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
  56. package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
  57. package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
  58. package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -0
  59. package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
  60. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
  61. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
  62. package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
  63. package/dist/vendors/analytics/adobe-analytics.js +2 -49
  64. package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
  65. package/dist/vendors/analytics/amplitude.js +2 -134
  66. package/dist/vendors/analytics/clearbit.js +2 -28
  67. package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
  68. package/dist/vendors/analytics/databuddy.js +2 -103
  69. package/dist/vendors/analytics/fathom-analytics.js +2 -35
  70. package/dist/vendors/analytics/google-tag.js +2 -66
  71. package/dist/vendors/analytics/heap.js +2 -134
  72. package/dist/vendors/analytics/hightouch.js +2 -109
  73. package/dist/vendors/analytics/hotjar.js +2 -44
  74. package/dist/vendors/analytics/logrocket.js +2 -58
  75. package/dist/vendors/analytics/matomo-analytics.js +2 -191
  76. package/dist/vendors/analytics/microsoft-clarity.js +2 -100
  77. package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
  78. package/dist/vendors/analytics/one-dollar-stats.js +2 -0
  79. package/dist/vendors/analytics/pirsch.js +2 -67
  80. package/dist/vendors/analytics/plausible-analytics.js +2 -81
  81. package/dist/vendors/analytics/posthog.js +2 -200
  82. package/dist/vendors/analytics/promptwatch.js +2 -29
  83. package/dist/vendors/analytics/rudderstack.js +2 -183
  84. package/dist/vendors/analytics/rybbit-analytics.js +2 -63
  85. package/dist/vendors/analytics/segment.js +2 -56
  86. package/dist/vendors/analytics/umami-analytics.js +2 -39
  87. package/dist/vendors/analytics/vercel-analytics.js +2 -53
  88. package/dist/vendors/email-and-sms/klaviyo.js +2 -0
  89. package/dist/vendors/functional/crisp.js +2 -100
  90. package/dist/vendors/functional/front-chat.js +2 -0
  91. package/dist/vendors/functional/intercom.js +2 -45
  92. package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
  93. package/dist/vendors/tag-managers/google-tag-manager.js +2 -59
  94. package/dist/vercel-analytics.js +2 -0
  95. package/dist/x-pixel.js +2 -0
  96. package/dist-types/adobe-analytics.d.ts +2 -0
  97. package/dist-types/ahrefs-analytics.d.ts +2 -0
  98. package/dist-types/amplitude.d.ts +2 -0
  99. package/dist-types/clearbit.d.ts +2 -0
  100. package/dist-types/cloudflare-web-analytics.d.ts +2 -0
  101. package/dist-types/cloudflare-zaraz.d.ts +2 -0
  102. package/dist-types/crisp.d.ts +2 -0
  103. package/dist-types/databuddy.d.ts +2 -0
  104. package/dist-types/e2e-test-utils.d.ts +2 -0
  105. package/dist-types/engine/compile.d.ts +2 -3
  106. package/dist-types/engine/runtime.d.ts +2 -3
  107. package/dist-types/events.d.ts +2 -0
  108. package/dist-types/fathom-analytics.d.ts +2 -0
  109. package/dist-types/front-chat.d.ts +2 -0
  110. package/dist-types/google-tag-manager.d.ts +2 -0
  111. package/dist-types/google-tag.d.ts +2 -0
  112. package/dist-types/heap.d.ts +2 -0
  113. package/dist-types/hightouch.d.ts +2 -0
  114. package/dist-types/hotjar.d.ts +2 -0
  115. package/dist-types/intercom.d.ts +2 -0
  116. package/dist-types/klaviyo.d.ts +2 -0
  117. package/dist-types/linkedin-insights.d.ts +2 -0
  118. package/dist-types/logrocket.d.ts +2 -0
  119. package/dist-types/matomo-analytics.d.ts +2 -0
  120. package/dist-types/meta-pixel.d.ts +2 -0
  121. package/dist-types/microsoft-clarity.d.ts +2 -0
  122. package/dist-types/microsoft-uet.d.ts +2 -0
  123. package/dist-types/mixpanel-analytics.d.ts +2 -0
  124. package/dist-types/one-dollar-stats.d.ts +2 -0
  125. package/dist-types/openai-pixel.d.ts +2 -0
  126. package/dist-types/pinterest-tag.d.ts +2 -0
  127. package/dist-types/pirsch.d.ts +2 -0
  128. package/dist-types/plausible-analytics.d.ts +2 -0
  129. package/dist-types/posthog.d.ts +2 -0
  130. package/dist-types/promptwatch.d.ts +2 -0
  131. package/dist-types/reddit-pixel.d.ts +2 -0
  132. package/dist-types/registry.d.ts +2 -458
  133. package/dist-types/resolve.d.ts +2 -9
  134. package/dist-types/rudderstack.d.ts +2 -0
  135. package/dist-types/rybbit-analytics.d.ts +2 -0
  136. package/dist-types/segment.d.ts +2 -0
  137. package/dist-types/snapchat-pixel.d.ts +2 -0
  138. package/dist-types/tiktok-pixel.d.ts +2 -0
  139. package/dist-types/types.d.ts +2 -314
  140. package/dist-types/umami-analytics.d.ts +2 -0
  141. package/dist-types/vendors/_shared/attributes.d.ts +2 -35
  142. package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
  143. package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
  144. package/dist-types/vendors/_shared/required-id.d.ts +2 -0
  145. package/dist-types/vendors/_shared/script-url.d.ts +2 -75
  146. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
  147. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
  148. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
  149. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
  150. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -0
  151. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
  152. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
  153. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
  154. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
  155. package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
  156. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
  157. package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
  158. package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
  159. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
  160. package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
  161. package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
  162. package/dist-types/vendors/analytics/google-tag.d.ts +2 -93
  163. package/dist-types/vendors/analytics/heap.d.ts +2 -316
  164. package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
  165. package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
  166. package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
  167. package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
  168. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
  169. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
  170. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -0
  171. package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
  172. package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
  173. package/dist-types/vendors/analytics/posthog.d.ts +2 -175
  174. package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
  175. package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
  176. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
  177. package/dist-types/vendors/analytics/segment.d.ts +2 -158
  178. package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
  179. package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
  180. package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
  181. package/dist-types/vendors/functional/crisp.d.ts +2 -78
  182. package/dist-types/vendors/functional/front-chat.d.ts +2 -0
  183. package/dist-types/vendors/functional/intercom.d.ts +2 -135
  184. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
  185. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -94
  186. package/dist-types/vercel-analytics.d.ts +2 -0
  187. package/dist-types/x-pixel.d.ts +2 -0
  188. package/docs/README.md +129 -59
  189. package/docs/assets/v3/bottom-bar.png +0 -0
  190. package/docs/assets/v3/brand-card.png +0 -0
  191. package/docs/assets/v3/brand-preferences.png +0 -0
  192. package/docs/assets/v3/choice-wall.png +0 -0
  193. package/docs/assets/v3/headless-bar-html.png +0 -0
  194. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  195. package/docs/assets/v3/headless-bar.png +0 -0
  196. package/docs/assets/v3/slim-bar.png +0 -0
  197. package/docs/concepts/choose-your-setup.md +87 -0
  198. package/docs/concepts/consent-categories.md +84 -0
  199. package/docs/concepts/consent-state.md +357 -0
  200. package/docs/{guides → concepts}/data-fetching.md +31 -27
  201. package/docs/concepts/how-consent-works.md +123 -0
  202. package/docs/concepts/policies.md +71 -0
  203. package/docs/customization/class-names.md +202 -0
  204. package/docs/customization/dark-mode.md +157 -0
  205. package/docs/customization/motion.md +119 -0
  206. package/docs/customization/overview.md +67 -33
  207. package/docs/customization/recipes.md +839 -47
  208. package/docs/customization/slots.md +216 -35
  209. package/docs/customization/stylesheets.md +147 -0
  210. package/docs/customization/tailwind.md +842 -0
  211. package/docs/customization/tokens.md +166 -36
  212. package/docs/customization/translations.md +61 -3
  213. package/docs/frameworks/astro/embeds.md +160 -0
  214. package/docs/frameworks/astro/network-blocker.md +86 -0
  215. package/docs/frameworks/astro/scripts.md +146 -0
  216. package/docs/frameworks/html/embeds.md +142 -0
  217. package/docs/frameworks/html/network-blocker.md +105 -0
  218. package/docs/frameworks/html/scripts.md +164 -0
  219. package/docs/frameworks/javascript/scripts.md +119 -0
  220. package/docs/frameworks/next/embeds.md +90 -0
  221. package/docs/frameworks/next/network-blocker.md +153 -0
  222. package/docs/frameworks/next/scripts.md +196 -0
  223. package/docs/frameworks/nuxt/embeds.md +81 -0
  224. package/docs/frameworks/nuxt/network-blocker.md +97 -0
  225. package/docs/frameworks/nuxt/scripts.md +89 -0
  226. package/docs/frameworks/react/embeds.md +89 -0
  227. package/docs/frameworks/react/network-blocker.md +140 -0
  228. package/docs/frameworks/react/scripts.md +115 -0
  229. package/docs/frameworks/svelte/embeds.md +96 -0
  230. package/docs/frameworks/svelte/network-blocker.md +141 -0
  231. package/docs/frameworks/svelte/scripts.md +137 -0
  232. package/docs/frameworks/sveltekit/embeds.md +103 -0
  233. package/docs/frameworks/sveltekit/network-blocker.md +149 -0
  234. package/docs/frameworks/sveltekit/scripts.md +141 -0
  235. package/docs/frameworks/tanstack-start/embeds.md +96 -0
  236. package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
  237. package/docs/frameworks/tanstack-start/scripts.md +103 -0
  238. package/docs/frameworks/vue/embeds.md +84 -0
  239. package/docs/frameworks/vue/network-blocker.md +99 -0
  240. package/docs/frameworks/vue/scripts.md +93 -0
  241. package/docs/guides/banner-experiments.md +654 -0
  242. package/docs/guides/troubleshooting.md +120 -47
  243. package/docs/guides/verify-consent.md +81 -49
  244. package/docs/integrations/adobe-analytics.md +168 -160
  245. package/docs/integrations/ahrefs-analytics.md +153 -155
  246. package/docs/integrations/amplitude.md +163 -157
  247. package/docs/integrations/building-integrations.md +136 -37
  248. package/docs/integrations/clearbit.md +155 -155
  249. package/docs/integrations/cloudflare-web-analytics.md +157 -157
  250. package/docs/integrations/cloudflare-zaraz.md +210 -262
  251. package/docs/integrations/crisp.md +165 -159
  252. package/docs/integrations/databuddy.md +158 -174
  253. package/docs/integrations/fathom-analytics.md +159 -157
  254. package/docs/integrations/front-chat.md +322 -0
  255. package/docs/integrations/google-maps.md +119 -84
  256. package/docs/integrations/google-tag-manager.md +179 -164
  257. package/docs/integrations/google-tag.md +164 -161
  258. package/docs/integrations/heap.md +164 -156
  259. package/docs/integrations/hightouch.md +162 -158
  260. package/docs/integrations/hotjar.md +160 -156
  261. package/docs/integrations/intercom.md +184 -154
  262. package/docs/integrations/klaviyo.md +486 -0
  263. package/docs/integrations/linkedin-insights.md +175 -151
  264. package/docs/integrations/logrocket.md +161 -157
  265. package/docs/integrations/matomo-analytics.md +189 -179
  266. package/docs/integrations/meta-pixel.md +189 -151
  267. package/docs/integrations/microsoft-clarity.md +164 -156
  268. package/docs/integrations/microsoft-uet.md +149 -155
  269. package/docs/integrations/mixpanel-analytics.md +156 -161
  270. package/docs/integrations/one-dollar-stats.md +306 -0
  271. package/docs/integrations/openai-pixel.md +205 -302
  272. package/docs/integrations/overview.md +143 -83
  273. package/docs/integrations/pinterest-tag.md +329 -0
  274. package/docs/integrations/pirsch.md +170 -160
  275. package/docs/integrations/plausible-analytics.md +173 -159
  276. package/docs/integrations/posthog.md +227 -242
  277. package/docs/integrations/promptwatch.md +155 -155
  278. package/docs/integrations/reddit-pixel.md +186 -158
  279. package/docs/integrations/rudderstack.md +202 -187
  280. package/docs/integrations/rybbit-analytics.md +172 -161
  281. package/docs/integrations/segment.md +183 -155
  282. package/docs/integrations/snapchat-pixel.md +187 -157
  283. package/docs/integrations/tiktok-pixel.md +172 -151
  284. package/docs/integrations/umami-analytics.md +164 -159
  285. package/docs/integrations/vercel-analytics.md +167 -158
  286. package/docs/integrations/x-pixel.md +177 -151
  287. package/docs/integrations/youtube.md +122 -87
  288. package/docs/upgrade-v3.md +490 -354
  289. package/package.json +12 -236
  290. package/dist-types/__tests__/helpers.d.ts +0 -141
  291. package/docs/assets/v3/brand-bar.png +0 -0
  292. package/docs/assets/v3/mobile-card.png +0 -0
  293. package/docs/assets/v3/preferences.png +0 -0
  294. package/docs/frameworks/javascript/script-loader.md +0 -94
  295. package/docs/frameworks/next/script-loader.md +0 -210
  296. package/docs/frameworks/react/script-loader.md +0 -63
  297. package/docs/guides/consent-state.md +0 -60
  298. package/docs/guides/deployment-modes.md +0 -75
  299. package/docs/integrations/clear-on-revocation.md +0 -167
  300. package/docs/integrations/granular-consent.md +0 -208
@@ -0,0 +1,146 @@
1
+ ---
2
+ title: Scripts
3
+ description: Load vendor scripts and gated inline scripts on an Astro site only
4
+ after the visitor allows their consent category, and stop them when consent is
5
+ withdrawn.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Register vendor scripts
10
+
11
+ The `ConsentBanner` component does not stop a `<script>` tag you already have.
12
+ Give c15t each vendor script to load instead, and remove the vendor's own
13
+ snippet so it loads once.
14
+
15
+ Vendor helpers from `@c15t/integrations` contain callbacks, and the integration
16
+ options in `astro.config.mjs` must survive JSON serialization. So register
17
+ helpers in the module that the integration's `clientEntrypoint` option names.
18
+ This list loads PostHog on measurement and X Pixel on marketing:
19
+
20
+ ```ts title="src/example-scripts.ts"
21
+ import { posthog } from '@c15t/integrations/posthog';
22
+ import { xPixel } from '@c15t/integrations/x-pixel';
23
+
24
+ export const scripts = [
25
+ posthog({
26
+ id: 'phc_your_project_key',
27
+ initOptions: { cookieless_mode: 'never' },
28
+ loadMode: 'after-consent',
29
+ region: 'eu',
30
+ }),
31
+ xPixel({ pixelId: 'your-pixel-id' }),
32
+ ];
33
+ ```
34
+
35
+ ```ts title="src/consent-client.ts"
36
+ import type { C15tClientOptionsExtension } from 'c15t/astro';
37
+
38
+ import { scripts } from './example-scripts';
39
+
40
+ export default { scripts } satisfies C15tClientOptionsExtension;
41
+ ```
42
+
43
+ The integration imports that module into the page's boot script, so every page
44
+ shares one script loader. Each helper's guide under
45
+ [integrations](../../integrations/overview.md) lists its options and the requests
46
+ to expect.
47
+
48
+ ## Add a script without a helper
49
+
50
+ A script with no callbacks can go straight into the integration options. Give
51
+ it an `id`, a `category`, and either a `src` or inline `textContent`:
52
+
53
+ ```js title="astro.config.mjs (partial)"
54
+ c15t({
55
+ mode: hosted({ url: 'https://your-project.inth.app' }),
56
+ scripts: [
57
+ {
58
+ id: 'example-analytics',
59
+ category: 'measurement',
60
+ src: 'https://analytics.example.com/script.js',
61
+ },
62
+ ],
63
+ });
64
+ ```
65
+
66
+ Scripts from `astro.config.mjs` and from the client entrypoint both load.
67
+
68
+ ## Gate an inline script
69
+
70
+ For a script that has to stay in the page's markup, make it inert and label it
71
+ with a category. c15t runs it once the category is allowed:
72
+
73
+ ```astro title="src/pages/index.astro (partial)"
74
+ <script data-c15t-category="measurement" is:inline type="text/plain">
75
+ document.getElementById('inline-script-status').textContent =
76
+ 'Measurement script ran';
77
+ </script>
78
+ ```
79
+
80
+ Three attributes matter:
81
+
82
+ * `type="text/plain"` stops the browser from running the script.
83
+ * `data-c15t-category` names one category. An unknown name logs a warning and
84
+ the script never runs.
85
+ * `is:inline` makes Astro ship the tag as written. Without it, Astro bundles the
86
+ script and runs it regardless of consent.
87
+
88
+ Add `data-c15t-vendor` with a vendor slug to also hold the script while the
89
+ visitor has switched that vendor off. See
90
+ [vendor consent](https://c15t.com/docs/frameworks/astro/vendor-consent).
91
+
92
+ Under a nonce-based Content Security Policy, where your middleware sets
93
+ `Astro.locals.c15t.nonce`, also add `nonce={Astro.locals.c15t?.nonce}` to the
94
+ tag. c15t then activates only gated tags that carry the page's nonce, and
95
+ skips the rest with a console warning. See
96
+ [put the nonce on your gated scripts](https://c15t.com/docs/frameworks/astro/content-security-policy#put-the-nonce-on-your-gated-scripts).
97
+
98
+ c15t checks gated scripts when the page loads, after each consent change and
99
+ after each `ClientRouter` navigation, so a script on a page you navigate to
100
+ runs as soon as it is allowed. A script with `src` works the same way. For
101
+ markup you insert later from your own code, call
102
+ `activateGatedScripts(getConsent(), container)` from `c15t/astro/client`.
103
+ Under a nonce policy, give the inserted tags the nonce and pass it as a third
104
+ argument.
105
+
106
+ A script that has run cannot be undone. When the visitor withdraws consent,
107
+ c15t reloads the page, and the reloaded page leaves the script inert.
108
+
109
+ ## Gate embeds and requests
110
+
111
+ An iframe loads as soon as it is in the page, so gate embeds separately. See
112
+ [Embeds](./embeds.md) for a component that renders an iframe
113
+ only while its category is allowed, and for the iframe blocker.
114
+
115
+ To stop `fetch` and `XMLHttpRequest` calls to tracking hosts until consent,
116
+ add rules to `networkBlocker`. See
117
+ [Network blocker](./network-blocker.md).
118
+
119
+ ## Clear data when consent is withdrawn
120
+
121
+ Set `clearOnRevocation` in the integration options to remove a vendor's
122
+ cookies and storage keys when its category is withdrawn. See
123
+ [clear on revocation](https://c15t.com/docs/frameworks/astro/clear-on-revocation) for the shape.
124
+
125
+ ## Withdraw consent without reloading
126
+
127
+ By default c15t reloads the page after a save turns off a category that was
128
+ allowed, because it cannot stop code that has already run. Set
129
+ `reloadOnConsentRevoked: false` in the integration options only if every script
130
+ on the page stops itself when its category is withdrawn. Stop your own code
131
+ from the `onPermissionsChanged` callback. See
132
+ [Callbacks](https://c15t.com/docs/frameworks/astro/callbacks).
133
+
134
+ ## Check script loading
135
+
136
+ Build the site and open it in a private window with DevTools Network open:
137
+
138
+ 1. Before you choose, filter for each vendor's domain. There are no requests,
139
+ and a gated inline script has not run.
140
+ 2. Allow one category from **Cookie preferences**. Only that category's vendors
141
+ load, and inline scripts gated on it run.
142
+ 3. Reload. The allowed vendors load again, and the others stay absent.
143
+ 4. Withdraw the category and save. The page reloads and the vendor does not
144
+ load.
145
+
146
+ See [Verify consent](../../guides/verify-consent.md) for the full checklist.
@@ -0,0 +1,142 @@
1
+ ---
2
+ title: Embeds
3
+ description: Hold YouTube videos, maps and other iframes on a plain HTML page
4
+ until their consent category is allowed with data-src and data-category, show
5
+ a placeholder, and configure the c15t iframe blocker.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Gate an iframe
10
+
11
+ Move the iframe's URL from `src` to `data-src` and name its category in
12
+ `data-category`. The c15t script tag gives the iframe its `src` once the
13
+ category is allowed:
14
+
15
+ ```html title="index.html"
16
+ <iframe
17
+ data-src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
18
+ data-category="measurement"
19
+ data-vendor="youtube"
20
+ title="YouTube video"
21
+ allow="encrypted-media; picture-in-picture"
22
+ allowfullscreen
23
+ ></iframe>
24
+ ```
25
+
26
+ An iframe with no `src` loads nothing, so the vendor gets no request and sets
27
+ no cookie until the visitor allows the category. Give every embed a `title`
28
+ that says what it shows.
29
+
30
+ ## Attributes
31
+
32
+ | Attribute | What it does |
33
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `data-src` | The embed's URL. c15t moves it to `src` when the gate opens. Only `http:` and `https:` URLs load. A relative URL resolves against the page. |
35
+ | `data-category` | One category name. An unknown name logs a warning and keeps the iframe blocked. |
36
+ | `data-vendor` | Optional vendor ID for vendor-level consent. The iframe also stays blocked while the visitor has turned that vendor off. See [vendor consent](https://c15t.com/docs/frameworks/html/vendor-consent). |
37
+ | `data-c15t-paused` | Set by c15t when it took away a `src` the iframe already had. Do not set it yourself. |
38
+
39
+ Iframes without `data-category` or `data-vendor` are left alone.
40
+
41
+ ## Show a placeholder
42
+
43
+ c15t does not draw anything in place of a blocked iframe. Put your own message
44
+ next to it and hide it once the iframe has a `src`. The quickstart page uses
45
+ this markup:
46
+
47
+ ```html
48
+ <div class="embed">
49
+ <iframe data-src="…" data-category="measurement" title="YouTube video"></iframe>
50
+ <div class="placeholder">
51
+ <p>Allow measurement to load this YouTube video.</p>
52
+ <a href="#c15t-preferences">Choose video permissions</a>
53
+ </div>
54
+ </div>
55
+ ```
56
+
57
+ ```css
58
+ .embed iframe:not([src]) { display: none; }
59
+ .embed iframe[src] + .placeholder { display: none; }
60
+ ```
61
+
62
+ The `#c15t-preferences` link opens the preference dialog, where the visitor
63
+ can allow the category. See [preferences link](https://c15t.com/docs/frameworks/html/components/preferences-link).
64
+
65
+ ## What happens on withdrawal
66
+
67
+ When a visitor turns the category off, c15t removes the iframe's `src` and puts
68
+ the URL back in `data-src`, which unloads the embed. The page also reloads by
69
+ default, as it does for scripts.
70
+
71
+ An iframe written with a normal `src` and a `data-category` starts loading
72
+ before c15t runs, then c15t removes the `src`. The vendor already got its
73
+ request, so always use `data-src`.
74
+
75
+ ## Iframes added later
76
+
77
+ c15t watches the whole document. An iframe that a page builder, a CMS widget
78
+ or your own script adds later is gated the moment it appears, and so is an
79
+ iframe whose `data-category` changes. This keeps working when a client router
80
+ such as Turbo replaces `<body>` on navigation, and when the c15t tag runs in
81
+ `<head>` before `<body>` exists.
82
+
83
+ ## Configure the iframe blocker
84
+
85
+ The iframe blocker is on by default in every `@c15t/browser` build. Set
86
+ `iframeBlocker` in a queued `config` call to change it:
87
+
88
+ ```html
89
+ <script>
90
+ window.c15t = window.c15t || [];
91
+ c15t.push(['config', { iframeBlocker: false }]);
92
+ </script>
93
+ ```
94
+
95
+ | Value | Effect |
96
+ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97
+ | omitted or `{}` | Gate every iframe with `data-category` or `data-vendor`, including ones added later. |
98
+ | `{ disableAutomaticBlocking: true }` | Do not scan or watch the page. c15t sets or removes a `src` only when you call `c15t.processIframes()`. See [check iframes yourself](#check-iframes-yourself). |
99
+ | `false` | Turn the blocker off. `data-src` iframes never load. |
100
+
101
+ The categories on gated iframes are added to the preference dialog, as they
102
+ are for gated scripts.
103
+
104
+ ## Check iframes yourself
105
+
106
+ With `iframeBlocker: { disableAutomaticBlocking: true }`, c15t leaves iframes
107
+ alone until your page calls `c15t.processIframes()`. Each call pauses gated
108
+ iframes, those with `data-category` or `data-vendor`, that consent does not
109
+ allow, and restores the ones it does. Call it after the policy resolves, after
110
+ you add iframes, and after consent changes:
111
+
112
+ ```html
113
+ <script>
114
+ window.c15t = window.c15t || [];
115
+ c15t.push(['config', { iframeBlocker: { disableAutomaticBlocking: true } }]);
116
+ c15t.push(['processIframes']);
117
+ c15t.push(['on', 'consent', () => c15t.processIframes()]);
118
+ </script>
119
+ ```
120
+
121
+ A queued `processIframes` runs once the policy has resolved, so
122
+ `c15t.push(['processIframes'])` is safe before and after the tag loads. With
123
+ automatic blocking on, c15t does this by itself and you do not need to call
124
+ it.
125
+
126
+ ## Vendor embeds
127
+
128
+ The [integration guides](../../integrations/overview.md) have an HTML tab for
129
+ YouTube, Google Maps and other embeds, with the category each one needs. The
130
+ `youtube-nocookie.com` player still contacts Google when it loads, so gate it
131
+ like any other embed.
132
+
133
+ ## Check it works
134
+
135
+ Open the page in a private window with the Network tab open.
136
+
137
+ 1. Before you choose, the iframe has no `src` in the Elements panel and there
138
+ is no request to the embed's host. The placeholder shows.
139
+ 2. Allow the category. The iframe gets its `src`, the embed loads and the
140
+ placeholder hides.
141
+ 3. Open preferences and turn the category off. The page reloads and the embed
142
+ stays unloaded.
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Network blocker
3
+ description: Hold fetch and XMLHttpRequest calls from a plain HTML page until
4
+ their consent category is allowed, with network blocker rules queued on the
5
+ c15t script tag.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## When you need it
10
+
11
+ Some code sends data with `fetch` or `XMLHttpRequest` from a place you cannot
12
+ change into a `text/plain` tag, such as a theme's bundled script, a plugin, or your own
13
+ code that runs before consent. A network blocker rule holds those requests
14
+ until the rule's category is allowed. Script tags and iframes do not need it;
15
+ gate those with [gated scripts](https://c15t.com/docs/frameworks/html/components/gated-script)
16
+ and [embeds](./embeds.md).
17
+
18
+ ## Add rules
19
+
20
+ Queue a `config` call with `networkBlocker` before the script tag:
21
+
22
+ ```html
23
+ <script>
24
+ window.c15t = window.c15t || [];
25
+ c15t.push(['config', {
26
+ networkBlocker: {
27
+ rules: [
28
+ { id: 'collector', domain: 'collect.example.com', category: 'measurement' },
29
+ {
30
+ id: 'events',
31
+ domain: 'example.com',
32
+ pathIncludes: '/api/track',
33
+ methods: ['POST'],
34
+ category: 'measurement',
35
+ },
36
+ ],
37
+ onRequestBlocked: ({ method, url, rule }) => {
38
+ console.info('c15t blocked', method, url, rule?.id);
39
+ },
40
+ },
41
+ }]);
42
+ </script>
43
+ ```
44
+
45
+ The first rule blocks every request to `collect.example.com` and its
46
+ subdomains until measurement is allowed. The second blocks only POST requests
47
+ to paths on `example.com` that contain `/api/track`.
48
+
49
+ ## Rule fields
50
+
51
+ | Field | Required | What it does |
52
+ | -------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
53
+ | `domain` | Yes | The host to match. Subdomains match too: `example.com` covers `www.example.com`. |
54
+ | `category` | Yes | The category that lets matching requests through. A condition such as `{ and: ['measurement', 'marketing'] }` works too. |
55
+ | `pathIncludes` | No | Match only URLs whose path contains this text. |
56
+ | `methods` | No | Match only these HTTP methods, such as `['POST']`. All methods when omitted. |
57
+ | `id` | No | A name for the rule, shown in logs and passed to `onRequestBlocked`. |
58
+ | `vendor` | No | Also block while the visitor has turned this vendor off. |
59
+ | `vendorId`, `iabPurposes`, `iabLegIntPurposes`, `iabSpecialFeatures` | No | IAB TCF conditions, checked only under an IAB policy. |
60
+
61
+ ## Blocker options
62
+
63
+ | Option | Default | What it does |
64
+ | -------------------- | -------- | ------------------------------------------------------------- |
65
+ | `rules` | required | The rules above. |
66
+ | `enabled` | `true` | `false` keeps the configuration but blocks nothing. |
67
+ | `logBlockedRequests` | `true` | Log each blocked request with `console.warn`. |
68
+ | `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request. |
69
+
70
+ ## What a blocked request sees
71
+
72
+ * A blocked `fetch` resolves with a `451` response. It does not reject, so
73
+ check `response.ok` in code that expects data.
74
+ * A blocked `XMLHttpRequest` fires an `error` event.
75
+ * A request sent before the policy resolves waits, then goes out or is
76
+ blocked once c15t knows the visitor's permissions. If the policy fails to
77
+ load, it is blocked.
78
+ * A request that does not match any rule goes out at once.
79
+
80
+ The rules' categories are added to the preference dialog, as they are for
81
+ gated scripts.
82
+
83
+ ## What it cannot block
84
+
85
+ The network blocker patches `fetch` and `XMLHttpRequest` after the script tag
86
+ runs. It does not cover:
87
+
88
+ * `navigator.sendBeacon`, WebSockets and `EventSource`;
89
+ * requests from `<img>`, `<script>`, `<link>` and `<iframe>` elements;
90
+ * requests sent before the c15t tag ran. With `defer`, the tag runs after the
91
+ page has parsed, so inline scripts anywhere in the page run before it;
92
+ * requests from other frames and from service workers.
93
+
94
+ Use gated tags for scripts and iframes. When inline code sends requests while
95
+ the page parses, load the c15t tag without `defer` at the top of `<head>`, so
96
+ it runs first. The banner still waits for the document to parse before it
97
+ mounts.
98
+
99
+ ## Check it works
100
+
101
+ 1. Open the page in a private window with the console and Network tab open.
102
+ 2. Trigger the request, for example by loading the page that sends it. The
103
+ console shows `[c15t] blocked POST https://example.com/api/track (rule: events)`
104
+ and the Network tab shows no request.
105
+ 3. Allow measurement and trigger it again. The request goes out.
@@ -0,0 +1,164 @@
1
+ ---
2
+ title: Scripts
3
+ description: Hold vendor scripts on a plain HTML page until the visitor allows
4
+ their category, load scripts with callbacks, handle withdrawal and clear
5
+ vendor cookies with the c15t script tag and no build step.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Choose how to gate a script
10
+
11
+ The c15t script tag has two ways to hold a vendor script until its category is
12
+ allowed:
13
+
14
+ | Form | Use it when |
15
+ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
16
+ | A `<script type="text/plain" data-c15t-category>` tag in your HTML | You paste a vendor snippet into a theme, a CMS field or a page builder. No JavaScript of your own. |
17
+ | A `scripts` entry in a queued `config` call | You need a callback after the vendor loads, on an error, or when consent changes. |
18
+
19
+ Both wait for the same permission and both add their category to the
20
+ preference dialog. Iframes use `data-src` instead; see
21
+ [embeds](./embeds.md). Requests your own code sends with
22
+ `fetch` use the [network blocker](./network-blocker.md).
23
+
24
+ ## Gate a pasted snippet
25
+
26
+ Change the vendor's tag to `type="text/plain"` and add
27
+ `data-c15t-category`:
28
+
29
+ ```html
30
+ <script type="text/plain" data-c15t-category="measurement">
31
+ // The vendor's snippet, unchanged
32
+ </script>
33
+ <script type="text/plain" data-c15t-category="marketing" src="https://vendor.example/tag.js"></script>
34
+ ```
35
+
36
+ On a page whose c15t tag has a nonce, add the same `nonce` to each gated tag,
37
+ or c15t skips it. See
38
+ [gated tags on a page with a nonce](https://c15t.com/docs/frameworks/html/components/gated-script#gated-tags-on-a-page-with-a-nonce).
39
+ The [quickstart](https://c15t.com/docs/frameworks/html/quickstart#gate-your-vendor-scripts)
40
+ shows this with PostHog and X Pixel. [Gated scripts](https://c15t.com/docs/frameworks/html/components/gated-script)
41
+ covers load order, copied attributes and tags added after load.
42
+
43
+ ## What happens when a visitor withdraws permission
44
+
45
+ A script that has run cannot be stopped. When a visitor turns off a category
46
+ they had allowed, c15t saves the choice and reloads the page. The new page
47
+ starts with the vendor tag inert again. Allowing a category never reloads.
48
+
49
+ To handle withdrawal yourself, for example by calling a vendor's opt-out
50
+ function, set `reloadOnConsentRevoked: false` in `config`. Then c15t does not
51
+ reload and the vendor code that already ran keeps running until the next page
52
+ load. `callbacks.onBeforeConsentRevocationReload` runs right before the
53
+ reload, if you need to flush something first. See
54
+ [events and callbacks](https://c15t.com/docs/frameworks/html/callbacks).
55
+
56
+ ## Load a script with callbacks
57
+
58
+ List the script under `scripts` in a queued `config` call before the tag:
59
+
60
+ ```html
61
+ <script>
62
+ window.c15t = window.c15t || [];
63
+ c15t.push(['config', {
64
+ scripts: [
65
+ {
66
+ id: 'analytics',
67
+ src: 'https://analytics.example/sdk.js',
68
+ category: 'measurement',
69
+ onLoad: () => window.analytics.track('page_view'),
70
+ onConsentChange: ({ hasConsent }) => {
71
+ if (!hasConsent) window.analytics.optOut();
72
+ },
73
+ },
74
+ ],
75
+ }]);
76
+ </script>
77
+ ```
78
+
79
+ `analytics.track` and `analytics.optOut` stand for your vendor's own API.
80
+ c15t adds the script to `<head>` once measurement is allowed, and removes the
81
+ element again when the visitor withdraws it.
82
+
83
+ | Field | What it does |
84
+ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
85
+ | `id` | A unique, stable name for the script. Required. |
86
+ | `category` | The category it needs, such as `'measurement'`. Required. It also accepts a condition such as `{ and: ['measurement', 'marketing'] }`. |
87
+ | `src` or `textContent` | The URL to load, or inline code to run. One is required unless `callbackOnly` is set. |
88
+ | `callbackOnly` | Add no element. Only run the callbacks, for a vendor that is already on the page. |
89
+ | `alwaysLoad` | Load whatever the consent state. Use it only for tags that manage consent themselves. |
90
+ | `persistAfterConsentRevoked` | Keep the element on the page after withdrawal. |
91
+ | `target` | `'head'` (default) or `'body'`. |
92
+ | `async`, `defer`, `nonce`, `fetchPriority`, `attributes` | Attributes for the created `<script>` element. Without its own `nonce`, the script gets the c15t tag's nonce. |
93
+ | `anonymizeId` | Give the element a random `id` so blockers cannot match it by name. On by default. |
94
+ | `vendor` | A vendor ID for vendor-level consent. See [vendor consent](https://c15t.com/docs/frameworks/html/vendor-consent). |
95
+ | `onBeforeLoad`, `onLoad`, `onError` | Run before a load attempt, after the script loads, or when it fails. |
96
+ | `onConsentChange` | Run each time the script's consent changes. The argument has `hasConsent` and `consents`. |
97
+ | `onDispose` | Run when the script is removed from the configuration or c15t is disposed. |
98
+
99
+ Every callback receives the script's `id`, the created element's
100
+ `elementId`, `hasConsent`, the current `consents`, and the `element` when
101
+ there is one.
102
+
103
+ For a plain vendor snippet, the `text/plain` tag is simpler and works in any
104
+ CMS field that accepts HTML. The vendor helpers in `@c15t/integrations`, such as
105
+ the PostHog and Google tag helpers, are ES modules that need a bundler. On a
106
+ plain HTML page, paste the vendor's own snippet into a `text/plain` tag.
107
+
108
+ ## Clear cookies when permission is withdrawn
109
+
110
+ Gating stops new requests but does not delete what a vendor already stored.
111
+ Add `clearOnRevocation` to the config to delete named cookies and storage keys
112
+ when their category is denied. See
113
+ [clear on revocation](https://c15t.com/docs/frameworks/html/clear-on-revocation).
114
+
115
+ ## Google Consent Mode and tag managers
116
+
117
+ The script tag does not send Google Consent Mode signals. The Google tag and
118
+ Google Tag Manager helpers in `@c15t/integrations` do, but they need a bundler. On
119
+ a plain HTML page, gate Google's snippet like any other vendor. Google then
120
+ loads only after consent, so it sends nothing before a choice.
121
+ [Gated scripts](https://c15t.com/docs/frameworks/html/components/gated-script#load-order)
122
+ shows the GA4 snippet with its two tags in the right order.
123
+
124
+ If you need Consent Mode instead, load Google's snippet ungated and send the
125
+ signals yourself. Put this before Google's tags:
126
+
127
+ ```html
128
+ <script>
129
+ window.dataLayer = window.dataLayer || [];
130
+ function gtag() { dataLayer.push(arguments); }
131
+ gtag('consent', 'default', {
132
+ analytics_storage: 'denied',
133
+ ad_storage: 'denied',
134
+ ad_user_data: 'denied',
135
+ ad_personalization: 'denied',
136
+ });
137
+ window.c15t = window.c15t || [];
138
+ c15t.push(['on', 'consent', (snapshot) => {
139
+ const allowed = snapshot.effectivePermissions;
140
+ const state = (granted) => (granted ? 'granted' : 'denied');
141
+ gtag('consent', 'update', {
142
+ analytics_storage: state(allowed.measurement),
143
+ ad_storage: state(allowed.marketing),
144
+ ad_user_data: state(allowed.marketing),
145
+ ad_personalization: state(allowed.marketing),
146
+ });
147
+ }]);
148
+ </script>
149
+ ```
150
+
151
+ With Consent Mode, Google receives requests before a choice, marked as denied.
152
+ Decide which behavior your policy needs before you pick one.
153
+
154
+ The vendor guides under [integrations](../../integrations/overview.md) have an
155
+ HTML tab with the no-build setup.
156
+
157
+ ## Check it works
158
+
159
+ Open the page in a private window with the Network tab open.
160
+
161
+ 1. Filter for each vendor's domain. Nothing loads before a choice.
162
+ 2. Allow one category. Only that category's vendors load, and each `onLoad`
163
+ runs.
164
+ 3. Turn the category off again. The page reloads and the vendor stays absent.
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Scripts
3
+ description: Register vendor scripts with @c15t/browser, createConsentRuntime or
4
+ a consent kernel in JavaScript, and control what happens when a visitor
5
+ withdraws permission.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Register scripts where you start c15t
10
+
11
+ Pass the `scripts` list to whichever API starts c15t. The list is the same in
12
+ each case, built from `@c15t/integrations` helpers or your own script objects:
13
+
14
+ | Setup | Where `scripts` goes |
15
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `@c15t/browser` | `init({ backendURL, scripts })`, as in the [quickstart](https://c15t.com/docs/frameworks/javascript/quickstart#start-c15t). |
17
+ | `c15t/runtime` | `createConsentRuntime({ mode, scripts })`, as in [headless](https://c15t.com/docs/frameworks/javascript/headless#create-the-runtime). |
18
+ | Your own kernel | `createScriptLoader({ kernel, scripts })`. See [script loader](https://c15t.com/docs/frameworks/javascript/modules/script-loader#attach-the-loader-to-your-own-kernel). |
19
+
20
+ The quickstart's `src/scripts.ts` shows PostHog and X Pixel:
21
+
22
+ ```ts title="src/scripts.ts"
23
+ import { posthog } from '@c15t/integrations/posthog';
24
+ import { xPixel } from '@c15t/integrations/x-pixel';
25
+
26
+ export const scripts = [
27
+ posthog({
28
+ id: 'phc_your_project_key',
29
+ initOptions: { cookieless_mode: 'never' },
30
+ loadMode: 'after-consent',
31
+ }),
32
+ xPixel({ pixelId: 'your-pixel-id' }),
33
+ ];
34
+ ```
35
+
36
+ Each [integration guide](../../integrations/overview.md) gives the helper and
37
+ options for one vendor. For a vendor without a helper, write the object
38
+ yourself:
39
+
40
+ ```ts
41
+ const scripts = [
42
+ {
43
+ id: 'analytics',
44
+ src: 'https://analytics.example/sdk.js',
45
+ category: 'measurement',
46
+ onLoad: () => window.analytics?.track('page_view'),
47
+ },
48
+ ];
49
+ ```
50
+
51
+ `window.analytics` stands for your vendor's global. The
52
+ [script loader](https://c15t.com/docs/frameworks/javascript/modules/script-loader) page lists
53
+ every field and callback.
54
+
55
+ ## Gate a snippet in your HTML
56
+
57
+ `@c15t/browser` also runs `<script type="text/plain" data-c15t-category>`
58
+ tags in your `index.html` once their category is allowed, in page order. Use
59
+ it for a vendor snippet you would rather keep in HTML. `createConsentRuntime`
60
+ and a plain kernel do not scan the page for these tags;
61
+ `activateGatedScripts(snapshot)` from `@c15t/browser` runs them for you.
62
+
63
+ With the `nonce` option set, `@c15t/browser` runs only the tags that carry the
64
+ same `nonce`, and marks the others `data-c15t-activated="untrusted"`. Pass
65
+ `{ nonce }` as the third argument of `activateGatedScripts` for the same
66
+ check. See [Content Security Policy](https://c15t.com/docs/frameworks/javascript/content-security-policy).
67
+
68
+ ## When a visitor withdraws permission
69
+
70
+ A script that has run cannot be unloaded. `@c15t/browser` and
71
+ `createConsentRuntime` reload the page after a visitor turns off a category
72
+ they had allowed, so the new page starts without that vendor. Allowing a
73
+ category never reloads.
74
+
75
+ Set `reloadOnConsentRevoked: false` to handle withdrawal yourself, for
76
+ example with the vendor's opt-out call in the script's `onConsentChange`.
77
+ Until the next page load, code that already ran keeps running.
78
+ `callbacks.onBeforeConsentRevocationReload` runs right before the reload, for
79
+ work that must finish first. A kernel you create yourself does not reload;
80
+ add `watchRevocationReload` from `c15t` if you want it.
81
+
82
+ ## Keep one owner per vendor
83
+
84
+ Use stable script IDs and remove the vendor's original snippet, so each
85
+ vendor loads once and only through c15t. Ordinary scripts wait for
86
+ permission. Helpers with `alwaysLoad` load at once and pass consent to the
87
+ vendor's own API instead. Read the individual integration guide before
88
+ assuming all helpers have the same network behavior.
89
+
90
+ Create `init()` or the runtime once per page. A second client, or a second
91
+ loader on a kernel that `@c15t/browser` or `createConsentRuntime` owns, loads
92
+ vendors twice.
93
+
94
+ ## Let visitors turn off one vendor
95
+
96
+ A visitor can allow marketing and still switch off one vendor in it. Declare
97
+ the vendors in the `vendors` option and render the switch yourself; helpers
98
+ from `@c15t/integrations` already carry their vendor slug. See
99
+ [vendor consent](https://c15t.com/docs/frameworks/javascript/vendor-consent).
100
+
101
+ ## Clear stored tracking data
102
+
103
+ Script gating does not remove cookies or Web Storage entries that a script
104
+ already wrote. Configure [clear on revocation](https://c15t.com/docs/frameworks/javascript/clear-on-revocation)
105
+ to delete them when their category is denied.
106
+
107
+ ## Send your own events to allowed vendors
108
+
109
+ To send your own analytics events only to the vendors a visitor allows, use
110
+ the event dispatcher from `@c15t/integrations`. See
111
+ [send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
112
+
113
+ ## Check it works
114
+
115
+ Run the app in a private window with the Network tab open.
116
+
117
+ 1. Filter for each vendor's host. Nothing loads before a choice.
118
+ 2. Allow one category. Only that category's vendors load.
119
+ 3. Withdraw it. The page reloads and the vendor stays absent.