@mrrocket/sdk 0.1.0 → 0.3.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/dist/replay.js ADDED
@@ -0,0 +1,200 @@
1
+ "use client";
2
+
3
+ // src/replay.ts
4
+ import {
5
+ BUFFER_MS,
6
+ CHECKOUT_MS,
7
+ FLUSH_BYTES,
8
+ MAX_RECORDING_MS,
9
+ MAX_REQUEST_BYTES,
10
+ RingBuffer,
11
+ chunkDue,
12
+ inSample,
13
+ recordOptions,
14
+ toBase64,
15
+ triggerOfEvent
16
+ } from "./replay-core.js";
17
+ var clip = (s, n) => String(s ?? "").slice(0, n);
18
+ var pathNow = () => window.location.pathname.slice(0, 300) || "/";
19
+ var gzip = async (text) => {
20
+ const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
21
+ return new Uint8Array(await new Response(stream).arrayBuffer());
22
+ };
23
+ var startReplay = async (o) => {
24
+ if (typeof window === "undefined") return null;
25
+ const { record } = await import("@rrweb/record");
26
+ const url = `${o.host}/api/sdk/replay`;
27
+ let mode = inSample(o.sessionId, o.sampleRate) ? "upload" : "buffer";
28
+ let trigger = mode === "upload" ? "sample" : null;
29
+ let distinctId = o.distinctId;
30
+ let identified = o.identified === true;
31
+ let start = null;
32
+ let seq = 0;
33
+ let stopped = false;
34
+ const begun = Date.now();
35
+ const ring = new RingBuffer();
36
+ let pending = [];
37
+ let pendingBytes = 0;
38
+ let lastFlush = Date.now();
39
+ let marks = [{ t: Date.now(), kind: "pageview", path: pathNow() }];
40
+ let queue = Promise.resolve();
41
+ const end = (discard) => {
42
+ if (stopped) return;
43
+ if (!discard && mode === "upload") flush(false);
44
+ stopped = true;
45
+ stopRecording?.();
46
+ window.clearInterval(tick);
47
+ window.clearInterval(checkout);
48
+ window.removeEventListener("error", onError);
49
+ window.removeEventListener("unhandledrejection", onRejection);
50
+ window.removeEventListener("pagehide", onHide);
51
+ document.removeEventListener("visibilitychange", onVisibility);
52
+ if (console.error === patched) console.error = original;
53
+ pending = [];
54
+ ring.clear();
55
+ marks = [];
56
+ };
57
+ const send = async (payload, events, sync) => {
58
+ let body;
59
+ if (!sync && typeof CompressionStream !== "undefined") body = JSON.stringify({ ...payload, enc: "gzip", data: toBase64(await gzip(events)) });
60
+ else body = JSON.stringify({ ...payload, enc: "json", data: events });
61
+ if (body.length > MAX_REQUEST_BYTES) {
62
+ o.warn("A recording chunk was over 1 MB (a very large page). The recording stopped.");
63
+ end(true);
64
+ return;
65
+ }
66
+ if (sync && "sendBeacon" in navigator && navigator.sendBeacon(url, body)) return;
67
+ try {
68
+ const r = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: sync || body.length < 6e4 });
69
+ if (r.status === 403 || r.status === 409 || r.status === 413) end(true);
70
+ } catch {
71
+ o.warn(`Could not reach ${o.host || "this origin"} to upload a recording.`);
72
+ }
73
+ };
74
+ function flush(sync) {
75
+ if (!pending.length) return;
76
+ const events = pending;
77
+ pending = [];
78
+ pendingBytes = 0;
79
+ lastFlush = Date.now();
80
+ start ??= events[0].timestamp;
81
+ const chunkMarks = marks;
82
+ marks = [];
83
+ const payload = {
84
+ key: o.apiKey,
85
+ sessionId: o.sessionId,
86
+ distinctId,
87
+ identified,
88
+ start,
89
+ seq: seq++,
90
+ consent: { replay: true },
91
+ trigger,
92
+ final: sync,
93
+ meta: {
94
+ first: events[0].timestamp,
95
+ last: events[events.length - 1].timestamp,
96
+ vw: window.innerWidth,
97
+ vh: window.innerHeight,
98
+ marks: chunkMarks
99
+ }
100
+ };
101
+ const json = `[${events.map((e) => e.json).join(",")}]`;
102
+ if (sync) void send(payload, json, true);
103
+ else queue = queue.then(() => send(payload, json, false));
104
+ }
105
+ const fire = (kind) => {
106
+ if (stopped || mode === "upload") return;
107
+ mode = "upload";
108
+ trigger = kind;
109
+ window.clearInterval(checkout);
110
+ pending = ring.drain();
111
+ pendingBytes = pending.reduce((s, e) => s + e.json.length, 0);
112
+ flush(false);
113
+ };
114
+ const emit = (e) => {
115
+ if (stopped) return;
116
+ if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) {
117
+ end(false);
118
+ return;
119
+ }
120
+ const json = JSON.stringify(e);
121
+ const item = { type: e.type, timestamp: e.timestamp, json };
122
+ if (mode === "buffer") {
123
+ ring.push(item, json.length);
124
+ const from = ring.first ?? e.timestamp - BUFFER_MS;
125
+ marks = marks.filter((m) => m.t >= from);
126
+ return;
127
+ }
128
+ pending.push(item);
129
+ pendingBytes += json.length;
130
+ if (pendingBytes >= FLUSH_BYTES) flush(false);
131
+ };
132
+ const original = console.error;
133
+ const patched = (...args) => {
134
+ try {
135
+ record.addCustomEvent("$console", { level: "error", text: clip(args.map((a) => a instanceof Error ? a.message : typeof a === "string" ? a : "[object]").join(" "), 300) });
136
+ } catch {
137
+ }
138
+ original.apply(console, args);
139
+ };
140
+ const onError = (e) => {
141
+ const message = clip(e.message || "Script error", 200);
142
+ marks.push({ t: Date.now(), kind: "error", path: pathNow(), label: message });
143
+ try {
144
+ record.addCustomEvent("$error", { message });
145
+ } catch {
146
+ }
147
+ fire("error");
148
+ };
149
+ const onRejection = (e) => {
150
+ const r = e.reason;
151
+ const message = clip(typeof r?.message === "string" ? r.message : "Unhandled promise rejection", 200);
152
+ marks.push({ t: Date.now(), kind: "error", path: pathNow(), label: message });
153
+ try {
154
+ record.addCustomEvent("$error", { message });
155
+ } catch {
156
+ }
157
+ fire("error");
158
+ };
159
+ const onHide = () => mode === "upload" && flush(true);
160
+ const onVisibility = () => document.visibilityState === "hidden" && mode === "upload" && flush(false);
161
+ const stopRecording = record(recordOptions(emit));
162
+ if (!stopRecording) return null;
163
+ console.error = patched;
164
+ window.addEventListener("error", onError);
165
+ window.addEventListener("unhandledrejection", onRejection);
166
+ window.addEventListener("pagehide", onHide);
167
+ document.addEventListener("visibilitychange", onVisibility);
168
+ const tick = window.setInterval(() => {
169
+ if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) end(false);
170
+ else if (mode === "upload" && chunkDue(pendingBytes, Date.now() - lastFlush)) flush(false);
171
+ }, 1e3);
172
+ const checkout = mode === "buffer" ? window.setInterval(() => record.takeFullSnapshot(true), CHECKOUT_MS) : 0;
173
+ return {
174
+ event: (name) => {
175
+ const t = triggerOfEvent(name);
176
+ if (t) fire(t);
177
+ },
178
+ click: (c, rage) => {
179
+ if (stopped) return;
180
+ const t = Date.now();
181
+ const path = pathNow();
182
+ const label = c.label || c.selector.split(" > ").pop() || "";
183
+ marks.push({ t, kind: "click", path, label, x: c.x, y: c.y, vw: c.vw, dead: !c.interactive });
184
+ if (rage) {
185
+ marks.push({ t, kind: "rage", path, label, x: c.x, y: c.y, vw: c.vw });
186
+ fire("rage");
187
+ }
188
+ },
189
+ page: (path) => !stopped && marks.push({ t: Date.now(), kind: "pageview", path: path.slice(0, 300) || "/" }),
190
+ identify: (id) => {
191
+ distinctId = id;
192
+ identified = true;
193
+ },
194
+ stop: (discard = false) => end(discard)
195
+ };
196
+ };
197
+ export {
198
+ startReplay
199
+ };
200
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/replay.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The session recorder. The provider imports this file (and rrweb's recorder with it) only when a visit records: the\n * product has recordings on and the visitor gave replay consent. Pages without recording never load it.\n *\n * A visit in the product's sample uploads from its start. Any other visit keeps the last minute in memory and uploads\n * it only when something happens worth watching: a JS error, a rage click, or a `purchase…` / `checkout…` event; from\n * then on it uploads like a sampled one. Chunks go every 5 seconds or 64 KB, gzip'd where the browser can, to\n * `POST {host}/api/sdk/replay`; the last one leaves with sendBeacon when the page hides. A recording ends after 30\n * minutes. Withdrawing consent stops it and drops what was not sent.\n */\nimport type { ClickInfo } from \"./capture\";\nimport {\n BUFFER_MS,\n CHECKOUT_MS,\n FLUSH_BYTES,\n MAX_RECORDING_MS,\n MAX_REQUEST_BYTES,\n type ReplayEvent,\n type ReplayTrigger,\n RingBuffer,\n chunkDue,\n inSample,\n recordOptions,\n toBase64,\n triggerOfEvent,\n} from \"./replay-core\";\n\nexport interface ReplayOptions {\n host: string;\n apiKey: string;\n /** The SDK's session id; every chunk carries it, so the recording joins the session's events. */\n sessionId: string;\n /** The identify() id or the cookie id; missing cookieless until identify(). */\n distinctId?: string;\n /** Whether `distinctId` comes from identify() (a person) rather than the visitor cookie. */\n identified?: boolean;\n sampleRate: number;\n warn: (message: string) => void;\n}\n\nexport interface ReplayHandle {\n /** A tracked event; `purchase…` and `checkout…` upload the buffered minute. */\n event(name: string): void;\n click(c: ClickInfo, rage: boolean): void;\n /** A client-side navigation. */\n page(path: string): void;\n identify(id: string): void;\n /** Stops recording. `discard` (consent withdrawn) drops everything not sent yet. */\n stop(discard?: boolean): void;\n}\n\n/** One line of the masked event list the server keeps next to the recording (pages, clicks, rage clicks, errors). */\ninterface Mark {\n t: number;\n kind: \"pageview\" | \"click\" | \"rage\" | \"error\";\n path: string;\n label?: string;\n x?: number;\n y?: number;\n vw?: number;\n dead?: boolean;\n}\n\ninterface Held extends ReplayEvent {\n json: string;\n}\n\nconst clip = (s: unknown, n: number) => String(s ?? \"\").slice(0, n);\nconst pathNow = () => window.location.pathname.slice(0, 300) || \"/\";\n\nconst gzip = async (text: string) => {\n const stream = new Blob([text]).stream().pipeThrough(new CompressionStream(\"gzip\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n};\n\nexport const startReplay = async (o: ReplayOptions): Promise<ReplayHandle | null> => {\n if (typeof window === \"undefined\") return null;\n const { record } = await import(\"@rrweb/record\");\n\n const url = `${o.host}/api/sdk/replay`;\n let mode: \"upload\" | \"buffer\" = inSample(o.sessionId, o.sampleRate) ? \"upload\" : \"buffer\";\n let trigger: ReplayTrigger | null = mode === \"upload\" ? \"sample\" : null;\n let distinctId = o.distinctId;\n let identified = o.identified === true;\n let start: number | null = null;\n let seq = 0;\n let stopped = false;\n const begun = Date.now();\n const ring = new RingBuffer<Held>();\n let pending: Held[] = [];\n let pendingBytes = 0;\n let lastFlush = Date.now();\n let marks: Mark[] = [{ t: Date.now(), kind: \"pageview\", path: pathNow() }];\n let queue: Promise<void> = Promise.resolve();\n\n const end = (discard: boolean) => {\n if (stopped) return;\n if (!discard && mode === \"upload\") flush(false);\n stopped = true;\n stopRecording?.();\n window.clearInterval(tick);\n window.clearInterval(checkout);\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n window.removeEventListener(\"pagehide\", onHide);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n if (console.error === patched) console.error = original;\n pending = [];\n ring.clear();\n marks = [];\n };\n\n const send = async (payload: Record<string, unknown>, events: string, sync: boolean) => {\n let body: string;\n if (!sync && typeof CompressionStream !== \"undefined\") body = JSON.stringify({ ...payload, enc: \"gzip\", data: toBase64(await gzip(events)) });\n else body = JSON.stringify({ ...payload, enc: \"json\", data: events });\n if (body.length > MAX_REQUEST_BYTES) {\n o.warn(\"A recording chunk was over 1 MB (a very large page). The recording stopped.\");\n end(true);\n return;\n }\n if (sync && \"sendBeacon\" in navigator && navigator.sendBeacon(url, body)) return;\n try {\n const r = await fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body, keepalive: sync || body.length < 60_000 });\n // Recordings turned off, consent missing, or the recording reached its size cap: stop for this visit.\n if (r.status === 403 || r.status === 409 || r.status === 413) end(true);\n } catch {\n o.warn(`Could not reach ${o.host || \"this origin\"} to upload a recording.`);\n }\n };\n\n function flush(sync: boolean) {\n if (!pending.length) return;\n const events = pending;\n pending = [];\n pendingBytes = 0;\n lastFlush = Date.now();\n start ??= events[0].timestamp;\n const chunkMarks = marks;\n marks = [];\n const payload = {\n key: o.apiKey,\n sessionId: o.sessionId,\n distinctId,\n identified,\n start,\n seq: seq++,\n consent: { replay: true },\n trigger,\n final: sync,\n meta: {\n first: events[0].timestamp,\n last: events[events.length - 1].timestamp,\n vw: window.innerWidth,\n vh: window.innerHeight,\n marks: chunkMarks,\n },\n };\n const json = `[${events.map((e) => e.json).join(\",\")}]`;\n if (sync) void send(payload, json, true);\n else queue = queue.then(() => send(payload, json, false));\n }\n\n const fire = (kind: Exclude<ReplayTrigger, \"sample\">) => {\n if (stopped || mode === \"upload\") return;\n mode = \"upload\";\n trigger = kind;\n window.clearInterval(checkout);\n pending = ring.drain();\n pendingBytes = pending.reduce((s, e) => s + e.json.length, 0);\n flush(false);\n };\n\n const emit = (e: ReplayEvent) => {\n if (stopped) return;\n if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) {\n end(false);\n return;\n }\n const json = JSON.stringify(e);\n const item: Held = { type: e.type, timestamp: e.timestamp, json };\n if (mode === \"buffer\") {\n ring.push(item, json.length);\n const from = ring.first ?? e.timestamp - BUFFER_MS;\n marks = marks.filter((m) => m.t >= from);\n return;\n }\n pending.push(item);\n pendingBytes += json.length;\n if (pendingBytes >= FLUSH_BYTES) flush(false);\n };\n\n const original = console.error;\n const patched = (...args: unknown[]) => {\n try {\n record.addCustomEvent(\"$console\", { level: \"error\", text: clip(args.map((a) => (a instanceof Error ? a.message : typeof a === \"string\" ? a : \"[object]\")).join(\" \"), 300) });\n } catch {\n // Recording must never break the page's own logging.\n }\n original.apply(console, args);\n };\n const onError = (e: ErrorEvent) => {\n const message = clip(e.message || \"Script error\", 200);\n marks.push({ t: Date.now(), kind: \"error\", path: pathNow(), label: message });\n try {\n record.addCustomEvent(\"$error\", { message });\n } catch {\n // ignore\n }\n fire(\"error\");\n };\n const onRejection = (e: PromiseRejectionEvent) => {\n const r = e.reason as { message?: unknown } | undefined;\n const message = clip(typeof r?.message === \"string\" ? r.message : \"Unhandled promise rejection\", 200);\n marks.push({ t: Date.now(), kind: \"error\", path: pathNow(), label: message });\n try {\n record.addCustomEvent(\"$error\", { message });\n } catch {\n // ignore\n }\n fire(\"error\");\n };\n const onHide = () => mode === \"upload\" && flush(true);\n const onVisibility = () => document.visibilityState === \"hidden\" && mode === \"upload\" && flush(false);\n\n const stopRecording = record(recordOptions(emit) as unknown as Parameters<typeof record>[0]);\n if (!stopRecording) return null;\n console.error = patched;\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n window.addEventListener(\"pagehide\", onHide);\n document.addEventListener(\"visibilitychange\", onVisibility);\n const tick = window.setInterval(() => {\n if (Date.now() - (start ?? begun) > MAX_RECORDING_MS) end(false);\n else if (mode === \"upload\" && chunkDue(pendingBytes, Date.now() - lastFlush)) flush(false);\n }, 1000);\n // A buffering visit snapshots every 30 seconds, so the buffer always starts at a full snapshot within the minute.\n const checkout = mode === \"buffer\" ? window.setInterval(() => record.takeFullSnapshot(true), CHECKOUT_MS) : 0;\n\n return {\n event: (name) => {\n const t = triggerOfEvent(name);\n if (t) fire(t);\n },\n click: (c, rage) => {\n if (stopped) return;\n const t = Date.now();\n const path = pathNow();\n const label = c.label || c.selector.split(\" > \").pop() || \"\";\n marks.push({ t, kind: \"click\", path, label, x: c.x, y: c.y, vw: c.vw, dead: !c.interactive });\n if (rage) {\n marks.push({ t, kind: \"rage\", path, label, x: c.x, y: c.y, vw: c.vw });\n fire(\"rage\");\n }\n },\n page: (path) => !stopped && marks.push({ t: Date.now(), kind: \"pageview\", path: path.slice(0, 300) || \"/\" }),\n identify: (id) => {\n distinctId = id;\n identified = true;\n },\n stop: (discard = false) => end(discard),\n };\n};\n"],"mappings":"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","names":[]}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CockpitConfig, type VariantId } from "./core.js";
1
+ import { type CockpitConfig, type FlagPerson, type VariantId } from "./core.js";
2
2
  /**
3
3
  * Server helpers for the App Router: load the experiments once per request, then render the right variant on the
4
4
  * first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.
@@ -40,6 +40,18 @@ interface RequestInfo {
40
40
  }
41
41
  /** The variant of one experiment for this request. */
42
42
  export declare const getVariant: (key: string, { config, searchParams, cookies, distinctId }: RequestInfo) => VariantId;
43
+ /**
44
+ * A feature flag for this request: true while it is on for the visitor, false while it is off or unknown (or MRRocket
45
+ * could not be reached). `person` takes the properties the flag's audience reads ("Email", "Plan" …), as you pass them
46
+ * to identify(). For a multivariate flag use `getFlagVariant`.
47
+ */
48
+ export declare const getFlag: (key: string, { config, cookies, distinctId, person }: RequestInfo & {
49
+ person?: FlagPerson;
50
+ }) => boolean;
51
+ /** The variant of a multivariate flag for this request, or null while it is off. */
52
+ export declare const getFlagVariant: (key: string, { config, cookies, distinctId, person }: RequestInfo & {
53
+ person?: FlagPerson;
54
+ }) => string | null;
43
55
  /** Every text change for this request, to render `<Text>` defaults server-side. */
44
56
  export declare const getTexts: ({ config, searchParams, cookies, distinctId }: RequestInfo) => Record<string, string>;
45
57
  export * from "./core.js";
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/server.ts
2
- import { EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, envKey, parsePreview, resolveHost, resolveVariant, textChanges } from "./core.js";
2
+ import { EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, envKey, flagFor, parsePreview, resolveHost, resolveVariant, textChanges } from "./core.js";
3
3
  export * from "./core.js";
4
4
  import { REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from "./referral.js";
5
5
  var CONFIG_TIMEOUT_MS = 2500;
@@ -28,11 +28,18 @@ var previewFrom = (searchParams) => {
28
28
  };
29
29
  var distinctIdFrom = (cookies) => cookies?.get(ID_COOKIE)?.value ?? "anonymous";
30
30
  var getVariant = (key, { config, searchParams, cookies, distinctId }) => resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);
31
+ var getFlag = (key, { config, cookies, distinctId, person }) => flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person) !== false;
32
+ var getFlagVariant = (key, { config, cookies, distinctId, person }) => {
33
+ const v = flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person);
34
+ return typeof v === "string" ? v : null;
35
+ };
31
36
  var getTexts = ({ config, searchParams, cookies, distinctId }) => textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);
32
37
  export {
33
38
  REFERRAL_COOKIE,
34
39
  distinctIdFrom,
35
40
  getCockpitConfig,
41
+ getFlag,
42
+ getFlagVariant,
36
43
  getTexts,
37
44
  getVariant,
38
45
  previewFrom,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { type CockpitConfig, EMBED_PARAM, ID_COOKIE, PREVIEW_PARAM, type VariantId, envKey, parsePreview, resolveHost, resolveVariant, textChanges } from \"./core\";\n\n/**\n * Server helpers for the App Router: load the experiments once per request, then render the right variant on the\n * first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.\n */\n\ntype SearchParams = Record<string, string | string[] | undefined>;\ninterface CookieReader {\n get(name: string): { value: string } | undefined;\n}\n\n/** How long the server waits for MRRocket before it renders with control. */\nconst CONFIG_TIMEOUT_MS = 2500;\n\n/**\n * The experiments for a public key. `null` when MRRocket cannot be reached, answers with an error or is slower than\n * `timeoutMs`: everyone then sees control and the page still renders. Runs on Node, the Edge runtime and Cloudflare\n * Workers (only `fetch` and `AbortController`).\n *\n * `apiKey` and `host` default to `NEXT_PUBLIC_MRROCKET_KEY` and `NEXT_PUBLIC_MRROCKET_HOST`.\n */\nexport const getCockpitConfig = async ({ apiKey, host, timeoutMs = CONFIG_TIMEOUT_MS }: { apiKey?: string; host?: string; timeoutMs?: number } = {}): Promise<CockpitConfig | null> => {\n const key = apiKey ?? envKey();\n if (!key) return null;\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n // `next.revalidate: 0` keeps this request uncached in Next.js. Not `cache: \"no-store\"`: Cloudflare Workers\n // throws on the `cache` field of a fetch unless a compatibility flag is on, and every config would be null.\n const res = await fetch(`${resolveHost(host)}/api/sdk/config?key=${encodeURIComponent(key)}`, {\n signal: ctl.signal,\n next: { revalidate: 0 },\n } as RequestInit);\n if (!res.ok) return null;\n const config = (await res.json()) as CockpitConfig | null;\n return config && Array.isArray(config.experiments) ? config : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n};\n\n/** Forced variants and the embed flag from a page's search params, for previews in Cockpit. */\nexport const previewFrom = (searchParams: SearchParams | undefined) => {\n const embed = searchParams?.[EMBED_PARAM];\n return { preview: parsePreview(searchParams?.[PREVIEW_PARAM]), embedded: (Array.isArray(embed) ? embed[0] : embed) === \"1\" };\n};\n\n/** The visitor's id from the `ck_id` cookie, if the browser has one yet (only in consent \"cookies\" mode). */\nexport const distinctIdFrom = (cookies: CookieReader | undefined) => cookies?.get(ID_COOKIE)?.value ?? \"anonymous\";\n\n/**\n * Who the visitor is for this request. Pass `distinctId` (your signed-in user id, the same you give\n * `<CockpitProvider distinctId>`) when there is no `ck_id` cookie, as in the default cookieless mode.\n */\ninterface RequestInfo {\n config: CockpitConfig | null;\n searchParams?: SearchParams;\n cookies?: CookieReader;\n distinctId?: string;\n}\n\n/** The variant of one experiment for this request. */\nexport const getVariant = (key: string, { config, searchParams, cookies, distinctId }: RequestInfo): VariantId =>\n resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\n/** Every text change for this request, to render `<Text>` defaults server-side. */\nexport const getTexts = ({ config, searchParams, cookies, distinctId }: RequestInfo) =>\n textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\nexport * from \"./core\";\n/** Affiliate referrals on the server: read the `mrr_ref` cookie in the route that creates a Stripe Checkout Session. */\nexport { type CheckoutReferral, type Referral, REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from \"./referral\";\n"],"mappings":";AAAA,SAA6B,aAAa,WAAW,eAA+B,QAAQ,cAAc,aAAa,gBAAgB,mBAAmB;AAwE1J,cAAc;AAEd,SAA+C,iBAAiB,oBAAoB,qBAAqB,oBAAoB;AA7D7H,IAAM,oBAAoB;AASnB,IAAM,mBAAmB,OAAO,EAAE,QAAQ,MAAM,YAAY,kBAAkB,IAA4D,CAAC,MAAqC;AACnL,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,gBAAgB;AAChC,QAAM,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG,SAAS;AACrD,MAAI;AAGA,UAAM,MAAM,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,uBAAuB,mBAAmB,GAAG,CAAC,IAAI;AAAA,MAC1F,QAAQ,IAAI;AAAA,MACZ,MAAM,EAAE,YAAY,EAAE;AAAA,IAC1B,CAAgB;AAChB,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,SAAU,MAAM,IAAI,KAAK;AAC/B,WAAO,UAAU,MAAM,QAAQ,OAAO,WAAW,IAAI,SAAS;AAAA,EAClE,QAAQ;AACJ,WAAO;AAAA,EACX,UAAE;AACE,iBAAa,KAAK;AAAA,EACtB;AACJ;AAGO,IAAM,cAAc,CAAC,iBAA2C;AACnE,QAAM,QAAQ,eAAe,WAAW;AACxC,SAAO,EAAE,SAAS,aAAa,eAAe,aAAa,CAAC,GAAG,WAAW,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI,WAAW,IAAI;AAC/H;AAGO,IAAM,iBAAiB,CAAC,YAAsC,SAAS,IAAI,SAAS,GAAG,SAAS;AAchG,IAAM,aAAa,CAAC,KAAa,EAAE,QAAQ,cAAc,SAAS,WAAW,MAChF,eAAe,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;AAGjG,IAAM,WAAW,CAAC,EAAE,QAAQ,cAAc,SAAS,WAAW,MACjE,YAAY,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { type CockpitConfig, EMBED_PARAM, type FlagPerson, ID_COOKIE, PREVIEW_PARAM, type VariantId, envKey, flagFor, parsePreview, resolveHost, resolveVariant, textChanges } from \"./core\";\n\n/**\n * Server helpers for the App Router: load the experiments once per request, then render the right variant on the\n * first paint (no flicker) and hand the same config to `<CockpitProvider config>` so the browser agrees.\n */\n\ntype SearchParams = Record<string, string | string[] | undefined>;\ninterface CookieReader {\n get(name: string): { value: string } | undefined;\n}\n\n/** How long the server waits for MRRocket before it renders with control. */\nconst CONFIG_TIMEOUT_MS = 2500;\n\n/**\n * The experiments for a public key. `null` when MRRocket cannot be reached, answers with an error or is slower than\n * `timeoutMs`: everyone then sees control and the page still renders. Runs on Node, the Edge runtime and Cloudflare\n * Workers (only `fetch` and `AbortController`).\n *\n * `apiKey` and `host` default to `NEXT_PUBLIC_MRROCKET_KEY` and `NEXT_PUBLIC_MRROCKET_HOST`.\n */\nexport const getCockpitConfig = async ({ apiKey, host, timeoutMs = CONFIG_TIMEOUT_MS }: { apiKey?: string; host?: string; timeoutMs?: number } = {}): Promise<CockpitConfig | null> => {\n const key = apiKey ?? envKey();\n if (!key) return null;\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n // `next.revalidate: 0` keeps this request uncached in Next.js. Not `cache: \"no-store\"`: Cloudflare Workers\n // throws on the `cache` field of a fetch unless a compatibility flag is on, and every config would be null.\n const res = await fetch(`${resolveHost(host)}/api/sdk/config?key=${encodeURIComponent(key)}`, {\n signal: ctl.signal,\n next: { revalidate: 0 },\n } as RequestInit);\n if (!res.ok) return null;\n const config = (await res.json()) as CockpitConfig | null;\n return config && Array.isArray(config.experiments) ? config : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n};\n\n/** Forced variants and the embed flag from a page's search params, for previews in Cockpit. */\nexport const previewFrom = (searchParams: SearchParams | undefined) => {\n const embed = searchParams?.[EMBED_PARAM];\n return { preview: parsePreview(searchParams?.[PREVIEW_PARAM]), embedded: (Array.isArray(embed) ? embed[0] : embed) === \"1\" };\n};\n\n/** The visitor's id from the `ck_id` cookie, if the browser has one yet (only in consent \"cookies\" mode). */\nexport const distinctIdFrom = (cookies: CookieReader | undefined) => cookies?.get(ID_COOKIE)?.value ?? \"anonymous\";\n\n/**\n * Who the visitor is for this request. Pass `distinctId` (your signed-in user id, the same you give\n * `<CockpitProvider distinctId>`) when there is no `ck_id` cookie, as in the default cookieless mode.\n */\ninterface RequestInfo {\n config: CockpitConfig | null;\n searchParams?: SearchParams;\n cookies?: CookieReader;\n distinctId?: string;\n}\n\n/** The variant of one experiment for this request. */\nexport const getVariant = (key: string, { config, searchParams, cookies, distinctId }: RequestInfo): VariantId =>\n resolveVariant(key, config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\n/**\n * A feature flag for this request: true while it is on for the visitor, false while it is off or unknown (or MRRocket\n * could not be reached). `person` takes the properties the flag's audience reads (\"Email\", \"Plan\" …), as you pass them\n * to identify(). For a multivariate flag use `getFlagVariant`.\n */\nexport const getFlag = (key: string, { config, cookies, distinctId, person }: RequestInfo & { person?: FlagPerson }): boolean =>\n flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person) !== false;\n\n/** The variant of a multivariate flag for this request, or null while it is off. */\nexport const getFlagVariant = (key: string, { config, cookies, distinctId, person }: RequestInfo & { person?: FlagPerson }): string | null => {\n const v = flagFor(key, config, distinctId ?? distinctIdFrom(cookies), person);\n return typeof v === \"string\" ? v : null;\n};\n\n/** Every text change for this request, to render `<Text>` defaults server-side. */\nexport const getTexts = ({ config, searchParams, cookies, distinctId }: RequestInfo) =>\n textChanges(config, distinctId ?? distinctIdFrom(cookies), previewFrom(searchParams).preview);\n\nexport * from \"./core\";\n/** Affiliate referrals on the server: read the `mrr_ref` cookie in the route that creates a Stripe Checkout Session. */\nexport { type CheckoutReferral, type Referral, REFERRAL_COOKIE, readReferralCookie, referralForCheckout, referralLink } from \"./referral\";\n"],"mappings":";AAAA,SAA6B,aAA8B,WAAW,eAA+B,QAAQ,SAAS,cAAc,aAAa,gBAAgB,mBAAmB;AAsFpL,cAAc;AAEd,SAA+C,iBAAiB,oBAAoB,qBAAqB,oBAAoB;AA3E7H,IAAM,oBAAoB;AASnB,IAAM,mBAAmB,OAAO,EAAE,QAAQ,MAAM,YAAY,kBAAkB,IAA4D,CAAC,MAAqC;AACnL,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,gBAAgB;AAChC,QAAM,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG,SAAS;AACrD,MAAI;AAGA,UAAM,MAAM,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,uBAAuB,mBAAmB,GAAG,CAAC,IAAI;AAAA,MAC1F,QAAQ,IAAI;AAAA,MACZ,MAAM,EAAE,YAAY,EAAE;AAAA,IAC1B,CAAgB;AAChB,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,SAAU,MAAM,IAAI,KAAK;AAC/B,WAAO,UAAU,MAAM,QAAQ,OAAO,WAAW,IAAI,SAAS;AAAA,EAClE,QAAQ;AACJ,WAAO;AAAA,EACX,UAAE;AACE,iBAAa,KAAK;AAAA,EACtB;AACJ;AAGO,IAAM,cAAc,CAAC,iBAA2C;AACnE,QAAM,QAAQ,eAAe,WAAW;AACxC,SAAO,EAAE,SAAS,aAAa,eAAe,aAAa,CAAC,GAAG,WAAW,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI,WAAW,IAAI;AAC/H;AAGO,IAAM,iBAAiB,CAAC,YAAsC,SAAS,IAAI,SAAS,GAAG,SAAS;AAchG,IAAM,aAAa,CAAC,KAAa,EAAE,QAAQ,cAAc,SAAS,WAAW,MAChF,eAAe,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;AAOjG,IAAM,UAAU,CAAC,KAAa,EAAE,QAAQ,SAAS,YAAY,OAAO,MACvE,QAAQ,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,MAAM,MAAM;AAGrE,IAAM,iBAAiB,CAAC,KAAa,EAAE,QAAQ,SAAS,YAAY,OAAO,MAA4D;AAC1I,QAAM,IAAI,QAAQ,KAAK,QAAQ,cAAc,eAAe,OAAO,GAAG,MAAM;AAC5E,SAAO,OAAO,MAAM,WAAW,IAAI;AACvC;AAGO,IAAM,WAAW,CAAC,EAAE,QAAQ,cAAc,SAAS,WAAW,MACjE,YAAY,QAAQ,cAAc,eAAe,OAAO,GAAG,YAAY,YAAY,EAAE,OAAO;","names":[]}
package/dist/styles.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */
2
- export declare const CSS = "\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n";
2
+ export declare const CSS = "\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-root .ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n";
3
3
  /** Puts the stylesheet into the page once, however many widgets mount. */
4
4
  export declare const useStyles: () => void;
package/dist/styles.js CHANGED
@@ -119,8 +119,8 @@ font-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizi
119
119
  .ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}
120
120
  .ckw-num:hover{border-color:var(--ckw-accent)}
121
121
  .ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}
122
- .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}
123
- .ckw-star[data-on=true]{color:#f5a524}
122
+ .ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}
123
+ .ckw-root .ckw-star[data-on=true]{color:#f5a524}
124
124
  .ckw-star svg{width:28px;height:28px}
125
125
  .ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}
126
126
  .ckw-opts{display:flex;flex-direction:column;gap:6px}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styles.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nconst STYLE_ID = \"ckw-styles\";\n\n/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */\nexport const CSS = `\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n`;\n\n/** Puts the stylesheet into the page once, however many widgets mount. */\nexport const useStyles = () => {\n useEffect(() => {\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n};\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAE1B,IAAM,WAAW;AAGV,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkIZ,IAAM,YAAY,MAAM;AAC3B,YAAU,MAAM;AACZ,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,OAAG,KAAK;AACR,OAAG,cAAc;AACjB,aAAS,KAAK,YAAY,EAAE;AAAA,EAChC,GAAG,CAAC,CAAC;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/styles.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\nconst STYLE_ID = \"ckw-styles\";\n\n/** The one stylesheet for every Cockpit widget. Everything is scoped under `.ckw-root` and prefixed `ckw-`. */\nexport const CSS = `\n.ckw-root{--ckw-accent:#3f76ff;--ckw-accent-ink:#fff;--ckw-bg:#fff;--ckw-bg2:#f7f7f8;--ckw-bg3:#eeeff1;--ckw-fg:#1c1d20;--ckw-fg2:#5d6068;--ckw-fg3:#8b8e96;--ckw-line:#e4e5e8;--ckw-danger:#d92d20;--ckw-shadow:0 8px 30px rgba(16,18,24,.16);\nfont-size:14px;line-height:1.45;font-family:inherit;color:var(--ckw-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}\n.ckw-root[data-theme=dark]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto]{--ckw-bg:#17181b;--ckw-bg2:#1f2024;--ckw-bg3:#292a30;--ckw-fg:#f1f2f4;--ckw-fg2:#b0b3ba;--ckw-fg3:#7e818a;--ckw-line:#2e2f35;--ckw-shadow:0 8px 30px rgba(0,0,0,.5)}}\n.ckw-root *,.ckw-root *::before,.ckw-root *::after{box-sizing:border-box}\n.ckw-root button,.ckw-root input,.ckw-root select,.ckw-root textarea{font:inherit;color:inherit;margin:0}\n.ckw-root button{cursor:pointer}\n.ckw-root :focus-visible{outline:2px solid var(--ckw-accent);outline-offset:2px}\n.ckw-board{width:100%;max-width:760px;margin:0 auto;background:var(--ckw-bg);min-width:0}\n.ckw-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}\n.ckw-title{font-size:20px;font-weight:600;margin:0;letter-spacing:-.01em}\n.ckw-sub{color:var(--ckw-fg2);margin:2px 0 0;font-size:13px}\n.ckw-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--ckw-line);background:var(--ckw-bg);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-btn:hover{background:var(--ckw-bg2)}\n.ckw-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckw-btn-primary{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-btn-primary:hover{background:var(--ckw-accent);filter:brightness(1.08)}\n.ckw-btn-ghost{border-color:transparent;background:transparent;color:var(--ckw-fg2)}\n.ckw-icon{width:16px;height:16px;flex:none;display:block}\n.ckw-tabs{display:flex;gap:4px;border-bottom:1px solid var(--ckw-line);margin-bottom:12px;overflow-x:auto}\n.ckw-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 10px;margin-bottom:-1px;color:var(--ckw-fg2);font-weight:500;font-size:13px;white-space:nowrap}\n.ckw-tab[aria-selected=true]{color:var(--ckw-fg);border-bottom-color:var(--ckw-accent)}\n.ckw-tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}\n.ckw-input,.ckw-select,.ckw-textarea{width:100%;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);padding:7px 10px;font-size:14px;min-height:34px}\n.ckw-input::placeholder,.ckw-textarea::placeholder{color:var(--ckw-fg3)}\n.ckw-textarea{resize:vertical;min-height:72px}\n.ckw-search{flex:1 1 160px;min-width:0}\n.ckw-seg{display:inline-flex;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-seg button{border:0;background:var(--ckw-bg);padding:0 10px;height:32px;font-size:13px;color:var(--ckw-fg2)}\n.ckw-seg button[aria-pressed=true]{background:var(--ckw-bg3);color:var(--ckw-fg);font-weight:500}\n.ckw-chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}\n.ckw-chip{border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:999px;padding:3px 10px;font-size:12px;color:var(--ckw-fg2)}\n.ckw-chip[aria-pressed=true]{background:var(--ckw-fg);color:var(--ckw-bg);border-color:var(--ckw-fg)}\n.ckw-list{list-style:none;margin:0;padding:0;border:1px solid var(--ckw-line);border-radius:8px;overflow:hidden}\n.ckw-list>li+li{border-top:1px solid var(--ckw-line)}\n.ckw-row{display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:var(--ckw-bg)}\n.ckw-row:hover{background:var(--ckw-bg2)}\n.ckw-vote{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;width:44px;min-height:48px;border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;padding:4px 0;color:var(--ckw-fg2)}\n.ckw-vote:hover{border-color:var(--ckw-accent);color:var(--ckw-accent)}\n.ckw-vote[aria-pressed=true]{background:var(--ckw-accent);border-color:var(--ckw-accent);color:var(--ckw-accent-ink)}\n.ckw-vote-sm{width:auto;min-height:0;flex-direction:row;gap:4px;padding:2px 8px}\n.ckw-main{flex:1;min-width:0;text-align:left}\n.ckw-link{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font-weight:600;font-size:14px;color:var(--ckw-fg);overflow-wrap:anywhere}\n.ckw-link:hover{color:var(--ckw-accent)}\n.ckw-excerpt{margin:2px 0 0;color:var(--ckw-fg2);font-size:13px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}\n.ckw-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--ckw-fg3);font-size:12px;flex-wrap:wrap}\n.ckw-pill{display:inline-flex;align-items:center;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;background:var(--ckw-bg3);color:var(--ckw-fg2)}\n.ckw-pill[data-s=planned]{background:rgba(63,118,255,.14);color:#3f76ff}\n.ckw-pill[data-s=progress]{background:rgba(245,158,11,.16);color:#b45309}\n.ckw-pill[data-s=shipped]{background:rgba(22,163,74,.15);color:#15803d}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=progress]{color:#fbbf24}\n.ckw-root[data-theme=dark] .ckw-pill[data-s=shipped]{color:#4ade80}\n@media (prefers-color-scheme:dark){.ckw-root[data-theme=auto] .ckw-pill[data-s=progress]{color:#fbbf24}.ckw-root[data-theme=auto] .ckw-pill[data-s=shipped]{color:#4ade80}}\n.ckw-stat{display:inline-flex;align-items:center;gap:4px}\n.ckw-empty{border:1px dashed var(--ckw-line);border-radius:8px;padding:28px 16px;text-align:center;color:var(--ckw-fg2)}\n.ckw-error{border:1px solid color-mix(in srgb,var(--ckw-danger) 40%,transparent);background:color-mix(in srgb,var(--ckw-danger) 8%,transparent);color:var(--ckw-danger);border-radius:8px;padding:8px 10px;font-size:13px;display:flex;gap:8px;align-items:center;justify-content:space-between;margin-bottom:10px}\n.ckw-skel{height:68px;border-radius:8px;background:var(--ckw-bg2);animation:ckw-sh 1.2s ease-in-out infinite;margin-bottom:6px}\n@keyframes ckw-sh{50%{opacity:.5}}\n@media (prefers-reduced-motion:reduce){.ckw-skel{animation:none}.ckw-root *{transition:none!important}}\n.ckw-form{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ckw-line);border-radius:8px;padding:12px;margin-bottom:12px;background:var(--ckw-bg)}\n.ckw-field{display:flex;flex-direction:column;gap:4px}\n.ckw-label{font-size:12px;font-weight:600;color:var(--ckw-fg2)}\n.ckw-actions{display:flex;gap:8px;justify-content:flex-end}\n.ckw-dupes{border:1px solid var(--ckw-line);border-radius:8px;background:var(--ckw-bg2);padding:8px 10px;font-size:13px}\n.ckw-dupes p{margin:0 0 4px;color:var(--ckw-fg2);font-size:12px}\n.ckw-dupe{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:3px 0}\n.ckw-dupe span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ckw-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}\n@media (max-width:640px){.ckw-cols{grid-template-columns:1fr}}\n.ckw-col h3{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--ckw-fg2);margin:0 0 8px;display:flex;gap:6px;align-items:center}\n.ckw-card{border:1px solid var(--ckw-line);border-radius:8px;padding:10px;margin-bottom:8px;background:var(--ckw-bg)}\n.ckw-card .ckw-link{margin-bottom:6px}\n.ckw-entry{padding:12px 0;border-bottom:1px solid var(--ckw-line)}\n.ckw-entry time{font-size:12px;color:var(--ckw-fg3)}\n.ckw-entry h3{font-size:15px;margin:2px 0 4px}\n.ckw-entry p{margin:0;color:var(--ckw-fg2);white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-back{margin-bottom:8px;padding-left:4px}\n.ckw-detail-head{display:flex;gap:12px;align-items:flex-start}\n.ckw-detail-head h2{margin:0 0 4px;font-size:18px;overflow-wrap:anywhere}\n.ckw-body{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ckw-fg2);margin:12px 0}\n.ckw-h{font-size:13px;font-weight:600;margin:16px 0 8px}\n.ckw-comment{padding:8px 0;border-top:1px solid var(--ckw-line)}\n.ckw-comment b{font-size:13px}\n.ckw-comment p{margin:2px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n.ckw-team{margin-left:6px;background:var(--ckw-accent);color:var(--ckw-accent-ink);border-radius:4px;padding:0 5px;font-size:10px;font-weight:700;text-transform:uppercase;vertical-align:1px}\n.ckw-time{color:var(--ckw-fg3);font-size:12px;margin-left:6px}\n.ckw-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n/* Floating widget */\n.ckw-fab{position:fixed;z-index:2147483000;bottom:20px;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border-radius:999px;border:0;background:var(--ckw-accent);color:var(--ckw-accent-ink);font-weight:600;font-size:14px;box-shadow:var(--ckw-shadow)}\n.ckw-fab[data-pos=bottom-right],.ckw-pop[data-pos=bottom-right]{right:20px}\n.ckw-fab[data-pos=bottom-left],.ckw-pop[data-pos=bottom-left]{left:20px}\n.ckw-pop{position:fixed;z-index:2147483001;bottom:70px;width:380px;max-width:calc(100vw - 24px);max-height:min(560px,calc(100vh - 90px));display:flex;flex-direction:column;background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);overflow:hidden}\n.ckw-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--ckw-line);font-weight:600}\n.ckw-pop-body{padding:12px;overflow-y:auto;flex:1}\n.ckw-pop-foot{padding:8px 12px;border-top:1px solid var(--ckw-line);display:flex;justify-content:space-between;align-items:center;font-size:13px}\n.ckw-pop-foot a{color:var(--ckw-accent);text-decoration:none;font-weight:500}\n.ckw-pop .ckw-list{border:0;border-radius:0}\n.ckw-pop .ckw-row{padding:8px 0}\n.ckw-pop .ckw-form{border:0;padding:0;margin:12px 0 0}\n@media (max-width:520px){.ckw-pop{left:0!important;right:0!important;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0;max-height:85vh}.ckw-fab{bottom:14px}.ckw-fab[data-pos=bottom-right]{right:14px}.ckw-fab[data-pos=bottom-left]{left:14px}}\n/* Survey */\n.ckw-survey{position:fixed;z-index:2147483000;left:20px;bottom:20px;width:360px;max-width:calc(100vw - 24px);background:var(--ckw-bg);border:1px solid var(--ckw-line);border-radius:12px;box-shadow:var(--ckw-shadow);padding:14px;animation:ckw-in .2s ease-out}\n@keyframes ckw-in{from{opacity:0;transform:translateY(8px)}}\n@media (max-width:520px){.ckw-survey{left:0;right:0;bottom:0;width:100%;max-width:100%;border-radius:12px 12px 0 0}}\n.ckw-sv-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}\n.ckw-dots{display:flex;gap:4px}\n.ckw-dot{width:6px;height:6px;border-radius:50%;background:var(--ckw-bg3)}\n.ckw-dot[data-on=true]{background:var(--ckw-accent)}\n.ckw-q{font-weight:600;font-size:15px;margin:0 0 2px;border:0;padding:0}\n.ckw-root .ckw-q:focus-visible{outline:none}\n.ckw-qd{color:var(--ckw-fg2);font-size:13px;margin:0 0 10px}\n.ckw-scale{display:flex;gap:4px;flex-wrap:wrap}\n.ckw-num{flex:1 1 0;min-width:26px;height:34px;border:1px solid var(--ckw-line);background:var(--ckw-bg);border-radius:8px;font-size:13px;font-weight:600;padding:0}\n.ckw-num:hover{border-color:var(--ckw-accent)}\n.ckw-num[aria-pressed=true]{background:var(--ckw-accent);color:var(--ckw-accent-ink);border-color:var(--ckw-accent)}\n.ckw-root .ckw-star{background:none;border:0;padding:2px;color:var(--ckw-bg3)}\n.ckw-root .ckw-star[data-on=true]{color:#f5a524}\n.ckw-star svg{width:28px;height:28px}\n.ckw-lh{display:flex;justify-content:space-between;color:var(--ckw-fg3);font-size:11px;margin-top:4px}\n.ckw-opts{display:flex;flex-direction:column;gap:6px}\n.ckw-opt{display:flex;gap:8px;align-items:center;border:1px solid var(--ckw-line);border-radius:8px;padding:7px 10px;background:var(--ckw-bg);cursor:pointer}\n.ckw-opt:has(input:checked){border-color:var(--ckw-accent);background:color-mix(in srgb,var(--ckw-accent) 8%,transparent)}\n.ckw-opt input{accent-color:var(--ckw-accent)}\n.ckw-sv-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:12px}\n.ckw-thanks{text-align:center;padding:14px 0;display:flex;flex-direction:column;align-items:center;gap:8px}\n.ckw-tick{width:32px;height:32px;border-radius:50%;background:var(--ckw-accent);color:var(--ckw-accent-ink);display:flex;align-items:center;justify-content:center}\n`;\n\n/** Puts the stylesheet into the page once, however many widgets mount. */\nexport const useStyles = () => {\n useEffect(() => {\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n};\n"],"mappings":";;;AAEA,SAAS,iBAAiB;AAE1B,IAAM,WAAW;AAGV,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkIZ,IAAM,YAAY,MAAM;AAC3B,YAAU,MAAM;AACZ,QAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,OAAG,KAAK;AACR,OAAG,cAAc;AACjB,aAAS,KAAK,YAAY,EAAE;AAAA,EAChC,GAAG,CAAC,CAAC;AACT;","names":[]}
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@mrrocket/sdk",
3
- "version": "0.1.0",
4
- "description": "MRRocket SDK: page views, events, A/B tests, feedback board and surveys that your MRRocket agents can run and read. Next.js today (@mrrocket/sdk/next), more frameworks next.",
3
+ "version": "0.3.0",
4
+ "description": "MRRocket SDK: page views, events, heatmap clicks, session recordings, A/B tests, feedback board and surveys that your MRRocket agents can run and read. Next.js today (@mrrocket/sdk/next), more frameworks next.",
5
5
  "keywords": [
6
6
  "nextjs",
7
7
  "analytics",
8
8
  "ab-testing",
9
+ "heatmaps",
10
+ "session-replay",
9
11
  "experiments",
10
12
  "feedback",
11
13
  "surveys",
@@ -69,6 +71,9 @@
69
71
  "check:publish": "node scripts/check-publish.mjs",
70
72
  "prepublishOnly": "npm run check:publish && npm run typecheck && npm run build"
71
73
  },
74
+ "dependencies": {
75
+ "@rrweb/record": "2.1.6"
76
+ },
72
77
  "peerDependencies": {
73
78
  "next": ">=15",
74
79
  "react": ">=19"
@@ -81,5 +86,12 @@
81
86
  "react-dom": "^19",
82
87
  "tsup": "^8.5.1",
83
88
  "typescript": "^5.9"
89
+ },
90
+ "overrides": {
91
+ "rrweb": "2.1.6",
92
+ "rrdom": "2.1.6",
93
+ "rrweb-snapshot": "2.1.6",
94
+ "@rrweb/types": "2.1.6",
95
+ "@rrweb/utils": "2.1.6"
84
96
  }
85
97
  }