highseat 0.6.0 → 0.8.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/react.d.ts CHANGED
@@ -1,13 +1,7 @@
1
1
  import { BeforeSend, InjectOptions, UserId } from "./index.js";
2
+ import { EmbedBackground, EmbedTheme, EmbedWidget } from "./embed.js";
3
+ import { CSSProperties, IframeHTMLAttributes, ReactElement } from "react";
2
4
  //#region src/react.d.ts
3
- export interface HighseatProps extends InjectOptions {
4
- /**
5
- * The signed-in user's id from your own system, never an email or other personal data. Pass
6
- * `null` once the visitor is signed out, and `undefined` while the session is still loading.
7
- * See {@link useIdentify}.
8
- */
9
- userId?: UserId;
10
- }
11
5
  /**
12
6
  * Keeps the visitor's user id in step with a session. Each new value is passed to `identify`
13
7
  * once: an id identifies the visitor, `null` clears the id at sign-out or session expiry, and
@@ -39,6 +33,14 @@ export declare function useIdentify(userId: UserId): void;
39
33
  * ```
40
34
  */
41
35
  export declare function useBeforeSend(beforeSend: BeforeSend | undefined): void;
36
+ export interface HighseatProps extends InjectOptions {
37
+ /**
38
+ * The signed-in user's id from your own system, never an email or other personal data. Pass
39
+ * `null` once the visitor is signed out, and `undefined` while the session is still loading.
40
+ * See {@link useIdentify}.
41
+ */
42
+ userId?: UserId;
43
+ }
42
44
  /**
43
45
  * Loads the Highseat tracker. Render it once, in the root layout. It adds the script tag after
44
46
  * the page mounts and renders nothing; mounting it again adds no second tag, and the tracker
@@ -46,5 +48,53 @@ export declare function useBeforeSend(beforeSend: BeforeSend | undefined): void;
46
48
  * {@link useIdentify} does, and with `beforeSend` it sets the hook, as {@link useBeforeSend} does.
47
49
  */
48
50
  export declare function Highseat({ site, src, outbound, downloads, hashRouting, beforeSend, debug, userId }: HighseatProps): null;
51
+ /**
52
+ * The props of `<Embed />`. Attributes it does not name, such as `id` or `loading`, go to the
53
+ * frame, while `className` and `style` go to the box around the frame and the credit link.
54
+ */
55
+ export interface EmbedProps extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, "children" | "className" | "height" | "src" | "srcDoc" | "style" | "title" | "width"> {
56
+ /** The token of a share link or a widget, the last part of its embed code's `/share/` URL. */
57
+ token: string;
58
+ /** The frame's accessible name, which a screen reader announces. */
59
+ title: string;
60
+ /**
61
+ * The embed's theme. Leave it out for `system`, which follows the reader's own setting. Pass
62
+ * the page's theme when the page has its own switch, or the frame can disagree with the page.
63
+ * Pass `null` while that theme is still unknown after mount: the slot stays empty until then,
64
+ * so the frame loads once.
65
+ */
66
+ theme?: EmbedTheme | null;
67
+ /**
68
+ * The embed's background. `transparent`, the default for a widget, lets your page's background
69
+ * show through. `default`, the default for a whole board, keeps the board's own.
70
+ */
71
+ background?: EmbedBackground;
72
+ /** The kind of widget the token shows, which sets the height held before the frame loads. */
73
+ widget?: EmbedWidget;
74
+ /** The height in pixels to hold until the embed reports its own. */
75
+ height?: number;
76
+ /** Whether to show the "Analytics by Highseat" link under the frame. It is yours to remove. */
77
+ credit?: boolean;
78
+ /** A class for the credit link. It replaces the link's default spacing and size. */
79
+ creditClassName?: string;
80
+ /** A class for the box around the frame and the credit link. */
81
+ className?: string;
82
+ /** A style for the box around the frame and the credit link. */
83
+ style?: CSSProperties;
84
+ }
85
+ /**
86
+ * Shows a Highseat share link or widget in a frame that fits its content, with a followed credit
87
+ * link under it. Needs no script tag: the component listens for the embed's height itself.
88
+ *
89
+ * The frame renders only after mount, so the page's theme can come from a client-only source such
90
+ * as `next-themes` with no hydration mismatch. Until then, an empty box holds the frame's height,
91
+ * and the credit link is already in the server's HTML.
92
+ *
93
+ * ```tsx
94
+ * <Embed token="YOUR_WIDGET_TOKEN" title="Visitors" widget="chart" theme="dark" />
95
+ * ```
96
+ */
97
+ export declare function Embed({ token, title, theme, background, widget, height, credit, creditClassName, className, style, ...frameProps }: EmbedProps): ReactElement;
49
98
  //#endregion
99
+ export type { EmbedBackground, EmbedTheme, EmbedWidget };
50
100
  //# sourceMappingURL=react.d.ts.map
package/react.js CHANGED
@@ -1,8 +1,18 @@
1
1
  "use client";
2
2
  import { inject, setBeforeSend } from "./index.js";
3
+ import { EMBED_FALLBACK_HEIGHT, WIDGET_FALLBACK_HEIGHTS, findEmbedHeight, formatEmbedSrc, getFrameColorScheme } from "./embed.js";
3
4
  import { syncIdentity } from "./identity.js";
4
- import { useEffect, useRef } from "react";
5
+ import { createElement, useEffect, useRef, useState, useSyncExternalStore } from "react";
5
6
  //#region src/react.ts
7
+ const CREDIT_STYLE = {
8
+ display: "block",
9
+ width: "fit-content",
10
+ marginTop: 8,
11
+ fontSize: 12
12
+ };
13
+ function subscribeToNothing() {
14
+ return () => {};
15
+ }
6
16
  /**
7
17
  * Keeps the visitor's user id in step with a session. Each new value is passed to `identify`
8
18
  * once: an id identifies the visitor, `null` clears the id at sign-out or session expiry, and
@@ -75,7 +85,64 @@ function Highseat({ site, src, outbound, downloads, hashRouting, beforeSend, deb
75
85
  useIdentify(userId);
76
86
  return null;
77
87
  }
88
+ /**
89
+ * Shows a Highseat share link or widget in a frame that fits its content, with a followed credit
90
+ * link under it. Needs no script tag: the component listens for the embed's height itself.
91
+ *
92
+ * The frame renders only after mount, so the page's theme can come from a client-only source such
93
+ * as `next-themes` with no hydration mismatch. Until then, an empty box holds the frame's height,
94
+ * and the credit link is already in the server's HTML.
95
+ *
96
+ * ```tsx
97
+ * <Embed token="YOUR_WIDGET_TOKEN" title="Visitors" widget="chart" theme="dark" />
98
+ * ```
99
+ */
100
+ function Embed({ token, title, theme, background, widget, height, credit = true, creditClassName, className, style, ...frameProps }) {
101
+ const isMounted = useSyncExternalStore(subscribeToNothing, () => true, () => false);
102
+ const [frame, setFrame] = useState(null);
103
+ const [reportedHeight, setReportedHeight] = useState();
104
+ useEffect(() => {
105
+ if (!frame) return;
106
+ function onMessage(event) {
107
+ const nextHeight = findEmbedHeight(event, frame?.contentWindow);
108
+ if (nextHeight !== void 0) setReportedHeight(nextHeight);
109
+ }
110
+ window.addEventListener("message", onMessage);
111
+ return () => window.removeEventListener("message", onMessage);
112
+ }, [frame]);
113
+ const look = {
114
+ theme: theme ?? "system",
115
+ background: background ?? (widget ? "transparent" : "default")
116
+ };
117
+ const fallbackHeight = widget ? WIDGET_FALLBACK_HEIGHTS[widget] : EMBED_FALLBACK_HEIGHT;
118
+ const frameHeight = reportedHeight ?? height ?? fallbackHeight;
119
+ const src = formatEmbedSrc(token, look);
120
+ return createElement("div", {
121
+ className,
122
+ style
123
+ }, isMounted && theme !== null ? createElement("iframe", {
124
+ loading: "lazy",
125
+ ...frameProps,
126
+ key: src,
127
+ ref: setFrame,
128
+ src,
129
+ title,
130
+ style: {
131
+ display: "block",
132
+ width: "100%",
133
+ height: frameHeight,
134
+ border: 0,
135
+ ...(look.background === "transparent" || widget) && { colorScheme: getFrameColorScheme(look.theme) }
136
+ }
137
+ }) : createElement("div", { style: { height: frameHeight } }), credit && createElement("a", {
138
+ href: "https://highseat.com/?ref=embed",
139
+ target: "_blank",
140
+ rel: "noopener",
141
+ className: creditClassName,
142
+ style: creditClassName ? void 0 : CREDIT_STYLE
143
+ }, "Analytics by Highseat"));
144
+ }
78
145
  //#endregion
79
- export { Highseat, useBeforeSend, useIdentify };
146
+ export { Embed, Highseat, useBeforeSend, useIdentify };
80
147
 
81
148
  //# sourceMappingURL=react.js.map
package/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport { syncIdentity } from \"./identity\"\nimport { inject, setBeforeSend, type BeforeSend, type InjectOptions, type UserId } from \"./index\"\n\nexport interface HighseatProps extends InjectOptions {\n /**\n * The signed-in user's id from your own system, never an email or other personal data. Pass\n * `null` once the visitor is signed out, and `undefined` while the session is still loading.\n * See {@link useIdentify}.\n */\n userId?: UserId\n}\n\n/**\n * Keeps the visitor's user id in step with a session. Each new value is passed to `identify`\n * once: an id identifies the visitor, `null` clears the id at sign-out or session expiry, and\n * `undefined`, a session still loading, changes nothing. Works in any client component, with or\n * without `<Highseat />` on the page, and before the tracker has loaded.\n *\n * Tell loading apart from signed out: `useIdentify(session?.user.id)` passes `undefined` after\n * sign-out too, so it never clears the id.\n *\n * ```tsx\n * const { data, isPending } = useSession()\n * useIdentify(isPending ? undefined : (data?.user.id ?? null))\n * ```\n */\nexport function useIdentify(userId: UserId): void {\n // The id this component identified last. A ref survives Strict Mode's second effect run, so\n // the same id is never sent twice.\n const identified = useRef<UserId>(undefined)\n\n useEffect(() => {\n identified.current = syncIdentity(identified.current, userId)\n }, [userId])\n}\n\n/**\n * Sets the {@link BeforeSend} hook while the component is mounted, and clears it on unmount.\n * `undefined` leaves the current hook alone. Use it in a client component when the tracker loads\n * from a server component, such as `<Highseat />` in a Next.js App Router layout, where a\n * function cannot be passed as a prop. Effects run before the tracker's script loads, so the\n * first page view already passes through the hook.\n *\n * ```tsx\n * \"use client\"\n * export function Redact() {\n * useBeforeSend(event => (event.url.includes(\"/admin/\") ? null : event))\n * return null\n * }\n * ```\n */\nexport function useBeforeSend(beforeSend: BeforeSend | undefined): void {\n useEffect(() => {\n if (!beforeSend) return\n setBeforeSend(beforeSend)\n return () => {\n // Another component may have set its own hook since.\n if (window.highseat?.beforeSend === beforeSend) setBeforeSend(undefined)\n }\n }, [beforeSend])\n}\n\n/**\n * Loads the Highseat tracker. Render it once, in the root layout. It adds the script tag after\n * the page mounts and renders nothing; mounting it again adds no second tag, and the tracker\n * counts client-side navigation on its own. With `userId`, it also identifies the visitor, as\n * {@link useIdentify} does, and with `beforeSend` it sets the hook, as {@link useBeforeSend} does.\n */\nexport function Highseat({\n site,\n src,\n outbound,\n downloads,\n hashRouting,\n beforeSend,\n debug,\n userId,\n}: HighseatProps): null {\n // Declared first, so its effect sets the hook before the next one adds the script tag.\n useBeforeSend(beforeSend)\n\n useEffect(() => {\n inject({ site, src, outbound, downloads, hashRouting, debug })\n }, [site, src, outbound, downloads, hashRouting, debug])\n\n useIdentify(userId)\n\n return null\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,YAAY,QAAsB;CAGhD,MAAM,aAAa,OAAe,KAAA,CAAS;CAE3C,gBAAgB;EACd,WAAW,UAAU,aAAa,WAAW,SAAS,MAAM;CAC9D,GAAG,CAAC,MAAM,CAAC;AACb;;;;;;;;;;;;;;;;AAiBA,SAAgB,cAAc,YAA0C;CACtE,gBAAgB;EACd,IAAI,CAAC,YAAY;EACjB,cAAc,UAAU;EACxB,aAAa;GAEX,IAAI,OAAO,UAAU,eAAe,YAAY,cAAc,KAAA,CAAS;EACzE;CACF,GAAG,CAAC,UAAU,CAAC;AACjB;;;;;;;AAQA,SAAgB,SAAS,EACvB,MACA,KACA,UACA,WACA,aACA,YACA,OACA,UACsB;CAEtB,cAAc,UAAU;CAExB,gBAAgB;EACd,OAAO;GAAE;GAAM;GAAK;GAAU;GAAW;GAAa;EAAM,CAAC;CAC/D,GAAG;EAAC;EAAM;EAAK;EAAU;EAAW;EAAa;CAAK,CAAC;CAEvD,YAAY,MAAM;CAElB,OAAO;AACT"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["\"use client\"\n\nimport {\n createElement,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type IframeHTMLAttributes,\n type ReactElement,\n} from \"react\"\nimport {\n EMBED_CREDIT_TEXT,\n EMBED_CREDIT_URL,\n EMBED_FALLBACK_HEIGHT,\n findEmbedHeight,\n formatEmbedSrc,\n getFrameColorScheme,\n WIDGET_FALLBACK_HEIGHTS,\n type EmbedBackground,\n type EmbedTheme,\n type EmbedWidget,\n} from \"./embed\"\nimport { syncIdentity } from \"./identity\"\nimport { inject, setBeforeSend, type BeforeSend, type InjectOptions, type UserId } from \"./index\"\n\nexport type { EmbedBackground, EmbedTheme, EmbedWidget } from \"./embed\"\n\nconst CREDIT_STYLE: CSSProperties = {\n display: \"block\",\n width: \"fit-content\",\n marginTop: 8,\n fontSize: 12,\n}\n\nfunction subscribeToNothing() {\n return () => {}\n}\n\n/**\n * Keeps the visitor's user id in step with a session. Each new value is passed to `identify`\n * once: an id identifies the visitor, `null` clears the id at sign-out or session expiry, and\n * `undefined`, a session still loading, changes nothing. Works in any client component, with or\n * without `<Highseat />` on the page, and before the tracker has loaded.\n *\n * Tell loading apart from signed out: `useIdentify(session?.user.id)` passes `undefined` after\n * sign-out too, so it never clears the id.\n *\n * ```tsx\n * const { data, isPending } = useSession()\n * useIdentify(isPending ? undefined : (data?.user.id ?? null))\n * ```\n */\nexport function useIdentify(userId: UserId): void {\n // The id this component identified last. A ref survives Strict Mode's second effect run, so\n // the same id is never sent twice.\n const identified = useRef<UserId>(undefined)\n\n useEffect(() => {\n identified.current = syncIdentity(identified.current, userId)\n }, [userId])\n}\n\n/**\n * Sets the {@link BeforeSend} hook while the component is mounted, and clears it on unmount.\n * `undefined` leaves the current hook alone. Use it in a client component when the tracker loads\n * from a server component, such as `<Highseat />` in a Next.js App Router layout, where a\n * function cannot be passed as a prop. Effects run before the tracker's script loads, so the\n * first page view already passes through the hook.\n *\n * ```tsx\n * \"use client\"\n * export function Redact() {\n * useBeforeSend(event => (event.url.includes(\"/admin/\") ? null : event))\n * return null\n * }\n * ```\n */\nexport function useBeforeSend(beforeSend: BeforeSend | undefined): void {\n useEffect(() => {\n if (!beforeSend) return\n setBeforeSend(beforeSend)\n return () => {\n // Another component may have set its own hook since.\n if (window.highseat?.beforeSend === beforeSend) setBeforeSend(undefined)\n }\n }, [beforeSend])\n}\n\nexport interface HighseatProps extends InjectOptions {\n /**\n * The signed-in user's id from your own system, never an email or other personal data. Pass\n * `null` once the visitor is signed out, and `undefined` while the session is still loading.\n * See {@link useIdentify}.\n */\n userId?: UserId\n}\n\n/**\n * Loads the Highseat tracker. Render it once, in the root layout. It adds the script tag after\n * the page mounts and renders nothing; mounting it again adds no second tag, and the tracker\n * counts client-side navigation on its own. With `userId`, it also identifies the visitor, as\n * {@link useIdentify} does, and with `beforeSend` it sets the hook, as {@link useBeforeSend} does.\n */\nexport function Highseat({\n site,\n src,\n outbound,\n downloads,\n hashRouting,\n beforeSend,\n debug,\n userId,\n}: HighseatProps): null {\n // Declared first, so its effect sets the hook before the next one adds the script tag.\n useBeforeSend(beforeSend)\n\n useEffect(() => {\n inject({ site, src, outbound, downloads, hashRouting, debug })\n }, [site, src, outbound, downloads, hashRouting, debug])\n\n useIdentify(userId)\n\n return null\n}\n\n/**\n * The props of `<Embed />`. Attributes it does not name, such as `id` or `loading`, go to the\n * frame, while `className` and `style` go to the box around the frame and the credit link.\n */\nexport interface EmbedProps extends Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n \"children\" | \"className\" | \"height\" | \"src\" | \"srcDoc\" | \"style\" | \"title\" | \"width\"\n> {\n /** The token of a share link or a widget, the last part of its embed code's `/share/` URL. */\n token: string\n /** The frame's accessible name, which a screen reader announces. */\n title: string\n /**\n * The embed's theme. Leave it out for `system`, which follows the reader's own setting. Pass\n * the page's theme when the page has its own switch, or the frame can disagree with the page.\n * Pass `null` while that theme is still unknown after mount: the slot stays empty until then,\n * so the frame loads once.\n */\n theme?: EmbedTheme | null\n /**\n * The embed's background. `transparent`, the default for a widget, lets your page's background\n * show through. `default`, the default for a whole board, keeps the board's own.\n */\n background?: EmbedBackground\n /** The kind of widget the token shows, which sets the height held before the frame loads. */\n widget?: EmbedWidget\n /** The height in pixels to hold until the embed reports its own. */\n height?: number\n /** Whether to show the \"Analytics by Highseat\" link under the frame. It is yours to remove. */\n credit?: boolean\n /** A class for the credit link. It replaces the link's default spacing and size. */\n creditClassName?: string\n /** A class for the box around the frame and the credit link. */\n className?: string\n /** A style for the box around the frame and the credit link. */\n style?: CSSProperties\n}\n\n/**\n * Shows a Highseat share link or widget in a frame that fits its content, with a followed credit\n * link under it. Needs no script tag: the component listens for the embed's height itself.\n *\n * The frame renders only after mount, so the page's theme can come from a client-only source such\n * as `next-themes` with no hydration mismatch. Until then, an empty box holds the frame's height,\n * and the credit link is already in the server's HTML.\n *\n * ```tsx\n * <Embed token=\"YOUR_WIDGET_TOKEN\" title=\"Visitors\" widget=\"chart\" theme=\"dark\" />\n * ```\n */\nexport function Embed({\n token,\n title,\n theme,\n background,\n widget,\n height,\n credit = true,\n creditClassName,\n className,\n style,\n ...frameProps\n}: EmbedProps): ReactElement {\n // React reads the last snapshot on the server and during hydration, so both render the slot.\n const isMounted = useSyncExternalStore(\n subscribeToNothing,\n () => true,\n () => false,\n )\n const [frame, setFrame] = useState<HTMLIFrameElement | null>(null)\n const [reportedHeight, setReportedHeight] = useState<number>()\n\n // The listener starts as the frame enters the page, before its page can load and post, so the\n // first height is heard and no `highseat:embed-request` is needed.\n useEffect(() => {\n if (!frame) return\n\n function onMessage(event: MessageEvent) {\n const nextHeight = findEmbedHeight(event, frame?.contentWindow)\n if (nextHeight !== undefined) setReportedHeight(nextHeight)\n }\n\n window.addEventListener(\"message\", onMessage)\n return () => window.removeEventListener(\"message\", onMessage)\n }, [frame])\n\n const look = {\n theme: theme ?? \"system\",\n background: background ?? (widget ? \"transparent\" : \"default\"),\n } as const\n const fallbackHeight = widget ? WIDGET_FALLBACK_HEIGHTS[widget] : EMBED_FALLBACK_HEIGHT\n const frameHeight = reportedHeight ?? height ?? fallbackHeight\n const src = formatEmbedSrc(token, look)\n\n return createElement(\n \"div\",\n { className, style },\n isMounted && theme !== null\n ? createElement(\"iframe\", {\n loading: \"lazy\",\n ...frameProps,\n // A new element per URL: changing `src` on a live frame would add a history entry, so\n // the host page's back button would step through the reader's theme changes.\n key: src,\n ref: setFrame,\n src,\n title,\n style: {\n display: \"block\",\n width: \"100%\",\n height: frameHeight,\n border: 0,\n // A widget's page is see-through around its card whatever the background.\n ...((look.background === \"transparent\" || widget) && {\n colorScheme: getFrameColorScheme(look.theme),\n }),\n },\n })\n : createElement(\"div\", { style: { height: frameHeight } }),\n credit &&\n createElement(\n \"a\",\n {\n href: EMBED_CREDIT_URL,\n target: \"_blank\",\n rel: \"noopener\",\n className: creditClassName,\n style: creditClassName ? undefined : CREDIT_STYLE,\n },\n EMBED_CREDIT_TEXT,\n ),\n )\n}\n"],"mappings":";;;;;;AA6BA,MAAM,eAA8B;CAClC,SAAS;CACT,OAAO;CACP,WAAW;CACX,UAAU;AACZ;AAEA,SAAS,qBAAqB;CAC5B,aAAa,CAAC;AAChB;;;;;;;;;;;;;;;AAgBA,SAAgB,YAAY,QAAsB;CAGhD,MAAM,aAAa,OAAe,KAAA,CAAS;CAE3C,gBAAgB;EACd,WAAW,UAAU,aAAa,WAAW,SAAS,MAAM;CAC9D,GAAG,CAAC,MAAM,CAAC;AACb;;;;;;;;;;;;;;;;AAiBA,SAAgB,cAAc,YAA0C;CACtE,gBAAgB;EACd,IAAI,CAAC,YAAY;EACjB,cAAc,UAAU;EACxB,aAAa;GAEX,IAAI,OAAO,UAAU,eAAe,YAAY,cAAc,KAAA,CAAS;EACzE;CACF,GAAG,CAAC,UAAU,CAAC;AACjB;;;;;;;AAiBA,SAAgB,SAAS,EACvB,MACA,KACA,UACA,WACA,aACA,YACA,OACA,UACsB;CAEtB,cAAc,UAAU;CAExB,gBAAgB;EACd,OAAO;GAAE;GAAM;GAAK;GAAU;GAAW;GAAa;EAAM,CAAC;CAC/D,GAAG;EAAC;EAAM;EAAK;EAAU;EAAW;EAAa;CAAK,CAAC;CAEvD,YAAY,MAAM;CAElB,OAAO;AACT;;;;;;;;;;;;;AAoDA,SAAgB,MAAM,EACpB,OACA,OACA,OACA,YACA,QACA,QACA,SAAS,MACT,iBACA,WACA,OACA,GAAG,cACwB;CAE3B,MAAM,YAAY,qBAChB,0BACM,YACA,KACR;CACA,MAAM,CAAC,OAAO,YAAY,SAAmC,IAAI;CACjE,MAAM,CAAC,gBAAgB,qBAAqB,SAAiB;CAI7D,gBAAgB;EACd,IAAI,CAAC,OAAO;EAEZ,SAAS,UAAU,OAAqB;GACtC,MAAM,aAAa,gBAAgB,OAAO,OAAO,aAAa;GAC9D,IAAI,eAAe,KAAA,GAAW,kBAAkB,UAAU;EAC5D;EAEA,OAAO,iBAAiB,WAAW,SAAS;EAC5C,aAAa,OAAO,oBAAoB,WAAW,SAAS;CAC9D,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,OAAO;EACX,OAAO,SAAS;EAChB,YAAY,eAAe,SAAS,gBAAgB;CACtD;CACA,MAAM,iBAAiB,SAAS,wBAAwB,UAAU;CAClE,MAAM,cAAc,kBAAkB,UAAU;CAChD,MAAM,MAAM,eAAe,OAAO,IAAI;CAEtC,OAAO,cACL,OACA;EAAE;EAAW;CAAM,GACnB,aAAa,UAAU,OACnB,cAAc,UAAU;EACtB,SAAS;EACT,GAAG;EAGH,KAAK;EACL,KAAK;EACL;EACA;EACA,OAAO;GACL,SAAS;GACT,OAAO;GACP,QAAQ;GACR,QAAQ;GAER,IAAK,KAAK,eAAe,iBAAiB,WAAW,EACnD,aAAa,oBAAoB,KAAK,KAAK,EAC7C;EACF;CACF,CAAC,IACD,cAAc,OAAO,EAAE,OAAO,EAAE,QAAQ,YAAY,EAAE,CAAC,GAC3D,UACE,cACE,KACA;EACE,MAAA;EACA,QAAQ;EACR,KAAK;EACL,WAAW;EACX,OAAO,kBAAkB,KAAA,IAAY;CACvC,GAAA,uBAEF,CACJ;AACF"}