@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
@@ -1,33 +1,22 @@
1
1
  ---
2
- title: Theme tokens and CSS
3
- description: Style c15t with semantic tokens and use the stylesheet that matches
4
- your CSS tooling.
2
+ title: Theme tokens
3
+ description: Change c15t's colors, type, radius, spacing, shadows and motion
4
+ with theme tokens, and see every --c15t-* variable with its default.
5
5
  group: customization
6
6
  ---
7
7
 
8
- ## Load the stock stylesheet once
8
+ ## Where tokens come from
9
9
 
10
- React, Next.js and Svelte provide `styles.css`. Import the adapter's stylesheet
11
- at the app's global entry point. Vue includes styles in its components; Astro
12
- adds styles through its integration.
13
-
14
- ```tsx
15
- import 'c15t/react/styles.css';
16
- ```
17
-
18
- The standard stylesheet places rules in `@layer components`. For Tailwind 3,
19
- use the `styles.tw3.css` entry instead of the standard stylesheet, between the
20
- components and utilities directives in your Tailwind entry:
21
-
22
- ```css
23
- @tailwind base;
24
- @tailwind components;
25
- @import 'c15t/react/styles.tw3.css';
26
- @tailwind utilities;
27
- ```
28
-
29
- Do not load both c15t stylesheet variants. For Tailwind 4 or unlayered CSS,
30
- inspect layer order before reaching for `!important`.
10
+ c15t's components take their colors from `--c15t-*` CSS variables, and most
11
+ of their fonts, radii, spacing, shadows and motion too. c15t's stylesheet sets
12
+ the defaults. You change them with a theme object or with CSS, and every part
13
+ that reads a token changes with it. Some values are still fixed for one
14
+ component, such as button padding, the radius of the "Secured by" tag and
15
+ several font sizes and weights, so a token change does not move them. Restyle
16
+ those parts through [slots](./slots.md) or your own CSS.
17
+ [Stylesheets and CSS layers](./stylesheets.md)
18
+ covers which stylesheet to load, and [dark mode](./dark-mode.md)
19
+ covers the dark set of tokens.
31
20
 
32
21
  ## Set semantic values together
33
22
 
@@ -39,18 +28,116 @@ readable in each state.
39
28
  import { defineTheme } from '@c15t/ui/theme';
40
29
 
41
30
  export const theme = defineTheme({
42
- colors: { primary: '#2f6f4e' },
43
- radius: { lg: '4px' },
44
- consentActions: {
45
- primary: { variant: 'primary', mode: 'filled' },
46
- dismiss: { variant: 'neutral', mode: 'stroke' },
47
- },
31
+ colors: { primary: '#2f6f4e' },
32
+ radius: { lg: '4px' },
33
+ consentActions: {
34
+ primary: { variant: 'primary', mode: 'filled' },
35
+ dismiss: { variant: 'neutral', mode: 'stroke' },
36
+ },
48
37
  });
49
38
  ```
50
39
 
51
- Install `@c15t/ui` if importing its theme helper directly. Pass `theme` in your
52
- provider options. `consentActions` selects styling by action role. A per-action
53
- entry overrides `primary`, which overrides `default`.
40
+ Install `@c15t/ui` if importing its theme helper directly. Where the theme
41
+ goes depends on the framework:
42
+
43
+ | Framework | Tokens | `consentActions` |
44
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
45
+ | Next.js, TanStack Start, React | `<ConsentTheme theme={theme} />`, rendered on the server where the app has one | `theme` in the provider options |
46
+ | Nuxt, Vue | `theme` or `tokens` in the module or plugin options | Not available |
47
+ | Astro | `theme` in the integration options | The same `theme` |
48
+ | Svelte, SvelteKit | `generateThemeCSS(theme)` from `@c15t/ui/theme` on the server, in a `<style>` element, or `--c15t-*` variables in your stylesheet | `theme` on `ConsentManagerProvider` |
49
+ | HTML, JavaScript | `ui.theme` | Not available |
50
+
51
+ React and Svelte providers do not turn tokens in their `theme` option into
52
+ CSS, and warn in development when a theme holds tokens but the page has no
53
+ `<style id="c15t-theme">`. Your framework's customize page shows the full
54
+ setup.
55
+
56
+ `consentActions` selects styling by action role. A per-action entry overrides
57
+ `primary`, which overrides `default`.
58
+
59
+ ## Combine a generated theme with your own CSS
60
+
61
+ `generateThemeCSS` writes its variables on `:root:root` and
62
+ `.c15t-theme-root.c15t-theme-root`, one step more specific than the defaults
63
+ in `styles.css`. The theme therefore overrides the defaults whether its
64
+ `<style>` element comes before or after the stylesheet. The same output backs
65
+ `ConsentTheme` in React, Next.js and TanStack Start, Astro's `theme` option and
66
+ the script tag's `ui.theme`.
67
+
68
+ A `--c15t-*` variable you set on plain `:root` in your own CSS loses to a
69
+ generated theme that sets the same variable, even when your rule loads later.
70
+ Put the value in the theme, or raise your selector:
71
+
72
+ ```css
73
+ :root:root {
74
+ --c15t-primary: #2f6f4e;
75
+ }
76
+ ```
77
+
78
+ Scoped rules such as `[data-prompt] { --c15t-primary: ... }` set the variable
79
+ on the banner element itself, so they still apply inside it.
80
+
81
+ ## Every token
82
+
83
+ Every framework uses the same tokens. A theme object, such as `ConsentTheme`'s
84
+ `theme`, Astro's and Vue's `theme` or the script tag's `ui.theme`, takes the
85
+ theme key, such as `radius.lg`. Vue and Nuxt `tokens` take the CSS variable
86
+ name without the leading `--`, such as `c15t-radius-lg`. A stylesheet sets the
87
+ CSS variable itself. Your framework's customize page shows where each one goes.
88
+ [Motion and animation](./motion.md) explains the duration and
89
+ easing tokens.
90
+
91
+ | CSS variable | Theme key | Default |
92
+ | ----------------------------- | -------------------------------- | ----------------------------------------------- |
93
+ | `--c15t-primary` | `colors.primary` | `hsl(228, 100%, 60%)` |
94
+ | `--c15t-primary-hover` | `colors.primaryHover` | `hsl(228, 100%, 55%)` |
95
+ | `--c15t-surface` | `colors.surface` | `hsl(0, 0%, 100%)` |
96
+ | `--c15t-surface-hover` | `colors.surfaceHover` | `hsl(0, 0%, 98%)` |
97
+ | `--c15t-border` | `colors.border` | `hsl(0, 0%, 90%)` |
98
+ | `--c15t-border-hover` | `colors.borderHover` | `hsl(0, 0%, 85%)` |
99
+ | `--c15t-text` | `colors.text` | `hsl(0, 0%, 10%)` |
100
+ | `--c15t-text-muted` | `colors.textMuted` | `hsl(0, 0%, 40%)` |
101
+ | `--c15t-text-on-primary` | `colors.textOnPrimary` | auto-derived from `colors.primary` when omitted |
102
+ | `--c15t-overlay` | `colors.overlay` | `hsla(0, 0%, 0%, 0.5)` |
103
+ | `--c15t-switch-track` | `colors.switchTrack` | `hsl(0, 0%, 85%)` |
104
+ | `--c15t-switch-track-active` | `colors.switchTrackActive` | `hsl(228, 100%, 60%)` |
105
+ | `--c15t-switch-thumb` | `colors.switchThumb` | `hsl(0, 0%, 100%)` |
106
+ | `--c15t-font-family` | `typography.fontFamily` | `system-ui, -apple-system, sans-serif` |
107
+ | `--c15t-font-size-sm` | `typography.fontSize.sm` | `0.875rem` |
108
+ | `--c15t-font-size-base` | `typography.fontSize.base` | `1rem` |
109
+ | `--c15t-font-size-lg` | `typography.fontSize.lg` | `1.125rem` |
110
+ | `--c15t-font-weight-normal` | `typography.fontWeight.normal` | `400` |
111
+ | `--c15t-font-weight-medium` | `typography.fontWeight.medium` | `500` |
112
+ | `--c15t-font-weight-semibold` | `typography.fontWeight.semibold` | `600` |
113
+ | `--c15t-line-height-tight` | `typography.lineHeight.tight` | `1.25` |
114
+ | `--c15t-line-height-normal` | `typography.lineHeight.normal` | `1.5` |
115
+ | `--c15t-line-height-relaxed` | `typography.lineHeight.relaxed` | `1.75` |
116
+ | `--c15t-space-xs` | `spacing.xs` | `0.25rem` |
117
+ | `--c15t-space-sm` | `spacing.sm` | `0.5rem` |
118
+ | `--c15t-space-md` | `spacing.md` | `1rem` |
119
+ | `--c15t-space-lg` | `spacing.lg` | `1.5rem` |
120
+ | `--c15t-space-xl` | `spacing.xl` | `2rem` |
121
+ | `--c15t-radius-sm` | `radius.sm` | `0.25rem` |
122
+ | `--c15t-radius-md` | `radius.md` | `0.5rem` |
123
+ | `--c15t-radius-lg` | `radius.lg` | `0.75rem` |
124
+ | `--c15t-radius-full` | `radius.full` | `9999px` |
125
+ | `--c15t-shadow-sm` | `shadows.sm` | `0 1px 2px hsla(0, 0%, 0%, 0.05)` |
126
+ | `--c15t-shadow-md` | `shadows.md` | `0 4px 12px hsla(0, 0%, 0%, 0.08)` |
127
+ | `--c15t-shadow-lg` | `shadows.lg` | `0 8px 24px hsla(0, 0%, 0%, 0.12)` |
128
+ | `--c15t-duration-fast` | `motion.duration.fast` | `80ms` |
129
+ | `--c15t-duration-normal` | `motion.duration.normal` | `150ms` |
130
+ | `--c15t-duration-slow` | `motion.duration.slow` | `200ms` |
131
+ | `--c15t-easing` | `motion.easing` | `cubic-bezier(0.4, 0, 0.2, 1)` |
132
+ | `--c15t-easing-out` | `motion.easingOut` | `cubic-bezier(0.215, 0.61, 0.355, 1)` |
133
+ | `--c15t-easing-in-out` | `motion.easingInOut` | `cubic-bezier(0.645, 0.045, 0.355, 1)` |
134
+ | `--c15t-easing-spring` | `motion.easingSpring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
135
+
136
+ The radius tokens round different parts. `radius.lg` rounds the banner card,
137
+ the preference dialog, `ConsentGate` placeholders and the floating trigger.
138
+ `radius.md` rounds buttons, accordions, tabs and the vendor list.
139
+ `radius.sm` rounds small parts inside the banner and dialog. To give the banner
140
+ and its buttons the same 4px corners, set both `lg` and `md`.
54
141
 
55
142
  ## Target a prompt with CSS
56
143
 
@@ -63,8 +150,9 @@ entry overrides `primary`, which overrides `default`.
63
150
  ```
64
151
 
65
152
  Use attributes exposed by the rendered component, not guessed class names.
66
- Test the prompt and the preferences dialog separately because tokens scoped to
67
- one prompt do not automatically reach a portaled dialog.
153
+ The script tag's banner has no `data-prompt` or `data-model`. Test the prompt
154
+ and the preferences dialog separately because tokens scoped to one prompt do
155
+ not automatically reach a portaled dialog.
68
156
 
69
157
  | Size variable | Default | Target |
70
158
  | ----------------------------------- | ------- | ------------- |
@@ -72,5 +160,47 @@ one prompt do not automatically reach a portaled dialog.
72
160
  | `--consent-banner-widget-max-width` | `20rem` | Widget |
73
161
  | `--consent-banner-wall-max-width` | `30rem` | Choice wall |
74
162
 
163
+ The banner footer lays out its actions by the card's width, not the
164
+ viewport's. With the default `compact` profile, a card narrower than 22rem
165
+ puts Reject and Accept on one row and Customize on a full-width row below
166
+ them, on any screen size.
167
+
75
168
  Test long translations and small screens after changing width or typography.
76
169
  A compact banner must still fit the required actions.
170
+
171
+ ## Restyle the "Secured by" tag
172
+
173
+ The tag sits on the edge of the banner and dialog cards and uses the primary
174
+ color by default. Set these variables on `:root`, or on an element that
175
+ contains the tag. The dialog renders in a portal, so a variable set on the
176
+ banner does not reach the dialog's tag.
177
+
178
+ | Variable | Default | Target |
179
+ | -------------------------------------------- | --------------------------------------- | -------------------------------------- |
180
+ | `--consent-branding-tag-background-color` | `var(--c15t-primary)` | Tag background |
181
+ | `--consent-branding-tag-border-color` | `--c15t-primary` mixed 14% toward black | Tag border |
182
+ | `--consent-branding-tag-text-color` | `var(--c15t-text-on-primary, #fff)` | "Secured by" and the wordmark |
183
+ | `--consent-branding-tag-mark-color` | The text color | c15t mark or inth logo |
184
+ | `--consent-branding-tag-shadow` | Inset highlight and a 1px drop shadow | Tag shadow |
185
+ | `--consent-branding-tag-attached-edge-width` | `0px` | Border on the edge that meets the card |
186
+
187
+ The stylesheet does not declare these variables. Each default resolves on the
188
+ tag, so a `--c15t-primary` you scope to a banner still colors the tag.
189
+
190
+ This makes the tag look like a tab of the card:
191
+
192
+ ```css
193
+ :root {
194
+ --consent-branding-tag-background-color: var(--c15t-surface);
195
+ --consent-branding-tag-border-color: var(--c15t-border);
196
+ --consent-branding-tag-text-color: var(--c15t-text-muted);
197
+ --consent-branding-tag-mark-color: var(--c15t-primary);
198
+ --consent-branding-tag-shadow: none;
199
+ }
200
+ ```
201
+
202
+ The edge that meets the card has no border by default. Above the banner the
203
+ tag overlaps the card's top border by 1px and covers it. Below the dialog the
204
+ tag starts under the card's bottom border. Set
205
+ `--consent-branding-tag-attached-edge-width: 1px` to draw that edge. It is
206
+ drawn over the card's border, so the two borders do not stack.
@@ -27,9 +27,25 @@ i18n: {
27
27
 
28
28
  Supply the same message keys in each supported locale. A one-off component prop
29
29
  such as `dismissButtonText` is useful for one banner; use translations for a
30
- site-wide change. Astro's serializable integration options and Vue's module
31
- configuration have their own types, so verify those shapes before copying a
32
- React object.
30
+ site-wide change. Astro's serializable integration options have their own
31
+ types, so verify that shape before copying a React object. The Vue plugin and
32
+ Nuxt module have no `i18n` option; their copy comes from the backend.
33
+
34
+ ## Combine messages with backend copy
35
+
36
+ With a backend or a manifest, the copy it sends for the visitor's language is
37
+ the base. Your `i18n.messages` for that language replace it key by key, and
38
+ keys you leave out keep the backend's wording, including copy edited in your
39
+ Inth project. c15t looks up messages for the exact language first, then for
40
+ its primary language, so `de-AT` uses your `de` messages when there is no
41
+ `de-AT` entry. Messages for other languages are not applied.
42
+
43
+ A key only overrides the backend when your text differs from c15t's built-in
44
+ wording for that language. So passing the stock bundles from
45
+ `@c15t/translations/all` to enable languages keeps backend edits visible,
46
+ while a key you actually reworded stays pinned in code. Core bundles only
47
+ English; for other languages, import `@c15t/translations/all` so c15t can
48
+ recognize its stock wording.
33
49
 
34
50
  ## Write labels that describe the action
35
51
 
@@ -41,6 +57,48 @@ The notice acknowledgement uses `common.acknowledge`, with `common.dismiss` as
41
57
  a fallback for older translation bundles. Keep the displayed label and the
42
58
  command's effect aligned.
43
59
 
60
+ ## Translate the ConsentGate placeholder
61
+
62
+ The `ConsentGate` placeholder reads the `consentGate` section:
63
+
64
+ | Key | Where it shows |
65
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
66
+ | `consentGate.title` | The placeholder text. `{category}` is replaced by the category's translated title. |
67
+ | `consentGate.actionButton` | The button that opens preferences, with the same `{category}` replacement. |
68
+ | `consentGate.policyBlocked` | React and Vue show it in place of the title, with no button, when a strict policy leaves the category out of scope. |
69
+
70
+ Earlier versions called this section `frame`. Copy under `frame` in
71
+ `i18n.messages`, custom translations or an older backend's `/init` response
72
+ still applies. c15t reads it as `consentGate`, a key set under `consentGate`
73
+ wins over the same key under `frame`, and c15t logs a warning once outside
74
+ production. Rename the section to `consentGate` to remove the warning.
75
+
76
+ ## Show right-to-left languages
77
+
78
+ c15t sets `dir="rtl"` on its surfaces when the resolved language is Arabic,
79
+ Hebrew, Persian, Urdu, Pashto, Sindhi, Kurdish or Dhivehi. It matches the
80
+ primary language, so `ar-EG` counts as Arabic. Hebrew is the only
81
+ right-to-left translation c15t ships. For the others, supply your own
82
+ messages or the copy from your Inth project.
83
+
84
+ What follows the direction:
85
+
86
+ * Text, headings and the button row in the banner, the preference dialog and
87
+ the preference widget flow right to left.
88
+ * A floating or widget banner in its default corner moves to the mirrored
89
+ corner, so `bottom-left` becomes `bottom-right`. A `position` you set
90
+ yourself stays where you put it.
91
+ * The HTML script tag, `@c15t/browser` and Astro's banner also set `lang` on
92
+ their surfaces. React, Vue and Svelte set `dir` only, so set `lang` on
93
+ `<html>` yourself.
94
+
95
+ What does not follow yet:
96
+
97
+ * The IAB TCF dialog aligns several labels, indents and borders to the left.
98
+ * The floating `ConsentDialogTrigger` keeps the corner you give it.
99
+
100
+ Test right-to-left pages with a real translation before you ship them.
101
+
44
102
  ## Test more than English
45
103
 
46
104
  Try the longest labels you support at a narrow width, with browser zoom and
@@ -0,0 +1,160 @@
1
+ ---
2
+ title: Embeds
3
+ description: Gate YouTube videos, maps, social posts and other iframes on an
4
+ Astro site so they load only after the visitor allows their consent category,
5
+ with a custom element or the c15t iframe blocker.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## Why an embed needs gating
10
+
11
+ An iframe sends requests to its host as soon as it is in the page with a
12
+ `src`, before any script can stop it. `ConsentBanner` does not block iframes
13
+ you already have. Render an embed only while its category is allowed, and
14
+ remove it when the visitor withdraws permission.
15
+
16
+ Astro has no consent gate component. Use one of these:
17
+
18
+ | Approach | Use it when |
19
+ | ------------------------------------------------------- | ---------------------------------------------------------- |
20
+ | A custom element that renders the iframe | You want a placeholder with a button in place of the embed |
21
+ | The iframe blocker, with `data-category` and `data-src` | You have iframe markup to gate as it is |
22
+
23
+ ## Gate an embed with a custom element
24
+
25
+ This component shows a placeholder with a preferences button until
26
+ measurement is allowed. It adds the iframe once the visitor allows
27
+ measurement, and removes it when permission is withdrawn. It also keeps the
28
+ iframe out while the visitor has switched YouTube off in
29
+ [vendor consent](https://c15t.com/docs/frameworks/astro/vendor-consent).
30
+
31
+ The component reads `client.isVendorAllowed('youtube')`, so declare `youtube`
32
+ in the `vendors` option of `c15t()` with `category: 'measurement'`. An
33
+ undeclared vendor reads as not allowed, and the video never loads:
34
+
35
+ ```astro title="src/components/consent-video.astro"
36
+ ---
37
+ import ConsentDialogTrigger from 'c15t/astro/components/consent-dialog-trigger.astro';
38
+ ---
39
+
40
+ <consent-video>
41
+ <div data-video>
42
+ <p>Allow measurement to load this YouTube video.</p>
43
+ </div>
44
+ <ConsentDialogTrigger>Open privacy settings</ConsentDialogTrigger>
45
+ </consent-video>
46
+
47
+ <script>
48
+ import { getConsentClient } from 'c15t/astro/client';
49
+
50
+ class ConsentVideo extends HTMLElement {
51
+ dispose?: () => void;
52
+
53
+ connect = () => {
54
+ const client = getConsentClient();
55
+ const container = this.querySelector('[data-video]');
56
+ if (this.dispose || !client || !container) {
57
+ return;
58
+ }
59
+ const render = () => {
60
+ // Measurement is allowed and the visitor has not switched YouTube
61
+ // off. An undeclared vendor is never allowed, so declare youtube.
62
+ if (!client.isVendorAllowed('youtube')) {
63
+ container.textContent =
64
+ 'Allow measurement to load this YouTube video. No video request is sent before permission.';
65
+ return;
66
+ }
67
+ if (container.querySelector('iframe')) {
68
+ return;
69
+ }
70
+ const frame = document.createElement('iframe');
71
+ frame.src = 'https://www.youtube-nocookie.com/embed/czTksCF6X8Y';
72
+ frame.title = 'YouTube video';
73
+ frame.allowFullscreen = true;
74
+ container.replaceChildren(frame);
75
+ };
76
+ render();
77
+ this.dispose = client.subscribe(render);
78
+ };
79
+
80
+ connectedCallback() {
81
+ // c15t boots from a module script, which can run after this one.
82
+ document.addEventListener('DOMContentLoaded', this.connect, {
83
+ once: true,
84
+ });
85
+ this.connect();
86
+ }
87
+
88
+ disconnectedCallback() {
89
+ document.removeEventListener('DOMContentLoaded', this.connect);
90
+ this.dispose?.();
91
+ this.dispose = undefined;
92
+ }
93
+ }
94
+
95
+ if (!customElements.get('consent-video')) {
96
+ customElements.define('consent-video', ConsentVideo);
97
+ }
98
+ </script>
99
+ ```
100
+
101
+ Use it like any Astro component. How it works:
102
+
103
+ * The iframe does not exist in the server HTML, so nothing loads before
104
+ consent, even before the consent runtime starts.
105
+ * `client.subscribe(render)` renders again on every consent change.
106
+ * `connectedCallback` runs again when `ClientRouter` swaps in a page that
107
+ contains the element, so the embed works across navigation.
108
+ * The first `connect()` can run before c15t has started, so the element tries
109
+ again on `DOMContentLoaded`.
110
+
111
+ Change the category, the iframe `src` and the placeholder text for other
112
+ embeds. The [YouTube](../../integrations/youtube.md) and
113
+ [Google Maps](../../integrations/google-maps.md) guides use the same pattern with
114
+ a reusable browser helper. [Integrations](../../integrations/overview.md) lists
115
+ the other vendors.
116
+
117
+ ## Gate existing iframe markup
118
+
119
+ The consent runtime includes an iframe blocker, on by default. Mark an iframe
120
+ with a category and move its URL from `src` to `data-src`:
121
+
122
+ ```html title="src/pages/contact.astro (partial)"
123
+ <iframe
124
+ data-category="marketing"
125
+ data-src="https://www.google.com/maps/embed?pb=..."
126
+ title="Office location"
127
+ ></iframe>
128
+ ```
129
+
130
+ When the category is allowed, the blocker copies `data-src` to `src`. When it
131
+ is withdrawn, the blocker removes `src` again. `data-vendor` gates the iframe
132
+ on one vendor as well. See [vendor consent](https://c15t.com/docs/frameworks/astro/vendor-consent).
133
+
134
+ Always use `data-src`, never `src`, for a gated iframe. The browser starts
135
+ loading a `src` from the HTML before the blocker runs, so an iframe with `src`
136
+ in the markup loads before consent.
137
+
138
+ The blocker watches the whole document, so it also gates iframes on pages you
139
+ reach with `ClientRouter`, which replaces `<body>` on each navigation.
140
+
141
+ ## Gate an embed on the server
142
+
143
+ On a server-rendered page, `Astro.locals.c15t.snapshot.effectivePermissions`
144
+ tells you whether the visitor had allowed the category when the request
145
+ arrived. Rendering the iframe on the server from it works for returning
146
+ visitors. A visitor who allows the category on the page sees the embed only
147
+ after the next navigation, so pair it with the custom element, or use the
148
+ custom element alone. See [Server API](https://c15t.com/docs/frameworks/astro/server).
149
+
150
+ ## Check the embeds
151
+
152
+ 1. Open the page in a private window with DevTools Network open. There is no
153
+ request to the embed's host, and no iframe from it in the Elements panel.
154
+ 2. Allow the embed's category from **Cookie preferences**. The iframe appears
155
+ and loads.
156
+ 3. Reload. The iframe loads again without a new choice.
157
+ 4. Withdraw the category and save. The page reloads, and the embed's host
158
+ gets no request.
159
+
160
+ See [Verify consent](../../guides/verify-consent.md) for the full checklist.
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Network blocker
3
+ description: Block fetch and XMLHttpRequest calls to tracking hosts on an Astro
4
+ site until the visitor allows their consent category, with c15t's network
5
+ blocker rules and an onRequestBlocked handler.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## What the network blocker does
10
+
11
+ `networkBlocker` stops `fetch` and `XMLHttpRequest` calls that match a rule
12
+ until the visitor allows the rule's category. A blocked `fetch` resolves to a
13
+ `451` response, and nothing is sent. It covers requests that code already on
14
+ the page makes, such as an SDK you load yourself or a tag manager's own calls.
15
+
16
+ It does not stop `navigator.sendBeacon`, WebSockets, image pixels, `<script>`
17
+ tags or iframes. Load scripts through c15t and gate iframes as
18
+ [Scripts](./scripts.md) and
19
+ [Embeds](./embeds.md) describe.
20
+
21
+ ## Add rules
22
+
23
+ Rules are plain data, so they can go in the integration options:
24
+
25
+ ```js title="astro.config.mjs (partial)"
26
+ c15t({
27
+ mode: hosted({ url: 'https://your-project.inth.app' }),
28
+ networkBlocker: {
29
+ rules: [
30
+ { category: 'measurement', domain: 'google-analytics.com' },
31
+ {
32
+ category: 'marketing',
33
+ domain: 'ads.example.com',
34
+ pathIncludes: '/collect',
35
+ methods: ['POST'],
36
+ },
37
+ ],
38
+ },
39
+ });
40
+ ```
41
+
42
+ | Rule field | Effect |
43
+ | -------------- | ----------------------------------------------------------------- |
44
+ | `category` | The category that must be allowed for the request to go through |
45
+ | `domain` | The host to match. It also matches every subdomain |
46
+ | `pathIncludes` | Matches only URLs whose path contains this text |
47
+ | `methods` | Matches only these HTTP methods |
48
+ | `vendor` | Also requires this vendor to be allowed, for vendor-level consent |
49
+
50
+ | Option | Default | Effect |
51
+ | -------------------- | -------- | ------------------------------------------------------------------- |
52
+ | `rules` | Required | The rules to apply |
53
+ | `enabled` | `true` | Set `false` to keep the rules but stop blocking |
54
+ | `logBlockedRequests` | `true` | Logs each blocked request to the console. Set `false` to silence it |
55
+
56
+ The blocker starts with the consent runtime, from a module script. A request
57
+ made before that, such as from an inline script at the top of `<head>`, is not
58
+ blocked.
59
+
60
+ ## Log or report blocked requests
61
+
62
+ `onRequestBlocked` is a function, so it cannot go in `astro.config.mjs`. Set
63
+ `networkBlocker` in the default export of your client entrypoint instead. It
64
+ replaces the integration's `networkBlocker` completely, so repeat the rules
65
+ there:
66
+
67
+ ```ts title="src/consent-client.ts (partial)"
68
+ export default {
69
+ scripts,
70
+ networkBlocker: {
71
+ rules: [{ category: 'measurement', domain: 'google-analytics.com' }],
72
+ onRequestBlocked: ({ url, rule }) => {
73
+ console.info('Blocked until consent:', url, rule?.category);
74
+ },
75
+ },
76
+ } satisfies C15tClientOptionsExtension;
77
+ ```
78
+
79
+ ## Check the network blocker
80
+
81
+ 1. Open the site in a private window with DevTools Network open, and trigger
82
+ the code that calls a blocked host. The request does not appear, and a
83
+ `fetch` receives a `451` response.
84
+ 2. Allow the rule's category. The next request to that host goes through.
85
+ 3. Withdraw the category and save. After the reload, requests to the host are
86
+ blocked again.