@mrrocket/sdk 0.16.0 → 0.17.0
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/CHANGELOG.md +19 -0
- package/README.md +11 -2
- package/dist/api-client.d.ts +7 -1
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +9 -2
- package/dist/browser/mrrocket-messenger.js +42 -43
- package/dist/browser/mrrocket.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +64 -14
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows.d.ts +49 -3
- package/dist/flows.js +86 -7
- package/dist/flows.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +10 -2
- package/dist/messenger-ui.js +57 -46
- package/dist/messenger-ui.js.map +1 -1
- package/dist/react-native/index.d.cts +46 -7
- package/dist/react-native/index.d.ts +46 -7
- package/dist/react-native/index.js +135 -31
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +40 -41
- package/dist/styles-css.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package. It follows semantic versioning; before 1.0 a minor version may change an API.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
- **Messenger brand:** MRRocket's look now sends `logo` (an uploaded logo of any shape, else the favicon; Home shows it up to 132 px wide), `icon` (the favicon, now the round mark in the chat's head and on the teaser instead of the first letter), `agentName` (on the AI agent's answers instead of "AI agent") and `agentPhoto` (a small round face beside them). React Native reads `logo` and `agentName`.
|
|
8
|
+
- **React Native tab bar:** only the open tab shows its name; Home, Messages and Help are icons otherwise, so the bar fits a narrow phone.
|
|
9
|
+
- **Cleaner chat field:** the optional email is the first line inside the message box ("Email for our reply (optional)"), marked so password managers leave it alone; the paperclip's menu opens above the paperclip. The app (React Native) puts the email inside the message box the same way.
|
|
10
|
+
|
|
11
|
+
## 0.17.0 (not published yet)
|
|
12
|
+
|
|
13
|
+
- **Onboarding as tours, checklists and modals:** MRRocket's onboarding items are now one type each and arrive as plain lists (no `nodes`). Older configs without the new fields behave as before.
|
|
14
|
+
- **The bubble's side:** tour steps take `placement` (`"auto" | "top" | "bottom" | "left" | "right"`, type `TourPlacement`). Auto keeps the old placing (below, above when there is no room); a chosen side flips to the opposite one, then to any side with room. The card now has an arrow pointing at the element and is placed again once it has its real height (a picture loading too).
|
|
15
|
+
- **Pictures and videos:** tour steps and modals take `media` (`{ kind: "image" | "video", url, alt? }`, type `FlowMedia`), drawn above the text. Videos from YouTube, Vimeo and Loom play in their privacy-friendly players (youtube-nocookie.com, Vimeo `dnt=1`), mp4/webm files in `<video controls>`; other links and non-http(s) addresses are left out. New helpers `videoEmbed(url)` and `mediaOf(step)`. React Native shows the picture with `Image` and opens a video from a "Watch the video" button (`Linking`).
|
|
16
|
+
- **Checklist tasks start tours:** a task with `startsTour` opens that tour (`flows.onboarding.startTour(stepId)`, also wired in the web and React Native checklists); the checklist comes back when the tour ends or is closed, a finished tour ticks a task without an event, and the start is recorded as `$onboarding {flow, action: "started", from}`. A tour with the trigger "From a checklist" (`FROM_CHECKLIST`) never starts on its own or from `startOnboarding()` without an id.
|
|
17
|
+
- **Page triggers:** a trigger that is a path ("/settings", "/projects/*", `isPageTrigger`) starts its item on that page, also after a client-side navigation (`$pageview`). Web only; React Native has no page path.
|
|
18
|
+
- **Signed-in only:** `audience: "signed_in"` on an item starts it on its own only after `identify`.
|
|
19
|
+
- **Modal links:** `url` on a modal opens after its button (http(s) or an app path only).
|
|
20
|
+
- **The next item follows:** when an item ends, the next unfinished one of the same moment ("First launch", this page, or the same event trigger) starts right away; items MRRocket split from one older mixed flow share its trigger.
|
|
21
|
+
- `sdk.js` budget 16.25 KB gzip (was 15.75); `sdk-flows.js` grows by about 1.1 KB gzip (8.4 KB) for pictures, videos and the sides.
|
|
22
|
+
- Version reported in event batches: `0.17.0`.
|
|
23
|
+
|
|
5
24
|
## 0.16.0 (not published yet)
|
|
6
25
|
|
|
7
26
|
- **Identity verification:** your server signs the user id with the product's server key and the SDK sends that signature as `userHash` with every request for the id, so someone who only knows a user's id can no longer read their support chat, register push for them or post feedback and events as them. Server: `userHash(userId, { serverKey? })` from `@mrrocket/sdk/next/server` (WebCrypto only; Node 18+, Edge, Workers; defaults to `MRROCKET_SERVER_KEY`, throws without a key). The formula for other backends: `hex(HMAC-SHA256(key = SHA-256(server key), message = user id))`.
|
package/README.md
CHANGED
|
@@ -423,13 +423,22 @@ Since 0.15.0 an answer can ask one follow-up question, show a different offer by
|
|
|
423
423
|
|
|
424
424
|
MRRocket never changes a subscription (its payment key is read-only): `onCancel` and `onSave` are your code. Only after they succeed is the outcome recorded, and MRRocket shows it under Churn as a cancellation or a save with the customer's plan and monthly revenue (matched by your `identify()` id, or the email you passed with it). If one throws, the dialog stays open with "Something went wrong. Please try again." Script tag: `mrrocket.cancelFlow.open({ … })`.
|
|
425
425
|
|
|
426
|
-
**Onboarding** (Conversion › Onboarding): the tour, checklist
|
|
426
|
+
**Onboarding** (Conversion › Onboarding): the product's live tours, checklists and modals. Each is one thing: a **tour** (bubbles that point at elements, one after another), a **checklist** (tasks in the corner) or a **modal** (one window). An item starts on its own when the person does its trigger: `track("Signed up")` (or `signup`, `sign_up`) for "Signed up", `track("first_export")` for "First export", the first page or screen for "First launch" / "First visit", and since 0.17.0 a page of your app ("/settings", "/projects/*", also after client-side navigation), once per person. Automatic starts need a signed-in person (`identify`) or stored progress (`consent="cookies"` / `"persistent"`), so nobody gets the same tour on every page; an item set to "Signed-in people only" (0.17.0) waits for `identify`. When an item ends, the next one of the same moment follows. `useCockpit().startOnboarding()` (script tag `mrrocket.onboarding.start()`, React Native `useOnboarding().start()`) starts one whenever you want; `onboarding={false}` on the provider switches the automatic start off. A tour step points at the element marked `data-ck-target="…"` or a CSS selector (web); a checklist task with an event ticks itself when you track it.
|
|
427
|
+
|
|
428
|
+
Since 0.17.0:
|
|
429
|
+
|
|
430
|
+
- **The bubble's side:** a tour step has `placement` `"auto" | "top" | "bottom" | "left" | "right"`. Auto (or no value) is the behaviour before: below the element, above it when there is no room. A chosen side flips to the opposite one, then to any side with room; an arrow points at the element. React Native shows tour steps as a card at the bottom, so the side does not apply there.
|
|
431
|
+
- **Pictures and videos:** tour steps and modals may carry `media: { kind: "image" | "video", url, alt? }`, shown above the text. A video is a YouTube, Vimeo or Loom link, played in their privacy-friendly players (`youtube-nocookie.com`, Vimeo with `dnt=1`), or a video file (mp4, webm) in a plain `<video controls>`; anything else is left out (`videoEmbed(url)` says how a link plays). React Native shows the picture and opens a video from a "Watch the video" button.
|
|
432
|
+
- **Tours from a checklist:** a checklist task with `startsTour` (another item's id) opens that tour; the checklist comes back when it ends, and a finished tour ticks a task that has no event of its own. A tour whose trigger is "From a checklist" never starts on its own.
|
|
433
|
+
- **A modal's link:** `url` on a modal (http(s) or a path of your app) opens after its button.
|
|
434
|
+
|
|
435
|
+
Older configs without these fields show exactly as before, and older tracking codes leave the new fields out (a bubble below its element, no picture, a task ticked by hand). Emails of older onboarding flows go out from MRRocket.
|
|
427
436
|
|
|
428
437
|
**In-app messages** (Lifecycle › Journeys, an "In-app message" step): a signed-in person's pending messages load after `identify()` and show as a banner or a dialog. Clicking or closing it moves the person on in the journey; after 7 days without an answer they go on anyway.
|
|
429
438
|
|
|
430
439
|
**Look:** `appearance={{ theme: "dark", accent: "#0f766e", className: "my-dialogs" }}` on `CockpitProvider` (React Native: `{ theme, accent }`; script tag: `data-theme`, `data-accent`). On the web the dialogs read CSS variables on `.ckf-root`: `--ckf-accent`, `--ckf-bg`, `--ckf-fg`, `--ckf-muted`, `--ckf-line`, `--ckf-radius`, `--ckf-font`. React Native draws them next to your app through `MRRocketProvider` (`flows={false}` to draw `client.flows` yourself with `<MRRocketFlows flows={client.flows} />`); tour steps show as a card there.
|
|
431
440
|
|
|
432
|
-
**Events** (under your analytics consent): `$cancel_flow` `{ action: "shown" | "answered" | "offer" | "finished", run, version, reason?, offer?, outcome?, comment? }`, `$onboarding` `{ flow, step?, action: "started" | "shown" | "completed" | "dismissed" }
|
|
441
|
+
**Events** (under your analytics consent): `$cancel_flow` `{ action: "shown" | "answered" | "offer" | "finished", run, version, reason?, offer?, outcome?, comment? }`, `$onboarding` `{ flow, step?, action: "started" | "shown" | "completed" | "dismissed", from? }` (`from`: the checklist a tour was started from, 0.17.0), `$inapp` `{ message, journey, action: "shown" | "clicked" | "dismissed" }`. With consent `"off"` the cancel flow still works, but nothing is recorded, and no onboarding or messages show.
|
|
433
442
|
|
|
434
443
|
## 16. Push notifications (0.7.0)
|
|
435
444
|
|
package/dist/api-client.d.ts
CHANGED
|
@@ -186,8 +186,14 @@ export interface MessengerLook {
|
|
|
186
186
|
header?: "plain" | "color";
|
|
187
187
|
/** "Powered by MRRocket" at the bottom; the server sends false only on plans that may hide it (from 0.15). */
|
|
188
188
|
branding?: boolean;
|
|
189
|
-
/** The
|
|
189
|
+
/** The logo at the top left of Home: the one uploaded in MRRocket, else the product's favicon; without it the title's first letter. */
|
|
190
190
|
logo?: string;
|
|
191
|
+
/** The product's favicon, the round mark in the chat's head (from 0.17; without it the title's first letter). */
|
|
192
|
+
icon?: string;
|
|
193
|
+
/** The AI agent's name on its answers (from 0.17; empty or left out: "AI agent"). */
|
|
194
|
+
agentName?: string;
|
|
195
|
+
/** The AI agent's picture beside its answers (from 0.17). */
|
|
196
|
+
agentPhoto?: string;
|
|
191
197
|
}
|
|
192
198
|
/** The link behind "Powered by MRRocket". */
|
|
193
199
|
export declare const BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.\n * `./api` re-exports all of it with the `useApi` hook.\n */\nimport { warnUnsigned } from \"./identity\";\n\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n review: \"Under review\",\n planned: \"Planned\",\n progress: \"In progress\",\n shipped: \"Shipped\",\n};\n\nexport interface FeedbackBoardInfo {\n id: string;\n label: string;\n}\n\nexport interface FeedbackPost {\n id: string;\n title: string;\n body: string;\n board: string;\n status: PostStatus;\n votes: number;\n comments: number;\n voted: boolean;\n createdAt: string;\n /** \"Lena K.\", \"Anonymous\" or \"Team\". Missing from servers before SDK 0.6. */\n author?: string;\n}\n\n/** The product's public face for the board's header. */\nexport interface FeedbackProduct {\n name: string;\n logo: string | null;\n /** The domain's favicon, tried when `logo` does not load. Older servers leave it out. */\n logoFallback?: string | null;\n}\n\nexport interface FeedbackList {\n boards: FeedbackBoardInfo[];\n posts: FeedbackPost[];\n product?: FeedbackProduct;\n}\n\nexport interface FeedbackRoadmap {\n /** `review` (Backlog) comes from servers of SDK 0.6 on. */\n columns: { review?: FeedbackPost[]; planned: FeedbackPost[]; progress: FeedbackPost[]; shipped: FeedbackPost[] };\n}\n\nexport interface ChangelogEntry {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n}\n\nexport interface FeedbackComment {\n id: string;\n body: string;\n author: string;\n isTeam: boolean;\n postedAt: string;\n}\n\nexport type SurveyQuestionKind = \"nps\" | \"rating\" | \"single\" | \"multi\" | \"text\";\n\nexport interface SurveyQuestion {\n key: string;\n kind: SurveyQuestionKind;\n title: string;\n description?: string;\n required: boolean;\n options?: string[];\n scale?: number;\n low?: string;\n high?: string;\n placeholder?: string;\n}\n\nexport interface SurveyDefinition {\n id: string;\n name: string;\n type: string;\n thanks: string;\n questions: SurveyQuestion[];\n}\n\nexport type SurveyAnswer = number | string | string[];\n\n/** A file in the messenger's chat: a screenshot, a photo or a PDF the visitor sent (servers from SDK 0.15 on). */\nexport interface ChatFile {\n /** From an upload: the signed id a message sends. In a message: the file's own id (`attachmentUrl` reads it). */\n id: string;\n name: string;\n type: \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"application/pdf\";\n /** Bytes. */\n size: number;\n}\n\n/** What the messenger can send: pictures and PDFs, at most 10 MB each, 4 per message. */\nexport const CHAT_FILE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"application/pdf\"] as const;\nexport const CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;\nexport const CHAT_FILES_PER_MESSAGE = 4;\n\n/** A file to upload: a Blob or File on the web, the image picker's `{ uri, name, type }` in React Native. */\nexport type ChatUpload = Blob | { uri: string; name: string; type: string };\n\n/** The bug context a visitor message carries; the server fills browser and system from the user agent when left out. */\nexport interface ChatContext {\n browser?: string;\n os?: string;\n /** \"1440×900\". */\n viewport?: string;\n}\n\n/** One message of the messenger's chat. \"visitor\" is the person on the site; the rest come from the product's team. */\nexport interface ChatMessage {\n id: string;\n from: \"visitor\" | \"team\" | \"ai\" | \"auto\";\n body: string;\n at: string;\n /** Help articles an answer drew on (servers from SDK 0.9 on). */\n articles?: { id: string; title: string }[];\n /** Files sent with it (servers from SDK 0.15 on). */\n attachments?: ChatFile[];\n}\n\n/** A published help article in a list (the messenger's Help tab). */\nexport interface ArticleSummary {\n id: string;\n title: string;\n collection: string;\n excerpt: string;\n updatedAt: string;\n}\n\n/** A run of text inside an article block. Links are http(s), mailto or tel only. */\nexport interface ArticleSpan {\n text: string;\n href?: string;\n bold?: boolean;\n italic?: boolean;\n code?: boolean;\n}\n\n/** An article's content as blocks, written with textContent (never parsed as HTML). */\nexport type ArticleBlock =\n | { type: \"h2\" | \"h3\" | \"p\" | \"quote\"; spans: ArticleSpan[] }\n | { type: \"ul\" | \"ol\"; items: ArticleSpan[][] }\n | { type: \"code\"; text: string }\n | { type: \"hr\" };\n\nexport interface Article {\n id: string;\n title: string;\n collection: string;\n updatedAt: string;\n blocks: ArticleBlock[];\n}\n\n/** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side. */\nexport type MessengerPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"middle-left\" | \"middle-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\n/** How round the messenger is. */\nexport type MessengerCorners = \"round\" | \"soft\" | \"square\";\n\n/** The radii per corner style, in px: the panel or sheet, message bubbles (and their tail), cards, fields and buttons. */\nexport const CORNERS: Record<MessengerCorners, { panel: number; bubble: number; tail: number; card: number; field: number }> = {\n round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },\n soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },\n square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 },\n};\n\n/** The messenger's look and texts as set in MRRocket › Support › Messenger (servers from SDK 0.9 on). */\nexport interface MessengerLook {\n accent: string;\n /** Up to two more colors blended into Home's background glow (MRRocket from 0.16; older servers leave it out). */\n glow?: string[];\n theme: \"light\" | \"dark\" | \"auto\";\n position: MessengerPosition;\n title: string;\n greeting: string;\n label: string;\n /** How soon the team usually answers (MRRocket from 0.10; older servers leave it out). */\n replyTime?: \"minutes\" | \"hours\" | \"day\";\n /** A round chat button in the app (React Native) too. */\n appLauncher?: boolean;\n help: boolean;\n sound: boolean;\n preview: boolean;\n askEmail: boolean;\n /** How round panel, bubbles, cards and fields are (MRRocket from 0.15; older servers leave it out: round). */\n corners?: MessengerCorners;\n /** The site's own font (the default) or the system's (web only; from 0.15). */\n font?: \"site\" | \"system\";\n /** Home's top: plain like the rest, or in the accent color (from 0.15; older servers leave it out: plain). */\n header?: \"plain\" | \"color\";\n /** \"Powered by MRRocket\" at the bottom; the server sends false only on plans that may hide it (from 0.15). */\n branding?: boolean;\n /** The product's icon (its favicon) at the top left of Home; without it the title's first letter. */\n logo?: string;\n}\n\n/** The link behind \"Powered by MRRocket\". */\nexport const BRAND_URL = \"https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by\";\n\n/** The line under \"Send us a message\" and in the chat's head. */\nexport const replyLine = (t: MessengerLook[\"replyTime\"]) =>\n t === \"minutes\" ? \"We usually reply in a few minutes\" : t === \"day\" ? \"We usually reply within a day\" : \"We usually reply in a few hours\";\n\n/** Black or white, whichever reads better on the accent (a light custom color gets dark text). */\nexport const inkOn = (hex: string) => {\n const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);\n if (!m) return \"#ffffff\";\n const [r, g, b] = [m[1], m[2], m[3]].map((x) => {\n const c = parseInt(x, 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? \"#111214\" : \"#ffffff\";\n};\n\n/** The visitor's newest chat (open or closed) and its messages. */\nexport interface ChatThread {\n conversation: { id: string; status: \"open\" | \"solved\" } | null;\n messages: ChatMessage[];\n /** The visitor gave an email already: the messenger stops asking. */\n hasEmail: boolean;\n}\n\nexport class ApiError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\ninterface Conn {\n host: string;\n apiKey: string;\n distinctId: string;\n user?: { id?: string; [key: string]: unknown };\n /**\n * The identity hash of `distinctId` (0.16.0, see identity.ts), sent with every request that names the visitor: a\n * JSON field next to `distinctId`, a query parameter, a form field. Leave it out for any other id.\n */\n userHash?: string;\n}\n\n// Not URLSearchParams: React Native's lacks `set` in older versions, and the apps use this client too.\nconst qs = (params: Record<string, string | undefined>) =>\n Object.entries(params)\n .filter((e): e is [string, string] => !!e[1])\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n\nconst call = async <T>(url: string, init: RequestInit | undefined, signal?: AbortSignal): Promise<T> => {\n let res: Response;\n try {\n res = await fetch(url, {\n ...init,\n signal,\n // A form (a file upload) sets its own multipart type with the boundary.\n headers: typeof init?.body === \"string\" ? { \"content-type\": \"application/json\" } : undefined,\n });\n } catch (e) {\n if ((e as Error).name === \"AbortError\") throw e;\n throw new ApiError(\"Could not reach the server. Check your connection and try again.\", 0);\n }\n if (!res.ok) {\n // A missing or wrong identity hash: the console says once how to sign the user id.\n warnUnsigned(res.status);\n let detail = \"\";\n try {\n detail = ((await res.json()) as { error?: string }).error ?? \"\";\n } catch {\n // Not JSON; the status is enough.\n }\n throw new ApiError(detail || (res.status === 429 ? \"Too many requests. Try again in a minute.\" : \"Something went wrong. Please try again.\"), res.status);\n }\n return (await res.json()) as T;\n};\n\n/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */\nexport const createApi = (c: Conn) => {\n const base = `${c.host}/api/sdk`;\n const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };\n // Every query that names the visitor names its hash too.\n const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };\n const post = <T>(path: string, body: Record<string, unknown>, signal?: AbortSignal) =>\n call<T>(`${base}${path}`, { method: \"POST\", body: JSON.stringify({ ...who, ...body }) }, signal);\n return {\n list: (o: { board?: string; sort?: \"top\" | \"new\"; q?: string }, signal?: AbortSignal) =>\n call<FeedbackList>(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, undefined, signal),\n post: (id: string, signal?: AbortSignal) =>\n call<FeedbackPost>(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n roadmap: (signal?: AbortSignal) => call<FeedbackRoadmap>(`${base}/feedback/roadmap?${qs(me)}`, undefined, signal),\n changelog: (signal?: AbortSignal) => call<{ entries: ChangelogEntry[] }>(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, undefined, signal),\n submit: (p: { title: string; body?: string; board: string }) => post<FeedbackPost>(\"/feedback\", p),\n vote: (id: string) => post<{ voted: boolean; votes: number }>(`/feedback/${encodeURIComponent(id)}/vote`, {}),\n comments: (id: string, signal?: AbortSignal) =>\n call<{ comments: FeedbackComment[] }>(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, undefined, signal),\n comment: (id: string, body: string) => post<FeedbackComment>(`/feedback/${encodeURIComponent(id)}/comments`, { body }),\n survey: (url: string, signal?: AbortSignal) =>\n call<{ survey: SurveyDefinition | null }>(`${base}/surveys?${qs({ ...me, url })}`, undefined, signal),\n seen: (id: string) => post<unknown>(`/surveys/${encodeURIComponent(id)}/seen`, { user: undefined }),\n respond: (id: string, r: { answers: Record<string, SurveyAnswer>; completed: boolean; seconds: number }) =>\n post<unknown>(`/surveys/${encodeURIComponent(id)}/respond`, r),\n chat: (signal?: AbortSignal) => call<ChatThread>(`${base}/messages?${qs(me)}`, undefined, signal),\n /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */\n sendChat: (m: { body: string; email?: string; url?: string; attachments?: string[]; context?: ChatContext }) => post<ChatThread>(\"/messages\", m),\n /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */\n uploadChatFile: (file: ChatUpload, signal?: AbortSignal) => {\n const form = new FormData();\n form.append(\"key\", c.apiKey);\n form.append(\"distinctId\", c.distinctId);\n if (c.userHash) form.append(\"userHash\", c.userHash);\n if (\"uri\" in file) form.append(\"file\", file as unknown as Blob);\n else form.append(\"file\", file, (file as Blob & { name?: string }).name || \"file\");\n return call<ChatFile>(`${base}/messages/attachments`, { method: \"POST\", body: form }, signal);\n },\n /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */\n attachmentUrl: (id: string) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,\n articles: (q?: string, signal?: AbortSignal) => call<{ articles: ArticleSummary[] }>(`${base}/articles?${qs({ key: c.apiKey, q })}`, undefined, signal),\n article: (id: string, signal?: AbortSignal) =>\n call<Article>(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n messengerLook: (signal?: AbortSignal) => call<MessengerLook>(`${base}/messenger?${qs({ key: c.apiKey })}`, undefined, signal),\n rateArticle: (id: string, helpful: boolean) => post<{ counted: boolean }>(`/articles/${encodeURIComponent(id)}/rate`, { helpful }),\n };\n};\n\nexport type Api = ReturnType<typeof createApi>;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.\n * `./api` re-exports all of it with the `useApi` hook.\n */\nimport { warnUnsigned } from \"./identity\";\n\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n review: \"Under review\",\n planned: \"Planned\",\n progress: \"In progress\",\n shipped: \"Shipped\",\n};\n\nexport interface FeedbackBoardInfo {\n id: string;\n label: string;\n}\n\nexport interface FeedbackPost {\n id: string;\n title: string;\n body: string;\n board: string;\n status: PostStatus;\n votes: number;\n comments: number;\n voted: boolean;\n createdAt: string;\n /** \"Lena K.\", \"Anonymous\" or \"Team\". Missing from servers before SDK 0.6. */\n author?: string;\n}\n\n/** The product's public face for the board's header. */\nexport interface FeedbackProduct {\n name: string;\n logo: string | null;\n /** The domain's favicon, tried when `logo` does not load. Older servers leave it out. */\n logoFallback?: string | null;\n}\n\nexport interface FeedbackList {\n boards: FeedbackBoardInfo[];\n posts: FeedbackPost[];\n product?: FeedbackProduct;\n}\n\nexport interface FeedbackRoadmap {\n /** `review` (Backlog) comes from servers of SDK 0.6 on. */\n columns: { review?: FeedbackPost[]; planned: FeedbackPost[]; progress: FeedbackPost[]; shipped: FeedbackPost[] };\n}\n\nexport interface ChangelogEntry {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n}\n\nexport interface FeedbackComment {\n id: string;\n body: string;\n author: string;\n isTeam: boolean;\n postedAt: string;\n}\n\nexport type SurveyQuestionKind = \"nps\" | \"rating\" | \"single\" | \"multi\" | \"text\";\n\nexport interface SurveyQuestion {\n key: string;\n kind: SurveyQuestionKind;\n title: string;\n description?: string;\n required: boolean;\n options?: string[];\n scale?: number;\n low?: string;\n high?: string;\n placeholder?: string;\n}\n\nexport interface SurveyDefinition {\n id: string;\n name: string;\n type: string;\n thanks: string;\n questions: SurveyQuestion[];\n}\n\nexport type SurveyAnswer = number | string | string[];\n\n/** A file in the messenger's chat: a screenshot, a photo or a PDF the visitor sent (servers from SDK 0.15 on). */\nexport interface ChatFile {\n /** From an upload: the signed id a message sends. In a message: the file's own id (`attachmentUrl` reads it). */\n id: string;\n name: string;\n type: \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"application/pdf\";\n /** Bytes. */\n size: number;\n}\n\n/** What the messenger can send: pictures and PDFs, at most 10 MB each, 4 per message. */\nexport const CHAT_FILE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"application/pdf\"] as const;\nexport const CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;\nexport const CHAT_FILES_PER_MESSAGE = 4;\n\n/** A file to upload: a Blob or File on the web, the image picker's `{ uri, name, type }` in React Native. */\nexport type ChatUpload = Blob | { uri: string; name: string; type: string };\n\n/** The bug context a visitor message carries; the server fills browser and system from the user agent when left out. */\nexport interface ChatContext {\n browser?: string;\n os?: string;\n /** \"1440×900\". */\n viewport?: string;\n}\n\n/** One message of the messenger's chat. \"visitor\" is the person on the site; the rest come from the product's team. */\nexport interface ChatMessage {\n id: string;\n from: \"visitor\" | \"team\" | \"ai\" | \"auto\";\n body: string;\n at: string;\n /** Help articles an answer drew on (servers from SDK 0.9 on). */\n articles?: { id: string; title: string }[];\n /** Files sent with it (servers from SDK 0.15 on). */\n attachments?: ChatFile[];\n}\n\n/** A published help article in a list (the messenger's Help tab). */\nexport interface ArticleSummary {\n id: string;\n title: string;\n collection: string;\n excerpt: string;\n updatedAt: string;\n}\n\n/** A run of text inside an article block. Links are http(s), mailto or tel only. */\nexport interface ArticleSpan {\n text: string;\n href?: string;\n bold?: boolean;\n italic?: boolean;\n code?: boolean;\n}\n\n/** An article's content as blocks, written with textContent (never parsed as HTML). */\nexport type ArticleBlock =\n | { type: \"h2\" | \"h3\" | \"p\" | \"quote\"; spans: ArticleSpan[] }\n | { type: \"ul\" | \"ol\"; items: ArticleSpan[][] }\n | { type: \"code\"; text: string }\n | { type: \"hr\" };\n\nexport interface Article {\n id: string;\n title: string;\n collection: string;\n updatedAt: string;\n blocks: ArticleBlock[];\n}\n\n/** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side. */\nexport type MessengerPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"middle-left\" | \"middle-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\n/** How round the messenger is. */\nexport type MessengerCorners = \"round\" | \"soft\" | \"square\";\n\n/** The radii per corner style, in px: the panel or sheet, message bubbles (and their tail), cards, fields and buttons. */\nexport const CORNERS: Record<MessengerCorners, { panel: number; bubble: number; tail: number; card: number; field: number }> = {\n round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },\n soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },\n square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 },\n};\n\n/** The messenger's look and texts as set in MRRocket › Support › Messenger (servers from SDK 0.9 on). */\nexport interface MessengerLook {\n accent: string;\n /** Up to two more colors blended into Home's background glow (MRRocket from 0.16; older servers leave it out). */\n glow?: string[];\n theme: \"light\" | \"dark\" | \"auto\";\n position: MessengerPosition;\n title: string;\n greeting: string;\n label: string;\n /** How soon the team usually answers (MRRocket from 0.10; older servers leave it out). */\n replyTime?: \"minutes\" | \"hours\" | \"day\";\n /** A round chat button in the app (React Native) too. */\n appLauncher?: boolean;\n help: boolean;\n sound: boolean;\n preview: boolean;\n askEmail: boolean;\n /** How round panel, bubbles, cards and fields are (MRRocket from 0.15; older servers leave it out: round). */\n corners?: MessengerCorners;\n /** The site's own font (the default) or the system's (web only; from 0.15). */\n font?: \"site\" | \"system\";\n /** Home's top: plain like the rest, or in the accent color (from 0.15; older servers leave it out: plain). */\n header?: \"plain\" | \"color\";\n /** \"Powered by MRRocket\" at the bottom; the server sends false only on plans that may hide it (from 0.15). */\n branding?: boolean;\n /** The logo at the top left of Home: the one uploaded in MRRocket, else the product's favicon; without it the title's first letter. */\n logo?: string;\n /** The product's favicon, the round mark in the chat's head (from 0.17; without it the title's first letter). */\n icon?: string;\n /** The AI agent's name on its answers (from 0.17; empty or left out: \"AI agent\"). */\n agentName?: string;\n /** The AI agent's picture beside its answers (from 0.17). */\n agentPhoto?: string;\n}\n\n/** The link behind \"Powered by MRRocket\". */\nexport const BRAND_URL = \"https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by\";\n\n/** The line under \"Send us a message\" and in the chat's head. */\nexport const replyLine = (t: MessengerLook[\"replyTime\"]) =>\n t === \"minutes\" ? \"We usually reply in a few minutes\" : t === \"day\" ? \"We usually reply within a day\" : \"We usually reply in a few hours\";\n\n/** Black or white, whichever reads better on the accent (a light custom color gets dark text). */\nexport const inkOn = (hex: string) => {\n const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);\n if (!m) return \"#ffffff\";\n const [r, g, b] = [m[1], m[2], m[3]].map((x) => {\n const c = parseInt(x, 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? \"#111214\" : \"#ffffff\";\n};\n\n/** The visitor's newest chat (open or closed) and its messages. */\nexport interface ChatThread {\n conversation: { id: string; status: \"open\" | \"solved\" } | null;\n messages: ChatMessage[];\n /** The visitor gave an email already: the messenger stops asking. */\n hasEmail: boolean;\n}\n\nexport class ApiError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\ninterface Conn {\n host: string;\n apiKey: string;\n distinctId: string;\n user?: { id?: string; [key: string]: unknown };\n /**\n * The identity hash of `distinctId` (0.16.0, see identity.ts), sent with every request that names the visitor: a\n * JSON field next to `distinctId`, a query parameter, a form field. Leave it out for any other id.\n */\n userHash?: string;\n}\n\n// Not URLSearchParams: React Native's lacks `set` in older versions, and the apps use this client too.\nconst qs = (params: Record<string, string | undefined>) =>\n Object.entries(params)\n .filter((e): e is [string, string] => !!e[1])\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n\nconst call = async <T>(url: string, init: RequestInit | undefined, signal?: AbortSignal): Promise<T> => {\n let res: Response;\n try {\n res = await fetch(url, {\n ...init,\n signal,\n // A form (a file upload) sets its own multipart type with the boundary.\n headers: typeof init?.body === \"string\" ? { \"content-type\": \"application/json\" } : undefined,\n });\n } catch (e) {\n if ((e as Error).name === \"AbortError\") throw e;\n throw new ApiError(\"Could not reach the server. Check your connection and try again.\", 0);\n }\n if (!res.ok) {\n // A missing or wrong identity hash: the console says once how to sign the user id.\n warnUnsigned(res.status);\n let detail = \"\";\n try {\n detail = ((await res.json()) as { error?: string }).error ?? \"\";\n } catch {\n // Not JSON; the status is enough.\n }\n throw new ApiError(detail || (res.status === 429 ? \"Too many requests. Try again in a minute.\" : \"Something went wrong. Please try again.\"), res.status);\n }\n return (await res.json()) as T;\n};\n\n/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */\nexport const createApi = (c: Conn) => {\n const base = `${c.host}/api/sdk`;\n const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };\n // Every query that names the visitor names its hash too.\n const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };\n const post = <T>(path: string, body: Record<string, unknown>, signal?: AbortSignal) =>\n call<T>(`${base}${path}`, { method: \"POST\", body: JSON.stringify({ ...who, ...body }) }, signal);\n return {\n list: (o: { board?: string; sort?: \"top\" | \"new\"; q?: string }, signal?: AbortSignal) =>\n call<FeedbackList>(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, undefined, signal),\n post: (id: string, signal?: AbortSignal) =>\n call<FeedbackPost>(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n roadmap: (signal?: AbortSignal) => call<FeedbackRoadmap>(`${base}/feedback/roadmap?${qs(me)}`, undefined, signal),\n changelog: (signal?: AbortSignal) => call<{ entries: ChangelogEntry[] }>(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, undefined, signal),\n submit: (p: { title: string; body?: string; board: string }) => post<FeedbackPost>(\"/feedback\", p),\n vote: (id: string) => post<{ voted: boolean; votes: number }>(`/feedback/${encodeURIComponent(id)}/vote`, {}),\n comments: (id: string, signal?: AbortSignal) =>\n call<{ comments: FeedbackComment[] }>(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, undefined, signal),\n comment: (id: string, body: string) => post<FeedbackComment>(`/feedback/${encodeURIComponent(id)}/comments`, { body }),\n survey: (url: string, signal?: AbortSignal) =>\n call<{ survey: SurveyDefinition | null }>(`${base}/surveys?${qs({ ...me, url })}`, undefined, signal),\n seen: (id: string) => post<unknown>(`/surveys/${encodeURIComponent(id)}/seen`, { user: undefined }),\n respond: (id: string, r: { answers: Record<string, SurveyAnswer>; completed: boolean; seconds: number }) =>\n post<unknown>(`/surveys/${encodeURIComponent(id)}/respond`, r),\n chat: (signal?: AbortSignal) => call<ChatThread>(`${base}/messages?${qs(me)}`, undefined, signal),\n /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */\n sendChat: (m: { body: string; email?: string; url?: string; attachments?: string[]; context?: ChatContext }) => post<ChatThread>(\"/messages\", m),\n /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */\n uploadChatFile: (file: ChatUpload, signal?: AbortSignal) => {\n const form = new FormData();\n form.append(\"key\", c.apiKey);\n form.append(\"distinctId\", c.distinctId);\n if (c.userHash) form.append(\"userHash\", c.userHash);\n if (\"uri\" in file) form.append(\"file\", file as unknown as Blob);\n else form.append(\"file\", file, (file as Blob & { name?: string }).name || \"file\");\n return call<ChatFile>(`${base}/messages/attachments`, { method: \"POST\", body: form }, signal);\n },\n /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */\n attachmentUrl: (id: string) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,\n articles: (q?: string, signal?: AbortSignal) => call<{ articles: ArticleSummary[] }>(`${base}/articles?${qs({ key: c.apiKey, q })}`, undefined, signal),\n article: (id: string, signal?: AbortSignal) =>\n call<Article>(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n messengerLook: (signal?: AbortSignal) => call<MessengerLook>(`${base}/messenger?${qs({ key: c.apiKey })}`, undefined, signal),\n rateArticle: (id: string, helpful: boolean) => post<{ counted: boolean }>(`/articles/${encodeURIComponent(id)}/rate`, { helpful }),\n };\n};\n\nexport type Api = ReturnType<typeof createApi>;\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(){'use strict';var
|
|
1
|
+
(function(){'use strict';var V=t=>{let e=(t!=null?t:"").trim();if(!/^https?:\/\//i.test(e))return null;let s=e.match(/^https?:\/\/(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/|youtube-nocookie\.com\/embed\/)([\w-]{6,20})/i);return s?{kind:"iframe",src:`https://www.youtube-nocookie.com/embed/${s[1]}?rel=0`}:(s=e.match(/^https?:\/\/(?:www\.|player\.)?vimeo\.com\/(?:video\/)?(\d{4,12})/i),s?{kind:"iframe",src:`https://player.vimeo.com/video/${s[1]}?dnt=1`}:(s=e.match(/^https?:\/\/(?:www\.)?loom\.com\/(?:share|embed)\/([\w-]{8,64})/i),s?{kind:"iframe",src:`https://www.loom.com/embed/${s[1]}`}:/\.(mp4|webm|mov|m4v)(\?|#|$)/i.test(e)||/\/media\/uploads\//.test(e)?{kind:"file",src:e}:null))},D=t=>{var s;let e=t.media;if(!(!e||e.kind!=="image"&&e.kind!=="video"||!/^https?:\/\//i.test((s=e.url)!=null?s:"")))return e.kind==="video"&&!V(e.url)?void 0:e};var W=(t,e)=>Math.round(t*(1-e/100)*100)/100,U=(t,e)=>{try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format(t)}catch(s){return `${t.toFixed(2)} ${e}`}},Y=t=>t.kind==="contact"?"Send to the team":t.kind==="pause"?"Pause instead":t.kind==="downgrade"?"Switch plan":"Accept offer";var B="ckf-styles",re=`
|
|
2
2
|
.ckf-root{--ckf-accent:#3f76ff;--ckf-accent-ink:#fff;--ckf-bg:#fff;--ckf-fg:#1c1d20;--ckf-muted:#5d6068;--ckf-soft:#f4f5f7;--ckf-line:#e4e5e8;--ckf-danger:#d92d20;--ckf-radius:14px;--ckf-font:inherit;--ckf-shadow:0 16px 40px -12px rgba(16,18,24,.28),0 0 0 1px rgba(16,18,24,.07)}
|
|
3
3
|
.ckf-root[data-theme=dark]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}
|
|
4
4
|
@media (prefers-color-scheme:dark){.ckf-root[data-theme=auto]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}}
|
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
.ckf-err{margin:0;color:var(--ckf-danger);font-size:13px;font-weight:500}
|
|
44
44
|
.ckf-row{display:flex;justify-content:flex-end;gap:8px;align-items:center}
|
|
45
45
|
.ckf-row .ckf-small{margin-right:auto}
|
|
46
|
+
.ckf-media{display:block;width:100%;max-height:240px;object-fit:cover;border-radius:10px;background:var(--ckf-soft)}
|
|
47
|
+
.ckf-video{position:relative;width:100%;aspect-ratio:16/9;border-radius:10px;overflow:hidden;background:#000}
|
|
48
|
+
.ckf-video iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
|
|
49
|
+
.ckf-arrow{position:absolute;width:12px;height:12px;margin:-6px;background:var(--ckf-bg);transform:rotate(45deg)}
|
|
50
|
+
[data-side=bottom]>.ckf-arrow{top:0;left:var(--ckf-ap)}[data-side=top]>.ckf-arrow{top:100%;left:var(--ckf-ap)}
|
|
51
|
+
[data-side=right]>.ckf-arrow{left:0;top:var(--ckf-ap)}[data-side=left]>.ckf-arrow{left:100%;top:var(--ckf-ap)}
|
|
52
|
+
.ckf-check .ckf-go{margin-left:auto;align-self:center;color:var(--ckf-accent);font-weight:700}
|
|
46
53
|
.ckf-ring{position:fixed;z-index:2147483000;pointer-events:none;border-radius:10px;box-shadow:0 0 0 2px var(--ckf-accent),0 0 0 6px color-mix(in srgb,var(--ckf-accent) 25%,transparent);transition:all .15s ease-out}
|
|
47
54
|
.ckf-check{display:flex;align-items:flex-start;gap:10px;width:100%;padding:8px 6px;border:0;border-radius:8px;background:none;text-align:left}
|
|
48
55
|
.ckf-check:where(button){cursor:pointer}
|
|
@@ -70,4 +77,4 @@
|
|
|
70
77
|
.ckf-banner{animation-name:ckf-fade}
|
|
71
78
|
@media (max-width:520px){.ckf-scrim{align-items:flex-end;padding:0}.ckf-scrim .ckf-card{max-width:none;border-radius:var(--ckf-radius) var(--ckf-radius) 0 0;max-height:92dvh}.ckf-float{right:12px;bottom:12px}}
|
|
72
79
|
@media (prefers-reduced-motion:reduce){.ckf-root *{animation:none!important;transition:none!important}}
|
|
73
|
-
`,t=(n,e={},...c)=>{let s=document.createElement(n);for(let[g,u]of Object.entries(e))u===void 0||u===false||(typeof u=="function"?s.addEventListener(g.slice(2).toLowerCase(),u):g==="className"?s.className=String(u):s.setAttribute(g,u===true?"":u));for(let g of c)(g||g==="")&&s.append(typeof g=="string"?document.createTextNode(g):g);return s},P=(n,e,c,s={})=>t("button",{type:"button",className:`ckf-btn${e==="secondary"?"":` ckf-${e}`}`,disabled:!!s.disabled,"data-ckf-id":s.id,onClick:()=>c()},n),$=(n,e)=>t("button",{type:"button",className:"ckf-x","aria-label":n,"data-ckf-id":"x",onClick:()=>e()},"\xD7"),U=(n,e)=>e>1?t("span",{className:"ckf-dots",role:"img","aria-label":`Step ${n} of ${e}`},...Array.from({length:e},(c,s)=>t("span",{className:"ckf-dot","data-on":s<n}))):t("span"),W=n=>{var e;return n.type==="cancel"?`c:${n.screen}:${(e=n.round)!=null?e:1}`:n.type==="step"?`s:${n.step.id}`:n.type==="checklist"?`l:${n.flow.id}`:`m:${n.message.id}`},H=n=>{var c;if(!document.getElementById(V)){let s=document.createElement("style");s.id=V,s.textContent=D,document.head.appendChild(s);}let e=t("div",{className:`ckf-root${n.className?` ${n.className}`:""}`,"data-theme":(c=n.theme)!=null?c:"auto"});return n.accent&&e.style.setProperty("--ckf-accent",n.accent),document.body.appendChild(e),e},j=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u="",b=null,C=r=>{var m,y,O,M;let o="ckf-title",l=r.busy,d=[];if(r.screen==="survey")d.push(t("h2",{className:"ckf-h",id:o},r.flow.question||"Why are you leaving?"),t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...r.flow.reasons.map(a=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===a.id),"data-ckf-id":`r:${a.id}`,onClick:()=>n.cancel.pick(a.id)},t("span",{className:"ckf-radio"}),a.label))),t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{disabled:!r.reason,id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));else if(r.screen==="followup"&&r.followUp){let a=r.followUp,x;if(a.answers.length)x=t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...a.answers.map((v,S)=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===v),"data-ckf-id":`a:${S}`,onClick:()=>n.cancel.answer(v)},t("span",{className:"ckf-radio"}),v)));else {let v=t("textarea",{className:"ckf-text",placeholder:"Your answer (optional)","aria-labelledby":o,maxlength:"500","data-ckf-id":"answer",onInput:S=>n.cancel.answer(S.target.value)});v.value=(m=r.answer)!=null?m:"",x=v;}let E=(y=r.flow.reasons.find(v=>v.id===r.reason))==null?void 0:y.label;d.push(E?t("p",{className:"ckf-small"},`You said: ${E}`):false,t("h2",{className:"ckf-h",id:o},a.question),x,t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));}else if(r.screen==="offer"&&r.offer){let a=r.offer,x=(O=r.flow.reasons.find(S=>S.id===r.reason))==null?void 0:O.label,E=false;a.kind==="discount"&&a.percent&&(E=r.price?t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},_(L(r.price.amount,a.percent),r.price.currency)),t("span",{className:"ckf-strike"},_(r.price.amount,r.price.currency)),t("span",{className:"ckf-small"},`/${(M=r.price.interval)!=null?M:"month"}${a.months?` for ${a.months} ${a.months===1?"month":"months"}`:""}`)):t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},`${a.percent}% off`),a.months?t("span",{className:"ckf-small"},`for ${a.months} ${a.months===1?"month":"months"}`):false)),a.kind==="pause"&&a.months&&(E=t("div",{className:"ckf-box"},t("span",{},`Pause for up to ${a.months} ${a.months===1?"month":"months"}. Your data stays.`))),a.kind==="downgrade"&&a.planName&&(E=t("div",{className:"ckf-box"},t("b",{},a.planName)));let v=a.kind==="contact"?t("textarea",{className:"ckf-text",placeholder:"What would make you stay?","aria-label":"Your message",maxlength:"500","data-ckf-id":"comment",onInput:S=>u=S.target.value}):null;v&&(v.value=u),d.push(x?t("p",{className:"ckf-small"},`You said: ${x}`):false,t("h2",{className:"ckf-h",id:o},a.headline),E,v!=null?v:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":A(a),"primary",()=>n.cancel.accept(u),{disabled:l,id:"go"}),P("No thanks, continue cancelling","quiet",n.cancel.decline,{disabled:l})));}else {let x=r.steps===1?null:t("textarea",{className:"ckf-text",placeholder:"Anything else we should know? (optional)","aria-label":"Anything else we should know? (optional)",maxlength:"500","data-ckf-id":"comment",onInput:E=>u=E.target.value});x&&(x.value=u),d.push(t("h2",{className:"ckf-h",id:o},r.flow.confirm.title),r.flow.confirm.body?t("p",{className:"ckf-p"},r.flow.confirm.body):false,x!=null?x:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":r.flow.confirm.button,"danger",()=>n.cancel.confirm(u),{disabled:l,id:"go"}),P("Keep my plan","secondary",n.cancel.close,{disabled:l})));}return t("div",{className:"ckf-scrim",onClick:a=>a.target===a.currentTarget&&!l&&n.cancel.close()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":o},t("div",{className:"ckf-top"},U(r.step,r.steps),$("Close",n.cancel.close)),...d,r.error?t("p",{className:"ckf-err",role:"alert"},r.error):false))},p=r=>{let o=r.step,l=t("div",{className:"ckf-card",role:"dialog","aria-modal":o.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},r.total>1?t("p",{className:"ckf-small"},`Step ${r.index+1} of ${r.total}`):t("span"),$("Close the tour",n.onboarding.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,t("div",{className:"ckf-row"},P(o.button||(r.last?"Done":"Next"),"primary",n.onboarding.next,{id:"go"})));if(o.kind==="modal")return t("div",{className:"ckf-scrim"},l);let d=o.target?i(o.target):null,m=t("div",{className:"ckf-float"},l);if(d){let y=t("div",{className:"ckf-ring","aria-hidden":"true"});return b={float:m,ring:y,target:d},w(m,y,d),t("div",{},y,m)}return m},i=r=>{var l;let o=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(l=f(r))!=null?l:o()},f=r=>{try{return document.querySelector(r)}catch(o){return null}},w=(r,o,l)=>{let d=l.getBoundingClientRect();Object.assign(o.style,{left:`${d.left-4}px`,top:`${d.top-4}px`,width:`${d.width+8}px`,height:`${d.height+8}px`});let m=Math.min(340,window.innerWidth-24),y=Math.max(12,Math.min(d.left,window.innerWidth-m-12)),O=d.bottom+12;Object.assign(r.style,{left:`${y}px`,right:"auto",width:`${m}px`}),O+200<window.innerHeight?Object.assign(r.style,{top:`${O}px`,bottom:"auto"}):Object.assign(r.style,{top:"auto",bottom:`${Math.max(12,window.innerHeight-d.top+12)}px`});},h=r=>{let o=r.items.filter(l=>l.done).length;return t("div",{className:"ckf-float"},t("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),$("Hide the checklist",n.onboarding.dismiss)),t("p",{className:"ckf-small"},`${o} of ${r.items.length} done`),t("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(o)},t("i",{style:`width:${o/r.items.length*100}%`})),t("div",{className:"ckf-opts"},...r.items.map(({step:l,done:d})=>{let m=[t("span",{className:"ckf-tick","aria-hidden":"true"},d?"\u2713":""),t("span",{},t("b",{},l.title),l.body?t("span",{className:"ckf-small"},l.body):false)];return d||l.event?t("div",{className:"ckf-check","data-done":d,role:"listitem"},...m):t("button",{type:"button",className:"ckf-check","aria-label":`Mark \u201C${l.title}\u201D done`,"data-ckf-id":`i:${l.id}`,onClick:()=>n.onboarding.check(l.id)},...m)}))))},N=r=>{let o=r.message,l=o.button?P(o.button,"primary",n.message.click,{id:"go"}):false;return o.style==="banner"?t("div",{className:"ckf-banner",role:"status"},t("span",{},t("b",{},o.title),o.body?t("span",{className:"ckf-small"},o.body):false),l,$("Dismiss",n.message.dismiss)):t("div",{className:"ckf-scrim",onClick:d=>d.target===d.currentTarget&&n.message.dismiss()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("span"),$("Dismiss",n.message.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,l?t("div",{className:"ckf-row"},l):false))},F=()=>{var d,m,y,O,M;let r=n.view(),o=r?W(r):"",l=(m=(d=document.activeElement)==null?void 0:d.closest)!=null&&m.call(d,".ckf-root")?(y=document.activeElement)==null?void 0:y.getAttribute("data-ckf-id"):null;if(o&&!s&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(g=document.activeElement),o!==s&&!o.startsWith("c:")&&(u=""),o||(u=""),b=null,c.replaceChildren(...r?[r.type==="cancel"?C(r):r.type==="step"?p(r):r.type==="checklist"?h(r):N(r)]:[]),o!==s){let a=c.querySelector("[aria-modal=true]"),x=(O=a==null?void 0:a.querySelector("[role=radio], [data-ckf-id=go], textarea"))!=null?O:null;x==null||x.focus(),!o&&g&&(g.focus(),g=null);}else l&&((M=c.querySelector(`[data-ckf-id="${CSS.escape(l)}"]`))==null||M.focus());s=o;},k=r=>{if(r.key!=="Escape")return;let o=n.view();(o==null?void 0:o.type)==="cancel"?n.cancel.close():(o==null?void 0:o.type)==="message"?n.message.dismiss():(o==null?void 0:o.type)==="step"&&o.step.kind==="modal"&&n.onboarding.dismiss();},R=()=>{b&&w(b.float,b.ring,b.target);};document.addEventListener("keydown",k),window.addEventListener("resize",R),window.addEventListener("scroll",R,{passive:true,capture:true});let I=n.subscribe(F);return F(),()=>{I(),document.removeEventListener("keydown",k),window.removeEventListener("resize",R),window.removeEventListener("scroll",R,{capture:true}),c.remove();}},K=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u=()=>{var h,N,F;let p=n.view(),i=p?`${p.paywall.id}:${p.index}`:"";if(i&&!s&&document.activeElement instanceof HTMLElement&&(g=document.activeElement),!p){c.replaceChildren(),c.removeAttribute("data-still"),s&&(g==null||g.focus()),s="";return}s&&c.setAttribute("data-still","");let f=p.page,w=f.kind==="plans"?t("div",{className:"ckf-opts",role:"radiogroup","aria-label":"Plans"},...p.plans.map(k=>t("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(p.plan===k.id),"data-ckf-id":`p:${k.id}`,onClick:()=>n.pick(k.id)},t("span",{className:"ckf-radio"}),t("span",{},k.name,k.note?t("span",{className:"ckf-small"},k.note):false),t("b",{},k.price)))):false;c.replaceChildren(t("div",{className:"ckf-scrim",onClick:k=>k.target===k.currentTarget&&n.close()},t("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},U(p.index+1,p.total),$("Close",n.close)),t("h2",{className:"ckf-h",id:"ckf-title"},f.title),f.body?t("p",{className:"ckf-p"},f.body):false,f.kind==="benefits"&&((h=f.items)!=null&&h.length)?t("div",{className:"ckf-opts"},...f.items.map(k=>t("p",{className:"ckf-li"},k))):false,f.kind==="proof"&&((N=f.quotes)!=null&&N.length)?t("div",{className:"ckf-opts"},...f.quotes.map(k=>t("blockquote",{className:"ckf-quote"},k.text,k.by?t("cite",{},k.by):false))):false,w,p.error?t("p",{className:"ckf-err",role:"alert"},p.error):false,t("div",{className:"ckf-btns"},P(p.busy?"One moment\u2026":f.button||"Continue","primary",n.next,{disabled:p.busy||f.kind==="plans"&&!p.plan,id:"go"}),p.index?P("Back","quiet",n.back,{disabled:p.busy}):false)))),i!==s&&((F=c.querySelector("[data-ckf-id=go]"))==null||F.focus()),s=i;},b=p=>{n.view()&&p.key==="Escape"&&n.close();};document.addEventListener("keydown",b);let C=n.subscribe(u);return u(),()=>{C(),document.removeEventListener("keydown",b),c.remove();}};var Y=n=>{let e=2166136261;for(let c=0;c<n.length;c++)e^=n.charCodeAt(c),e=Math.imul(e,16777619);return (e>>>0)/4294967296};var q=(n,e)=>{var u,b,C,p;let c=(b=(u=n.variants[0])==null?void 0:u.id)!=null?b:"control";if(n.winner)return n.winner;if(n.status!=="running")return c;let s=Y(`${n.key}:${e}`),g=0;for(let i of n.variants)if(g+=i.weight,s<g)return i.id;return (p=(C=n.variants[n.variants.length-1])==null?void 0:C.id)!=null?p:c};var T="$paywall",B=(n,e,c)=>{var b,C;let s=e.experiment?n==null?void 0:n.experiments.find(p=>p.key===e.experiment):void 0,g=s?q(s,c):(b=e.variants[0])==null?void 0:b.id,u=(C=e.variants.find(p=>p.id===g))!=null?C:e.variants[0];return u?{variant:u.id,pages:u.pages,test:(s==null?void 0:s.status)==="running"&&!s.winner?s.key:void 0}:null},z=n=>{let e=null,c=new Set,s=()=>{for(let i of [...c])try{i();}catch(f){}},g=()=>e?{paywall:e.w.id,placement:e.w.placement,variant:e.variant}:{},u=()=>{e&&!e.seen.has(e.index)&&(e.seen.add(e.index),n.track(T,{...g(),page:e.pages[e.index].id,index:e.index,action:"page"})),s();},b=i=>{var w,h;let f=e;f&&(e=null,i.outcome==="closed"&&((h=(w=f.options).onClose)==null||h.call(w,i)),f.resolve(i),s());},C=i=>{var f;return {outcome:i,index:(f=e==null?void 0:e.index)!=null?f:0,...e?{paywall:e.w.id,variant:e.variant}:{},...i!=="closed"&&e?{plan:e.plans.find(w=>w.id===e.plan)}:{}}},p=()=>{let i=e,f=i==null?void 0:i.plans.find(w=>w.id===i.plan);if(!(!i||!f||i.busy)){if(!i.options.onPurchase)return b(C("chosen"));i.busy=true,i.error=void 0,s(),Promise.resolve().then(()=>{var w,h;return (h=(w=i.options).onPurchase)==null?void 0:h.call(w,f)}).then(w=>{if(i.busy=false,e===i){if(w===false)return s();n.track(T,{...g(),page:i.pages[i.index].id,index:i.index,plan:f.id,action:"purchased"}),b(C("purchased"));}},()=>{i.busy=false,i.error="The purchase did not go through. Please try again.",s();});}};return {open:(i,f={})=>{var o,l,d;if(e)return e.promise;let w=n.config(),h=(o=w==null?void 0:w.paywalls)==null?void 0:o.find(m=>m.placement===i),N=h?B(w,h,n.distinctId()):null;if(!h||!(N!=null&&N.pages.length))return Promise.resolve({outcome:"closed",index:0});let F=new Map(((l=f.plans)!=null?l:[]).map(m=>[m.id,m])),k=h.plans.map(m=>{var y;return (y=F.get(m.id))!=null?y:m}),R=()=>{},I=new Promise(m=>R=m),r=k.some(m=>m.id===h.highlight)?h.highlight:(d=k[0])==null?void 0:d.id;return e={w:h,variant:N.variant,pages:N.pages,plans:k,index:0,plan:r,busy:false,seen:new Set,options:f,resolve:R,promise:I},N.test&&n.track("$exposure",{experiment:N.test,variant:N.variant}),u(),I},view:()=>e?{paywall:e.w,variant:e.variant,page:e.pages[e.index],index:e.index,total:e.pages.length,last:e.index===e.pages.length-1,plans:e.plans,plan:e.plan,busy:e.busy,error:e.error}:null,subscribe:i=>(c.add(i),()=>c.delete(i)),next:()=>{if(!(!e||e.busy)){if(e.pages[e.index].kind==="plans"||e.index===e.pages.length-1)return p();e.index++,e.error=void 0,u();}},back:()=>{!e||e.busy||!e.index||(e.index--,e.error=void 0,s());},pick:i=>{!e||e.busy||!e.plans.some(f=>f.id===i)||(e.plan=i,e.error=void 0,s());},close:()=>{!e||e.busy||(n.track(T,{...g(),index:e.index,action:"closed"}),b(C("closed")));}}};var J=(n,e)=>{let c=z(n);return K(c,e),c};window.__mrrocketFlows={mountFlows:j,paywallHost:J};})();
|
|
80
|
+
`,n=(t,e={},...s)=>{let a=document.createElement(t);for(let[m,p]of Object.entries(e))p===void 0||p===false||(typeof p=="function"?a.addEventListener(m.slice(2).toLowerCase(),p):m==="className"?a.className=String(p):a.setAttribute(m,p===true?"":p));for(let m of s)(m||m==="")&&a.append(typeof m=="string"?document.createTextNode(m):m);return a},O=(t,e,s,a={})=>n("button",{type:"button",className:`ckf-btn${e==="secondary"?"":` ckf-${e}`}`,disabled:!!a.disabled,"data-ckf-id":a.id,onClick:()=>s()},t),T=(t,e)=>n("button",{type:"button",className:"ckf-x","aria-label":t,"data-ckf-id":"x",onClick:()=>e()},"\xD7"),J=(t,e)=>e>1?n("span",{className:"ckf-dots",role:"img","aria-label":`Step ${t} of ${e}`},...Array.from({length:e},(s,a)=>n("span",{className:"ckf-dot","data-on":a<t}))):n("span"),oe=t=>{var e;return t.type==="cancel"?`c:${t.screen}:${(e=t.round)!=null?e:1}`:t.type==="step"?`s:${t.step.id}`:t.type==="checklist"?`l:${t.flow.id}`:`m:${t.message.id}`},X=t=>{var s;if(!document.getElementById(B)){let a=document.createElement("style");a.id=B,a.textContent=re,document.head.appendChild(a);}let e=n("div",{className:`ckf-root${t.className?` ${t.className}`:""}`,"data-theme":(s=t.theme)!=null?s:"auto"});return t.accent&&e.style.setProperty("--ckf-accent",t.accent),document.body.appendChild(e),e},G=(t,e={})=>{if(typeof document=="undefined")return ()=>{};let s=X(e),a="",m=null,p="",b=null,C=r=>{var f,v,R,P;let o="ckf-title",d=r.busy,i=[];if(r.screen==="survey")i.push(n("h2",{className:"ckf-h",id:o},r.flow.question||"Why are you leaving?"),n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...r.flow.reasons.map(c=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===c.id),"data-ckf-id":`r:${c.id}`,onClick:()=>t.cancel.pick(c.id)},n("span",{className:"ckf-radio"}),c.label))),n("div",{className:"ckf-btns"},O("Continue","primary",t.cancel.next,{disabled:!r.reason,id:"go"}),O("Never mind, keep my plan","quiet",t.cancel.close)));else if(r.screen==="followup"&&r.followUp){let c=r.followUp,k;if(c.answers.length)k=n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...c.answers.map((y,$)=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===y),"data-ckf-id":`a:${$}`,onClick:()=>t.cancel.answer(y)},n("span",{className:"ckf-radio"}),y)));else {let y=n("textarea",{className:"ckf-text",placeholder:"Your answer (optional)","aria-labelledby":o,maxlength:"500","data-ckf-id":"answer",onInput:$=>t.cancel.answer($.target.value)});y.value=(f=r.answer)!=null?f:"",k=y;}let E=(v=r.flow.reasons.find(y=>y.id===r.reason))==null?void 0:v.label;i.push(E?n("p",{className:"ckf-small"},`You said: ${E}`):false,n("h2",{className:"ckf-h",id:o},c.question),k,n("div",{className:"ckf-btns"},O("Continue","primary",t.cancel.next,{id:"go"}),O("Never mind, keep my plan","quiet",t.cancel.close)));}else if(r.screen==="offer"&&r.offer){let c=r.offer,k=(R=r.flow.reasons.find($=>$.id===r.reason))==null?void 0:R.label,E=false;c.kind==="discount"&&c.percent&&(E=r.price?n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},U(W(r.price.amount,c.percent),r.price.currency)),n("span",{className:"ckf-strike"},U(r.price.amount,r.price.currency)),n("span",{className:"ckf-small"},`/${(P=r.price.interval)!=null?P:"month"}${c.months?` for ${c.months} ${c.months===1?"month":"months"}`:""}`)):n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},`${c.percent}% off`),c.months?n("span",{className:"ckf-small"},`for ${c.months} ${c.months===1?"month":"months"}`):false)),c.kind==="pause"&&c.months&&(E=n("div",{className:"ckf-box"},n("span",{},`Pause for up to ${c.months} ${c.months===1?"month":"months"}. Your data stays.`))),c.kind==="downgrade"&&c.planName&&(E=n("div",{className:"ckf-box"},n("b",{},c.planName)));let y=c.kind==="contact"?n("textarea",{className:"ckf-text",placeholder:"What would make you stay?","aria-label":"Your message",maxlength:"500","data-ckf-id":"comment",onInput:$=>p=$.target.value}):null;y&&(y.value=p),i.push(k?n("p",{className:"ckf-small"},`You said: ${k}`):false,n("h2",{className:"ckf-h",id:o},c.headline),E,y!=null?y:false,n("div",{className:"ckf-btns"},O(d?"One moment\u2026":Y(c),"primary",()=>t.cancel.accept(p),{disabled:d,id:"go"}),O("No thanks, continue cancelling","quiet",t.cancel.decline,{disabled:d})));}else {let k=r.steps===1?null:n("textarea",{className:"ckf-text",placeholder:"Anything else we should know? (optional)","aria-label":"Anything else we should know? (optional)",maxlength:"500","data-ckf-id":"comment",onInput:E=>p=E.target.value});k&&(k.value=p),i.push(n("h2",{className:"ckf-h",id:o},r.flow.confirm.title),r.flow.confirm.body?n("p",{className:"ckf-p"},r.flow.confirm.body):false,k!=null?k:false,n("div",{className:"ckf-btns"},O(d?"One moment\u2026":r.flow.confirm.button,"danger",()=>t.cancel.confirm(p),{disabled:d,id:"go"}),O("Keep my plan","secondary",t.cancel.close,{disabled:d})));}return n("div",{className:"ckf-scrim",onClick:c=>c.target===c.currentTarget&&!d&&t.cancel.close()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":o},n("div",{className:"ckf-top"},J(r.step,r.steps),T("Close",t.cancel.close)),...i,r.error?n("p",{className:"ckf-err",role:"alert"},r.error):false))},g=r=>{var i;let o=D(r);if(!o)return false;if(o.kind==="image")return n("img",{className:"ckf-media",src:o.url,alt:(i=o.alt)!=null?i:"",onLoad:()=>F()});let d=V(o.url);return d.kind==="iframe"?n("div",{className:"ckf-video"},n("iframe",{src:d.src,title:o.alt||"Video",allow:"fullscreen; picture-in-picture",allowfullscreen:true,loading:"lazy",referrerpolicy:"strict-origin-when-cross-origin"})):n("video",{className:"ckf-media",src:d.src,controls:true,playsinline:true,preload:"metadata","aria-label":o.alt||void 0,onLoadedmetadata:()=>F()})},l=r=>{let o=r.step,d=n("div",{className:"ckf-card",role:"dialog","aria-modal":o.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},r.total>1?n("p",{className:"ckf-small"},`Step ${r.index+1} of ${r.total}`):n("span"),T("Close the tour",t.onboarding.dismiss)),g(o),n("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?n("p",{className:"ckf-p"},o.body):false,n("div",{className:"ckf-row"},O(o.button||(r.last?"Done":"Next"),"primary",t.onboarding.next,{id:"go"})));if(o.kind==="modal")return n("div",{className:"ckf-scrim"},d);let i=o.target?u(o.target):null,f=n("div",{className:"ckf-float"},d);if(i){let v=n("div",{className:"ckf-ring","aria-hidden":"true"});return f.append(n("span",{className:"ckf-arrow","aria-hidden":"true"})),b={float:f,ring:v,target:i,side:o.placement},x(f,v,i,o.placement),n("div",{},v,f)}return f},u=r=>{var d;let o=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(d=h(r))!=null?d:o()},h=r=>{try{return document.querySelector(r)}catch(o){return null}},x=(r,o,d,i="auto")=>{var z;let f=d.getBoundingClientRect();Object.assign(o.style,{left:`${f.left-4}px`,top:`${f.top-4}px`,width:`${f.width+8}px`,height:`${f.height+8}px`});let v=window.innerWidth,R=window.innerHeight,P=Math.min(340,v-24),c=r.offsetHeight||200,k=14,E={bottom:R-f.bottom-k-12>=c,top:f.top-k-12>=c,right:v-f.right-k-12>=P,left:f.left-k-12>=P},y={top:"bottom",bottom:"top",left:"right",right:"left"},_=(z=(i==="auto"||!y[i]?["bottom","top"]:[i,y[i],"bottom","top","right","left"]).find(A=>E[A]))!=null?z:i!=="auto"&&y[i]?i:f.top>R-f.bottom?"top":"bottom",j=(A,ne)=>Math.max(12,Math.min(A,ne-12)),I=_==="top"||_==="bottom",K=I?j(i==="auto"?f.left:f.left+f.width/2-P/2,v-P):_==="right"?f.right+k:f.left-k-P,q=I?_==="bottom"?f.bottom+k:f.top-k-c:j(f.top+f.height/2-c/2,R-c);Object.assign(r.style,{left:`${K}px`,top:`${q}px`,right:"auto",bottom:"auto",width:`${P}px`}),r.setAttribute("data-side",_);let te=I?f.left+f.width/2-K:f.top+f.height/2-q;r.style.setProperty("--ckf-ap",`${Math.max(18,Math.min(te,(I?P:c)-18))}px`);},N=r=>{let o=r.items.filter(d=>d.done).length;return n("div",{className:"ckf-float"},n("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),T("Hide the checklist",t.onboarding.dismiss)),n("p",{className:"ckf-small"},`${o} of ${r.items.length} done`),n("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(o)},n("i",{style:`width:${o/r.items.length*100}%`})),n("div",{className:"ckf-opts"},...r.items.map(({step:d,done:i,tour:f})=>{let v=[n("span",{className:"ckf-tick","aria-hidden":"true"},i?"\u2713":""),n("span",{},n("b",{},d.title),d.body?n("span",{className:"ckf-small"},d.body):false)];return !i&&f?n("button",{type:"button",className:"ckf-check","aria-label":`Start \u201C${d.title}\u201D`,"data-ckf-id":`t:${d.id}`,onClick:()=>t.onboarding.startTour(d.id)},...v,n("span",{className:"ckf-go","aria-hidden":"true"},"\u203A")):i||d.event?n("div",{className:"ckf-check","data-done":i,role:"listitem"},...v):n("button",{type:"button",className:"ckf-check","aria-label":`Mark \u201C${d.title}\u201D done`,"data-ckf-id":`i:${d.id}`,onClick:()=>t.onboarding.check(d.id)},...v)}))))},S=r=>{let o=r.message,d=o.button?O(o.button,"primary",t.message.click,{id:"go"}):false;return o.style==="banner"?n("div",{className:"ckf-banner",role:"status"},n("span",{},n("b",{},o.title),o.body?n("span",{className:"ckf-small"},o.body):false),d,T("Dismiss",t.message.dismiss)):n("div",{className:"ckf-scrim",onClick:i=>i.target===i.currentTarget&&t.message.dismiss()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("span"),T("Dismiss",t.message.dismiss)),n("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?n("p",{className:"ckf-p"},o.body):false,d?n("div",{className:"ckf-row"},d):false))},w=()=>{var i,f,v,R,P;let r=t.view(),o=r?oe(r):"",d=(f=(i=document.activeElement)==null?void 0:i.closest)!=null&&f.call(i,".ckf-root")?(v=document.activeElement)==null?void 0:v.getAttribute("data-ckf-id"):null;if(o&&!a&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(m=document.activeElement),o!==a&&!o.startsWith("c:")&&(p=""),o||(p=""),b=null,s.replaceChildren(...r?[r.type==="cancel"?C(r):r.type==="step"?l(r):r.type==="checklist"?N(r):S(r)]:[]),F(),o!==a){let c=s.querySelector("[aria-modal=true]"),k=(R=c==null?void 0:c.querySelector("[role=radio], [data-ckf-id=go], textarea"))!=null?R:null;k==null||k.focus(),!o&&m&&(m.focus(),m=null);}else d&&((P=s.querySelector(`[data-ckf-id="${CSS.escape(d)}"]`))==null||P.focus());a=o;},M=r=>{if(r.key!=="Escape")return;let o=t.view();(o==null?void 0:o.type)==="cancel"?t.cancel.close():(o==null?void 0:o.type)==="message"?t.message.dismiss():(o==null?void 0:o.type)==="step"&&o.step.kind==="modal"&&t.onboarding.dismiss();};function F(){b&&x(b.float,b.ring,b.target,b.side);}document.addEventListener("keydown",M),window.addEventListener("resize",F),window.addEventListener("scroll",F,{passive:true,capture:true});let L=t.subscribe(w);return w(),()=>{L(),document.removeEventListener("keydown",M),window.removeEventListener("resize",F),window.removeEventListener("scroll",F,{capture:true}),s.remove();}},Q=(t,e={})=>{if(typeof document=="undefined")return ()=>{};let s=X(e),a="",m=null,p=()=>{var x,N,S;let g=t.view(),l=g?`${g.paywall.id}:${g.index}`:"";if(l&&!a&&document.activeElement instanceof HTMLElement&&(m=document.activeElement),!g){s.replaceChildren(),s.removeAttribute("data-still"),a&&(m==null||m.focus()),a="";return}a&&s.setAttribute("data-still","");let u=g.page,h=u.kind==="plans"?n("div",{className:"ckf-opts",role:"radiogroup","aria-label":"Plans"},...g.plans.map(w=>n("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(g.plan===w.id),"data-ckf-id":`p:${w.id}`,onClick:()=>t.pick(w.id)},n("span",{className:"ckf-radio"}),n("span",{},w.name,w.note?n("span",{className:"ckf-small"},w.note):false),n("b",{},w.price)))):false;s.replaceChildren(n("div",{className:"ckf-scrim",onClick:w=>w.target===w.currentTarget&&t.close()},n("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},J(g.index+1,g.total),T("Close",t.close)),n("h2",{className:"ckf-h",id:"ckf-title"},u.title),u.body?n("p",{className:"ckf-p"},u.body):false,u.kind==="benefits"&&((x=u.items)!=null&&x.length)?n("div",{className:"ckf-opts"},...u.items.map(w=>n("p",{className:"ckf-li"},w))):false,u.kind==="proof"&&((N=u.quotes)!=null&&N.length)?n("div",{className:"ckf-opts"},...u.quotes.map(w=>n("blockquote",{className:"ckf-quote"},w.text,w.by?n("cite",{},w.by):false))):false,h,g.error?n("p",{className:"ckf-err",role:"alert"},g.error):false,n("div",{className:"ckf-btns"},O(g.busy?"One moment\u2026":u.button||"Continue","primary",t.next,{disabled:g.busy||u.kind==="plans"&&!g.plan,id:"go"}),g.index?O("Back","quiet",t.back,{disabled:g.busy}):false)))),l!==a&&((S=s.querySelector("[data-ckf-id=go]"))==null||S.focus()),a=l;},b=g=>{t.view()&&g.key==="Escape"&&t.close();};document.addEventListener("keydown",b);let C=t.subscribe(p);return p(),()=>{C(),document.removeEventListener("keydown",b),s.remove();}};var se=t=>{let e=2166136261;for(let s=0;s<t.length;s++)e^=t.charCodeAt(s),e=Math.imul(e,16777619);return (e>>>0)/4294967296};var Z=(t,e)=>{var p,b,C,g;let s=(b=(p=t.variants[0])==null?void 0:p.id)!=null?b:"control";if(t.winner)return t.winner;if(t.status!=="running")return s;let a=se(`${t.key}:${e}`),m=0;for(let l of t.variants)if(m+=l.weight,a<m)return l.id;return (g=(C=t.variants[t.variants.length-1])==null?void 0:C.id)!=null?g:s};var H="$paywall",ie=(t,e,s)=>{var b,C;let a=e.experiment?t==null?void 0:t.experiments.find(g=>g.key===e.experiment):void 0,m=a?Z(a,s):(b=e.variants[0])==null?void 0:b.id,p=(C=e.variants.find(g=>g.id===m))!=null?C:e.variants[0];return p?{variant:p.id,pages:p.pages,test:(a==null?void 0:a.status)==="running"&&!a.winner?a.key:void 0}:null},ee=t=>{let e=null,s=new Set,a=()=>{for(let l of [...s])try{l();}catch(u){}},m=()=>e?{paywall:e.w.id,placement:e.w.placement,variant:e.variant}:{},p=()=>{e&&!e.seen.has(e.index)&&(e.seen.add(e.index),t.track(H,{...m(),page:e.pages[e.index].id,index:e.index,action:"page"})),a();},b=l=>{var h,x;let u=e;u&&(e=null,l.outcome==="closed"&&((x=(h=u.options).onClose)==null||x.call(h,l)),u.resolve(l),a());},C=l=>{var u;return {outcome:l,index:(u=e==null?void 0:e.index)!=null?u:0,...e?{paywall:e.w.id,variant:e.variant}:{},...l!=="closed"&&e?{plan:e.plans.find(h=>h.id===e.plan)}:{}}},g=()=>{let l=e,u=l==null?void 0:l.plans.find(h=>h.id===l.plan);if(!(!l||!u||l.busy)){if(!l.options.onPurchase)return b(C("chosen"));l.busy=true,l.error=void 0,a(),Promise.resolve().then(()=>{var h,x;return (x=(h=l.options).onPurchase)==null?void 0:x.call(h,u)}).then(h=>{if(l.busy=false,e===l){if(h===false)return a();t.track(H,{...m(),page:l.pages[l.index].id,index:l.index,plan:u.id,action:"purchased"}),b(C("purchased"));}},()=>{l.busy=false,l.error="The purchase did not go through. Please try again.",a();});}};return {open:(l,u={})=>{var r,o,d;if(e)return e.promise;let h=t.config(),x=(r=h==null?void 0:h.paywalls)==null?void 0:r.find(i=>i.placement===l),N=x?ie(h,x,t.distinctId()):null;if(!x||!(N!=null&&N.pages.length))return Promise.resolve({outcome:"closed",index:0});let S=new Map(((o=u.plans)!=null?o:[]).map(i=>[i.id,i])),w=x.plans.map(i=>{var f;return (f=S.get(i.id))!=null?f:i}),M=()=>{},F=new Promise(i=>M=i),L=w.some(i=>i.id===x.highlight)?x.highlight:(d=w[0])==null?void 0:d.id;return e={w:x,variant:N.variant,pages:N.pages,plans:w,index:0,plan:L,busy:false,seen:new Set,options:u,resolve:M,promise:F},N.test&&t.track("$exposure",{experiment:N.test,variant:N.variant}),p(),F},view:()=>e?{paywall:e.w,variant:e.variant,page:e.pages[e.index],index:e.index,total:e.pages.length,last:e.index===e.pages.length-1,plans:e.plans,plan:e.plan,busy:e.busy,error:e.error}:null,subscribe:l=>(s.add(l),()=>s.delete(l)),next:()=>{if(!(!e||e.busy)){if(e.pages[e.index].kind==="plans"||e.index===e.pages.length-1)return g();e.index++,e.error=void 0,p();}},back:()=>{!e||e.busy||!e.index||(e.index--,e.error=void 0,a());},pick:l=>{!e||e.busy||!e.plans.some(u=>u.id===l)||(e.plan=l,e.error=void 0,a());},close:()=>{!e||e.busy||(t.track(H,{...m(),index:e.index,action:"closed"}),b(C("closed")));}}};var ae=(t,e)=>{let s=ee(t);return Q(s,e),s};window.__mrrocketFlows={mountFlows:G,paywallHost:ae};})();
|