@escape-game-over/atlas 0.1.41 → 0.1.43

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.41",
3
+ "version": "0.1.43",
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" />
@@ -87,6 +87,14 @@ export function campaign(): Campaign {
87
87
  }
88
88
  }
89
89
 
90
+ /**
91
+ * A moment as a Google Ads conversion import reads it:
92
+ * `2026-09-24 12:42:10+00:00`. In UTC, so the file needs no time zone setting.
93
+ */
94
+ export function conversionTime(at: Date = new Date()): string {
95
+ return `${at.toISOString().slice(0, 19).replace("T", " ")}+00:00`;
96
+ }
97
+
90
98
  /**
91
99
  * A cookie's value. `document.cookie` is always `name=value; name=value`, so a
92
100
  * split reads it — `cookieStore.get` would too, but is async and newer than
@@ -24,4 +24,5 @@ export {
24
24
  export * from "./filters.ts";
25
25
  export * from "./filters-view.ts";
26
26
  export { data, ref, refs } from "./ref.ts";
27
+ export * from "./umami-event.ts";
27
28
  export * from "./youtube.ts";
@@ -0,0 +1,4 @@
1
+ // Every page: a visit can land anywhere before it reaches a form.
2
+ import { rememberCampaign } from "./attribution.ts";
3
+
4
+ rememberCampaign();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Records a named event with Umami — a form sent, a deck downloaded — from a
3
+ * client script.
4
+ *
5
+ * Umami's own script defines `window.umami` once it has loaded; atlas emits
6
+ * that script from `analytics.umami`. Before then, or where a blocker stopped
7
+ * it, there is nothing to call, and the event is dropped rather than thrown:
8
+ * an analytics call must never be the reason a page misbehaves.
9
+ *
10
+ * The data goes to Umami as it is, so it should carry what the visitor chose,
11
+ * never who they are. For an event on a plain click, Umami's
12
+ * `data-umami-event` attribute needs no script at all.
13
+ */
14
+ export function umamiEvent(
15
+ name: string,
16
+ data?: Readonly<Record<string, string>>
17
+ ): void {
18
+ const umami = (
19
+ window as unknown as {
20
+ umami?: { track(event: string, data?: object): void };
21
+ }
22
+ ).umami;
23
+ umami?.track(name, data);
24
+ }