@escape-game-over/atlas 0.1.42 → 0.1.44

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -10,6 +10,9 @@
10
10
  * `restoreScroll={false}`: the router keeps its own position in the same
11
11
  * history state.
12
12
  *
13
+ * A site with a lead form passes `rememberCampaign`, so the `utm_*` tags a
14
+ * visit landed with survive to the page the form is on; see `attribution.ts`.
15
+ *
13
16
  * ```astro
14
17
  * <Document meta={meta} html={{ class: "scroll-smooth" }} body={{ class: "…" }}>
15
18
  * <Fragment slot="head"><link rel="preload" … /></Fragment>
@@ -20,6 +23,7 @@
20
23
  import type { HTMLAttributes } from "astro/types";
21
24
  import type { PageMeta } from "../site/index.ts";
22
25
  import MetaTags from "./MetaTags.astro";
26
+ import RememberCampaign from "./RememberCampaign.astro";
23
27
  import ScrollRestore from "./ScrollRestore.astro";
24
28
 
25
29
  interface Props {
@@ -29,9 +33,17 @@ interface Props {
29
33
  readonly body?: HTMLAttributes<"body">;
30
34
  /** Defaults to `true`; `false` for a site on `ClientRouter`. */
31
35
  readonly restoreScroll?: boolean;
36
+ /** Off unless stated: a site with no lead form has nothing to keep them for. */
37
+ readonly rememberCampaign?: boolean;
32
38
  }
33
39
 
34
- const { meta, html, body, restoreScroll = true } = Astro.props;
40
+ const {
41
+ meta,
42
+ html,
43
+ body,
44
+ restoreScroll = true,
45
+ rememberCampaign = false,
46
+ } = Astro.props;
35
47
  ---
36
48
 
37
49
  <!doctype html>
@@ -49,5 +61,6 @@ const { meta, html, body, restoreScroll = true } = Astro.props;
49
61
  <MetaTags tags={meta.bodyTags} />
50
62
  <slot />
51
63
  {restoreScroll && <ScrollRestore />}
64
+ {rememberCampaign && <RememberCampaign />}
52
65
  </body>
53
66
  </html>
@@ -0,0 +1,11 @@
1
+ ---
2
+ /**
3
+ * Keeps the campaign tags a visit landed with for the tab, so a lead form on
4
+ * another page can still report them — see `attribution.ts`.
5
+ *
6
+ * Rendered by `Document` when asked for. A bundled module rather than inline:
7
+ * the tags are wanted by a form, which cannot be sent before it runs.
8
+ */
9
+ ---
10
+
11
+ <script src="./remember-campaign.ts" />
@@ -11,7 +11,8 @@
11
11
  * - **Campaign tags** (`utm_*`) are labels a campaign shares with everyone who
12
12
  * clicked it, not an identifier. They are kept for the tab in
13
13
  * `sessionStorage`, so a visitor who lands on one page and fills a form on
14
- * another still reports them. `rememberCampaign()` on every page does that.
14
+ * another still reports them. `rememberCampaign()` does that; `Document`'s
15
+ * `rememberCampaign` runs it on every page, with analytics consent only.
15
16
  * - **The ad click id** (`gclid`, `gbraid`, `wbraid`) identifies one click, and
16
17
  * is never stored here. Google keeps it under the visitor's consent: on the
17
18
  * URL while marketing is denied — `urlPassthrough` in `GoogleSettings` — and
@@ -80,8 +80,15 @@ export function readConsent(): ConsentRecord | undefined {
80
80
  }
81
81
 
82
82
  /**
83
- * Remembers an answer, dated now, and tells Google about it — one call, so the
84
- * two cannot come apart.
83
+ * The event `recordConsent` fires on `window` with the new answer as `detail`,
84
+ * for anything on the page that waits on a category — without the banner
85
+ * having to know it exists.
86
+ */
87
+ export const CONSENT_EVENT = "atlas:consent";
88
+
89
+ /**
90
+ * Remembers an answer, dated now, tells Google about it, and announces it — one
91
+ * call, so the three cannot come apart.
85
92
  */
86
93
  export function recordConsent(choices: ConsentChoices): void {
87
94
  const record: ConsentRecord = { ...choices, at: new Date().toISOString() };
@@ -92,6 +99,9 @@ export function recordConsent(choices: ConsentChoices): void {
92
99
  // one — the choice still applies to this page.
93
100
  }
94
101
  applyConsent(choices);
102
+ window.dispatchEvent?.(
103
+ new CustomEvent<ConsentChoices>(CONSENT_EVENT, { detail: choices })
104
+ );
95
105
  }
96
106
 
97
107
  /**
@@ -0,0 +1,15 @@
1
+ // Every page: a visit can land anywhere before it reaches a form.
2
+ //
3
+ // Only with analytics consent. Keeping campaign tags is measurement, and EU
4
+ // rules ask consent for anything stored on a visitor's device that the site
5
+ // does not need to work — not only cookies. So the tags are kept now if the
6
+ // visitor already agreed, or the moment they agree on this page.
7
+ import { rememberCampaign } from "./attribution.ts";
8
+ import { CONSENT_EVENT, type ConsentChoices, readConsent } from "./consent.ts";
9
+
10
+ if (readConsent()?.analytics === "granted") rememberCampaign();
11
+
12
+ addEventListener(CONSENT_EVENT, (event) => {
13
+ const choices = (event as CustomEvent<ConsentChoices>).detail;
14
+ if (choices.analytics === "granted") rememberCampaign();
15
+ });
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  export {
16
16
  CONSENT_CATEGORIES,
17
+ CONSENT_MONTHS,
17
18
  type ConsentCategory,
18
19
  type ConsentChoice,
19
20
  type ConsentChoices,