@escape-game-over/atlas 0.1.109 → 0.1.110

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.109",
3
+ "version": "0.1.110",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -22,6 +22,7 @@
22
22
  "./astro/consent-banner": "./src/astro/ConsentBanner.astro",
23
23
  "./astro/document": "./src/astro/Document.astro",
24
24
  "./astro/element": "./src/astro/AtlasElement.astro",
25
+ "./astro/escapeall": "./src/astro/EscapeAll.astro",
25
26
  "./astro/escape-navigator": "./src/astro/EscapeNavigator.astro",
26
27
  "./astro/four-escape": "./src/astro/FourEscape.astro",
27
28
  "./astro/google-map": "./src/astro/GoogleMap.astro",
@@ -53,6 +53,7 @@ export const STORAGE_VENDOR_NAMES = {
53
53
  clarity: "Microsoft Clarity",
54
54
  "4escape": "4escape",
55
55
  escapenavigator: "Escape Navigator",
56
+ escapeall: "EscapeAll",
56
57
  stripe: "Stripe",
57
58
  } as const satisfies Record<StorageVendor | BookingVendor | "stripe", string>;
58
59
 
@@ -314,6 +315,13 @@ const BOOKING = {
314
315
  stripe: true,
315
316
  loads: ["google-analytics", "google-ads", "meta"],
316
317
  },
318
+ // Read from its script 2026-11-12: nothing as it renders; the phone
319
+ // confirmation while a visitor books. Its queue cookie waits for its own
320
+ // consent cookie, which a site of ours never sets.
321
+ escapeall: {
322
+ kept: ["ea-cell-confirmation"],
323
+ tab: [],
324
+ },
317
325
  } as const satisfies Record<BookingVendor, BookingStorage>;
318
326
 
319
327
  /**
@@ -452,7 +460,7 @@ export function storedItems(
452
460
  let stripe = false;
453
461
  for (const widget of new Set(booking)) {
454
462
  const entry: BookingStorage = BOOKING[widget];
455
- widgetRows.push(
463
+ const own: StoredItem[] = [
456
464
  {
457
465
  id: widget,
458
466
  names: entry.kept,
@@ -466,8 +474,10 @@ export function storedItems(
466
474
  provider: widget,
467
475
  lasts: "tab",
468
476
  consent: "none",
469
- }
470
- );
477
+ },
478
+ ];
479
+ // A widget that keeps nothing for the tab has no row for it.
480
+ widgetRows.push(...own.filter((row) => row.names.length > 0));
471
481
  for (const vendor of entry.loads ?? []) vendors.add(vendor);
472
482
  if (entry.stripe) stripe = true;
473
483
  }
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import type { HTMLAttributes } from "astro/types";
16
16
  import type { BookingWidget, BookingWidgetVars } from "../booking/widget.ts";
17
+ import EscapeAll from "./EscapeAll.astro";
17
18
  import EscapeNavigator from "./EscapeNavigator.astro";
18
19
  import FourEscape from "./FourEscape.astro";
19
20
 
@@ -39,6 +40,18 @@ const { widget, locale, vars, colorScheme, ...attrs } = Astro.props;
39
40
  vars={vars}
40
41
  />
41
42
  )}
43
+ {widget.vendor === "escapeall" && (
44
+ <EscapeAll
45
+ {...attrs}
46
+ host={widget.host}
47
+ companyId={widget.companyId}
48
+ serviceId={widget.serviceId}
49
+ template={widget.template}
50
+ skin={widget.skin}
51
+ players={widget.players}
52
+ locale={locale}
53
+ />
54
+ )}
42
55
  {widget.vendor === "escapenavigator" && (
43
56
  <EscapeNavigator
44
57
  {...attrs}
@@ -0,0 +1,62 @@
1
+ ---
2
+ /**
3
+ * An EscapeAll booking calendar: one room's, or every room's. See
4
+ * `escapeall.ts` for what it loads and keeps.
5
+ *
6
+ * `locale` is the page's. Attributes go on the calendar's element.
7
+ *
8
+ * ```astro
9
+ * <EscapeAll host="nl.escapeall.com" companyId="…" serviceId="…" template={9} locale={locale} />
10
+ * ```
11
+ */
12
+ import type { HTMLAttributes } from "astro/types";
13
+ import {
14
+ checkEscapeAll,
15
+ type EscapeAllWidget,
16
+ escapeAllAssets,
17
+ escapeAllLanguage,
18
+ } from "../booking/escapeall.ts";
19
+
20
+ type Props = HTMLAttributes<"div"> &
21
+ EscapeAllWidget & {
22
+ readonly locale: string;
23
+ };
24
+
25
+ const {
26
+ host,
27
+ companyId,
28
+ serviceId,
29
+ template,
30
+ skin,
31
+ players,
32
+ locale,
33
+ id,
34
+ ...attrs
35
+ } = Astro.props;
36
+ const widget = { host, companyId, serviceId, template, skin, players };
37
+
38
+ checkEscapeAll(widget, Astro.url.pathname);
39
+
40
+ const { stylesheets, script } = escapeAllAssets(widget);
41
+ // EscapeAll's script builds its selectors from the one it is given, so the
42
+ // calendar is found by id: the page's own, or one from the room or account.
43
+ const elementId = id ?? `escapeall-${serviceId ?? companyId}`;
44
+ ---
45
+
46
+ {stylesheets.map((href) => (
47
+ <link
48
+ rel='stylesheet'
49
+ href={href}
50
+ />
51
+ ))}
52
+ <script src="./escapeall-calendar.ts"></script>
53
+ <div
54
+ {...attrs}
55
+ id={elementId}
56
+ data-escapeall-company={companyId}
57
+ data-escapeall-service={serviceId}
58
+ data-escapeall-template={template}
59
+ data-escapeall-players={players}
60
+ data-escapeall-language={escapeAllLanguage(locale)}
61
+ data-escapeall-script={script}
62
+ ></div>
@@ -0,0 +1,32 @@
1
+ import { appendScript } from "./append-script.ts";
2
+
3
+ /**
4
+ * Draws each EscapeAll calendar on the page, once EscapeAll's script has
5
+ * loaded: the call its store scripts make, with the settings the component
6
+ * wrote on the element.
7
+ */
8
+ // Its script builds selectors by appending to the one it is given, so the
9
+ // calendar is named by its id, not handed over as an element.
10
+ type Calendar = (selector: string, settings: Record<string, unknown>) => void;
11
+
12
+ const host = window as unknown as { GetBookingCalendra?: Calendar };
13
+
14
+ const elements = [...document.querySelectorAll("[data-escapeall-company]")];
15
+ const [first] = elements;
16
+ if (first instanceof HTMLElement && first.dataset.escapeallScript) {
17
+ appendScript(first.dataset.escapeallScript).addEventListener("load", () => {
18
+ for (const element of elements) {
19
+ if (!(element instanceof HTMLElement)) continue;
20
+ const { escapeallCompany, escapeallService, escapeallLanguage } =
21
+ element.dataset;
22
+ host.GetBookingCalendra?.(`#${CSS.escape(element.id)}`, {
23
+ CompanyId: [escapeallCompany],
24
+ ...(escapeallService ? { ServiceId: [escapeallService] } : {}),
25
+ lang: escapeallLanguage,
26
+ Template: Number(element.dataset.escapeallTemplate),
27
+ InitialQuantity: Number(element.dataset.escapeallPlayers ?? 2),
28
+ FirstWeekDay: "Monday",
29
+ });
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,86 @@
1
+ import { invalidId, UUID, type Uuid } from "../analytics/ids.ts";
2
+
3
+ /**
4
+ * EscapeAll's booking calendar: a store's rooms and their free slots, booked
5
+ * on the page. One room's calendar when `serviceId` names it (EscapeAll calls
6
+ * a room a service), or every room's.
7
+ *
8
+ * EscapeAll's embed is a store script of its own that waits for EscapeAll's
9
+ * jQuery, then calls `GetBookingCalendra` from the shared `all.js` with the
10
+ * store's settings. The component makes that call itself, with the page's
11
+ * language, so it needs no store script. Read 2026-11-12 from Aruba's.
12
+ *
13
+ * It stores nothing as it renders, and loads nothing from anyone else; while
14
+ * a visitor books it keeps a phone confirmation in local storage. Its queue
15
+ * cookie is written only once the page sets EscapeAll's own consent cookie,
16
+ * which ours never does. All of it is in `storedItems`.
17
+ */
18
+ export interface EscapeAllWidget {
19
+ /** EscapeAll's regional site the account is on: `nl.escapeall.com`. */
20
+ readonly host: string;
21
+ /** The account's id, from EscapeAll: the store script's `CompanyId`. */
22
+ readonly companyId: Uuid;
23
+ /** One room's id, for its calendar alone; every room's without it. */
24
+ readonly serviceId?: Uuid;
25
+ /** The calendar's layout, by EscapeAll's number: the store's `Template`. */
26
+ readonly template: number;
27
+ /** The store's stylesheet, as EscapeAll hosts it: `CustomCss/<skin>.css`. */
28
+ readonly skin?: string;
29
+ /** The group size the calendar opens on: the store's `InitialQuantity`. */
30
+ readonly players?: number;
31
+ }
32
+
33
+ /** EscapeAll's own CSS variables: none it documents, so none taken. */
34
+ export type EscapeAllVars = Readonly<Partial<Record<string, never>>>;
35
+
36
+ /** The languages the calendar is translated into, as its paths take them. */
37
+ export const ESCAPEALL_LANGUAGES = ["en", "nl", "de"] as const;
38
+
39
+ export type EscapeAllLanguage = (typeof ESCAPEALL_LANGUAGES)[number];
40
+
41
+ /** EscapeAll's regional sites: `nl.escapeall.com`. */
42
+ const ESCAPEALL_HOST = /^[a-z]{2}\.escapeall\.com$/;
43
+
44
+ /** Fails the build on a calendar that would render nothing. */
45
+ export function checkEscapeAll(widget: EscapeAllWidget, at: string): void {
46
+ if (!ESCAPEALL_HOST.test(widget.host)) {
47
+ invalidId(
48
+ at,
49
+ `EscapeAll host "${widget.host}" is not one of EscapeAll's regional sites, like "nl.escapeall.com".`
50
+ );
51
+ }
52
+ for (const [name, id] of [
53
+ ["company", widget.companyId],
54
+ ["service", widget.serviceId],
55
+ ] as const) {
56
+ if (id !== undefined && !UUID.test(id)) {
57
+ invalidId(
58
+ at,
59
+ `EscapeAll ${name} id "${id}" is not a UUID, so the calendar would render nothing.`
60
+ );
61
+ }
62
+ }
63
+ }
64
+
65
+ /** The stylesheets and script a calendar on `host` needs. */
66
+ export function escapeAllAssets(widget: EscapeAllWidget) {
67
+ const base = `https://${widget.host}`;
68
+ return {
69
+ stylesheets: [
70
+ `${base}/Content/API/styles.css`,
71
+ ...(widget.skin === undefined
72
+ ? []
73
+ : [`${base}/CustomCss/${widget.skin}.css`]),
74
+ ],
75
+ script: `${base}/Scripts/API/all.js`,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * The calendar's language for a page's locale: English for one it is not
81
+ * translated into, as EscapeAll's own embeds fall back.
82
+ */
83
+ export function escapeAllLanguage(locale: string): EscapeAllLanguage {
84
+ const { language } = new Intl.Locale(locale);
85
+ return ESCAPEALL_LANGUAGES.find((known) => known === language) ?? "en";
86
+ }
@@ -2,6 +2,7 @@ import type {
2
2
  EscapeNavigatorVars,
3
3
  EscapeNavigatorWidget,
4
4
  } from "./escape-navigator.ts";
5
+ import type { EscapeAllVars, EscapeAllWidget } from "./escapeall.ts";
5
6
  import type { FourEscapeVars, FourEscapeWidget } from "./four-escape.ts";
6
7
 
7
8
  /**
@@ -10,7 +11,8 @@ import type { FourEscapeVars, FourEscapeWidget } from "./four-escape.ts";
10
11
  */
11
12
  export type BookingWidget =
12
13
  | ({ readonly vendor: "4escape" } & FourEscapeWidget)
13
- | ({ readonly vendor: "escapenavigator" } & EscapeNavigatorWidget);
14
+ | ({ readonly vendor: "escapenavigator" } & EscapeNavigatorWidget)
15
+ | ({ readonly vendor: "escapeall" } & EscapeAllWidget);
14
16
 
15
17
  /** Who makes a booking widget: what `storedItems` takes for their rows. */
16
18
  export type BookingVendor = BookingWidget["vendor"];
@@ -19,6 +21,7 @@ export type BookingVendor = BookingWidget["vendor"];
19
21
  export interface BookingVendorVars {
20
22
  readonly "4escape": FourEscapeVars;
21
23
  readonly escapenavigator: EscapeNavigatorVars;
24
+ readonly escapeall: EscapeAllVars;
22
25
  }
23
26
 
24
27
  /** The CSS variables a widget takes: its own vendor's, and no other's. */
package/src/index.ts CHANGED
@@ -62,6 +62,15 @@ export {
62
62
  type EscapeNavigatorWidget,
63
63
  escapeNavigatorLanguage,
64
64
  } from "./booking/escape-navigator.ts";
65
+ export {
66
+ checkEscapeAll,
67
+ ESCAPEALL_LANGUAGES,
68
+ type EscapeAllLanguage,
69
+ type EscapeAllVars,
70
+ type EscapeAllWidget,
71
+ escapeAllAssets,
72
+ escapeAllLanguage,
73
+ } from "./booking/escapeall.ts";
65
74
  export {
66
75
  checkFourEscape,
67
76
  FOUR_ESCAPE_LANGUAGES,