@hunterchen/canvas 0.8.0 → 0.9.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.
Files changed (39) hide show
  1. package/dist/components/canvas/backgrounds.js +67 -38
  2. package/dist/components/canvas/backgrounds.js.map +1 -1
  3. package/dist/components/canvas/canvas.js +451 -387
  4. package/dist/components/canvas/canvas.js.map +1 -1
  5. package/dist/components/canvas/component.js +108 -174
  6. package/dist/components/canvas/component.js.map +1 -1
  7. package/dist/components/canvas/draggable.js +168 -151
  8. package/dist/components/canvas/draggable.js.map +1 -1
  9. package/dist/components/canvas/navbar/index.js +164 -142
  10. package/dist/components/canvas/navbar/index.js.map +1 -1
  11. package/dist/components/canvas/navbar/single-button.js +176 -149
  12. package/dist/components/canvas/navbar/single-button.js.map +1 -1
  13. package/dist/components/canvas/toolbar.js +121 -82
  14. package/dist/components/canvas/toolbar.js.map +1 -1
  15. package/dist/components/canvas/wrapper.js +127 -99
  16. package/dist/components/canvas/wrapper.js.map +1 -1
  17. package/dist/contexts/CanvasContext.js +25 -17
  18. package/dist/contexts/CanvasContext.js.map +1 -1
  19. package/dist/contexts/PerformanceContext.js +51 -50
  20. package/dist/contexts/PerformanceContext.js.map +1 -1
  21. package/dist/hooks/usePerformanceMode.js +36 -37
  22. package/dist/hooks/usePerformanceMode.js.map +1 -1
  23. package/dist/hooks/useWindowDimensions.js +22 -18
  24. package/dist/hooks/useWindowDimensions.js.map +1 -1
  25. package/dist/index.js +17 -21
  26. package/dist/lib/canvas.js +65 -72
  27. package/dist/lib/canvas.js.map +1 -1
  28. package/dist/lib/constants.js +78 -92
  29. package/dist/lib/constants.js.map +1 -1
  30. package/dist/lib/utils.js +10 -5
  31. package/dist/lib/utils.js.map +1 -1
  32. package/dist/utils/performance.js +18 -23
  33. package/dist/utils/performance.js.map +1 -1
  34. package/package.json +7 -21
  35. package/dist/components/canvas/offest.js +0 -12
  36. package/dist/components/canvas/offest.js.map +0 -1
  37. package/dist/index.js.map +0 -1
  38. package/dist/types/index.js +0 -6
  39. package/dist/types/index.js.map +0 -1
@@ -1,149 +1,171 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { useCanvasContext } from "../../../contexts/CanvasContext.js";
2
+ import { NAVBAR_DEBOUNCE_MS, RESPONSIVE_ZOOM_MAP } from "../../../lib/constants.js";
3
+ import { getScreenSizeEnum, getSectionPanCoordinates } from "../../../lib/canvas.js";
4
+ import useWindowDimensions_default from "../../../hooks/useWindowDimensions.js";
5
+ import { cn } from "../../../lib/utils.js";
6
+ import SingleButton from "./single-button.js";
7
+ import { usePerformanceMode } from "../../../hooks/usePerformanceMode.js";
2
8
  import { motion, useMotionValueEvent } from "framer-motion";
3
- import { useState, useRef, useEffect, useCallback, useMemo } from "react";
4
- import SingleButton from "./single-button";
5
- import { useCanvasContext } from "../../../contexts/CanvasContext";
6
- import useWindowDimensions from "../../../hooks/useWindowDimensions";
7
- import { usePerformanceMode } from "../../../hooks/usePerformanceMode";
8
- import { getScreenSizeEnum, getSectionPanCoordinates, } from "../../../lib/canvas";
9
- import { RESPONSIVE_ZOOM_MAP, NAVBAR_DEBOUNCE_MS, } from "../../../lib/constants";
10
- import { cn } from "../../../lib/utils";
9
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+
12
+ //#region src/components/canvas/navbar/index.tsx
11
13
  const positionStyles = {
12
- top: {
13
- top: "1rem",
14
- bottom: "auto",
15
- left: "50%",
16
- transform: "translateX(-50%)",
17
- },
18
- bottom: {
19
- bottom: "1rem",
20
- top: "auto",
21
- left: "50%",
22
- transform: "translateX(-50%)",
23
- },
24
- left: {
25
- left: "1rem",
26
- right: "auto",
27
- top: "50%",
28
- transform: "translateY(-50%)",
29
- },
30
- right: {
31
- right: "1rem",
32
- left: "auto",
33
- top: "50%",
34
- transform: "translateY(-50%)",
35
- },
14
+ top: {
15
+ top: "1rem",
16
+ bottom: "auto",
17
+ left: "50%",
18
+ transform: "translateX(-50%)"
19
+ },
20
+ bottom: {
21
+ bottom: "1rem",
22
+ top: "auto",
23
+ left: "50%",
24
+ transform: "translateX(-50%)"
25
+ },
26
+ left: {
27
+ left: "1rem",
28
+ right: "auto",
29
+ top: "50%",
30
+ transform: "translateY(-50%)"
31
+ },
32
+ right: {
33
+ right: "1rem",
34
+ left: "auto",
35
+ top: "50%",
36
+ transform: "translateY(-50%)"
37
+ }
36
38
  };
37
- // Responsive position adjustments (mobile vs desktop)
38
39
  const responsivePositionClasses = {
39
- top: "top-12 md:top-4",
40
- bottom: "bottom-12 md:bottom-4",
41
- left: "left-4",
42
- right: "right-4",
40
+ top: "top-12 md:top-4",
41
+ bottom: "bottom-12 md:bottom-4",
42
+ left: "left-4",
43
+ right: "right-4"
43
44
  };
44
- export default function Navbar({ panToOffset, onReset, items, config = {}, }) {
45
- const { x, y, scale, animationStage, setNextTargetSection } = useCanvasContext();
46
- const [expandedButton, setExpandedButton] = useState(null);
47
- const activePans = useRef(0);
48
- const panTimeout = useRef(null);
49
- // Debounce state
50
- const debounceBlocked = useRef(false);
51
- const debounceCooldownTimeout = useRef(null);
52
- const { height, width } = useWindowDimensions();
53
- const { mode } = usePerformanceMode();
54
- const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];
55
- // Derive debounce duration from performance mode
56
- const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
57
- // Extract config values
58
- const { display = "icons", position = "bottom", className, style, buttonConfig, tooltipConfig, gap, padding, } = config;
59
- const isVertical = position === "left" || position === "right";
60
- // Find the home section from items
61
- const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
62
- // Leading-edge debounce handler
63
- const handleDebouncedClick = useCallback((callback) => {
64
- if (debounceMs === 0) {
65
- callback();
66
- return;
67
- }
68
- if (debounceBlocked.current) {
69
- // We're in the cooldown window; ignore this click
70
- return;
71
- }
72
- // Enter cooldown and perform the click immediately
73
- debounceBlocked.current = true;
74
- callback();
75
- if (debounceCooldownTimeout.current) {
76
- clearTimeout(debounceCooldownTimeout.current);
77
- }
78
- debounceCooldownTimeout.current = setTimeout(() => {
79
- debounceBlocked.current = false;
80
- debounceCooldownTimeout.current = null;
81
- }, debounceMs);
82
- }, [debounceMs]);
83
- const updateExpandedButton = () => {
84
- // reset activePans if no movement has occurred recently
85
- if (panTimeout.current)
86
- clearTimeout(panTimeout.current);
87
- panTimeout.current = setTimeout(() => {
88
- activePans.current = 0;
89
- }, 500);
90
- if (activePans.current == 0)
91
- setExpandedButton(null);
92
- };
93
- useMotionValueEvent(x, "change", updateExpandedButton);
94
- useMotionValueEvent(y, "change", updateExpandedButton);
95
- useMotionValueEvent(scale, "change", updateExpandedButton);
96
- const handlePan = useCallback(function handlePan(item) {
97
- setExpandedButton(item.id);
98
- activePans.current++;
99
- // Predictive pre-render hint: mark the target section so its CanvasComponent can
100
- // render even before it comes fully into view.
101
- setNextTargetSection(item.id);
102
- if (item.isHome) {
103
- onReset();
104
- return;
105
- }
106
- const panCoords = getSectionPanCoordinates({
107
- windowDimensions: { width, height },
108
- coords: { x: item.x, y: item.y, width: item.width, height: item.height },
109
- targetZoom: defaultZoom,
110
- negative: true,
111
- });
112
- panToOffset(panCoords, () => {
113
- activePans.current--;
114
- }, defaultZoom);
115
- }, [panToOffset, onReset, width, height, defaultZoom, setNextTargetSection]);
116
- // Clean up timers on unmount and pan to home on animation complete
117
- useEffect(() => {
118
- if (animationStage < 2)
119
- return;
120
- if (homeItem) {
121
- handlePan(homeItem);
122
- }
123
- return () => {
124
- if (panTimeout.current)
125
- clearTimeout(panTimeout.current);
126
- if (debounceCooldownTimeout.current)
127
- clearTimeout(debounceCooldownTimeout.current);
128
- };
129
- }, [handlePan, animationStage, homeItem]);
130
- // Compute container styles (positioning only)
131
- const containerStyle = {
132
- position: "fixed",
133
- zIndex: 1000,
134
- pointerEvents: "auto",
135
- display: "flex",
136
- justifyContent: "center",
137
- alignItems: "center",
138
- ...positionStyles[position],
139
- };
140
- // Compute inner container styles (visual styling)
141
- const innerStyle = {
142
- ...(gap !== undefined && { gap: `${gap}px` }),
143
- ...(padding !== undefined && { padding: `${padding}px` }),
144
- ...(isVertical && { flexDirection: "column" }),
145
- ...style,
146
- };
147
- return (_jsx("div", { className: responsivePositionClasses[position], style: containerStyle, children: _jsx("div", { className: isVertical ? "py-4 md:py-8" : "px-4 md:px-8", children: _jsx(motion.div, { className: cn("flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]", !style?.backgroundColor && "bg-white", !style?.borderColor && "border-neutral-200", isVertical && "flex-col", className), style: innerStyle, children: _jsx("div", { className: cn("flex items-center gap-1", isVertical && "flex-col"), children: items.map((item) => (_jsx(SingleButton, { label: item.label, icon: item.icon, onClick: () => handlePan(item), isPushed: expandedButton === item.id, onDebouncedClick: handleDebouncedClick, displayMode: display, buttonConfig: buttonConfig, tooltipConfig: tooltipConfig, isVertical: isVertical }, item.id))) }) }) }) }));
45
+ function Navbar({ panToOffset, onReset, items, config = {} }) {
46
+ const { x, y, scale, animationStage, setNextTargetSection } = useCanvasContext();
47
+ const [expandedButton, setExpandedButton] = useState(null);
48
+ const activePans = useRef(0);
49
+ const panTimeout = useRef(null);
50
+ const debounceBlocked = useRef(false);
51
+ const debounceCooldownTimeout = useRef(null);
52
+ const { height, width } = useWindowDimensions_default();
53
+ const { mode } = usePerformanceMode();
54
+ const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];
55
+ const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
56
+ const { display = "icons", position = "bottom", className, style, buttonConfig, tooltipConfig, gap, padding } = config;
57
+ const isVertical = position === "left" || position === "right";
58
+ const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
59
+ const handleDebouncedClick = useCallback((callback) => {
60
+ if (debounceMs === 0) {
61
+ callback();
62
+ return;
63
+ }
64
+ if (debounceBlocked.current) return;
65
+ debounceBlocked.current = true;
66
+ callback();
67
+ if (debounceCooldownTimeout.current) clearTimeout(debounceCooldownTimeout.current);
68
+ debounceCooldownTimeout.current = setTimeout(() => {
69
+ debounceBlocked.current = false;
70
+ debounceCooldownTimeout.current = null;
71
+ }, debounceMs);
72
+ }, [debounceMs]);
73
+ const updateExpandedButton = () => {
74
+ if (panTimeout.current) clearTimeout(panTimeout.current);
75
+ panTimeout.current = setTimeout(() => {
76
+ activePans.current = 0;
77
+ }, 500);
78
+ if (activePans.current == 0) setExpandedButton(null);
79
+ };
80
+ useMotionValueEvent(x, "change", updateExpandedButton);
81
+ useMotionValueEvent(y, "change", updateExpandedButton);
82
+ useMotionValueEvent(scale, "change", updateExpandedButton);
83
+ const handlePan = useCallback(function handlePan(item) {
84
+ setExpandedButton(item.id);
85
+ activePans.current++;
86
+ setNextTargetSection(item.id);
87
+ if (item.isHome) {
88
+ onReset();
89
+ return;
90
+ }
91
+ panToOffset(getSectionPanCoordinates({
92
+ windowDimensions: {
93
+ width,
94
+ height
95
+ },
96
+ coords: {
97
+ x: item.x,
98
+ y: item.y,
99
+ width: item.width,
100
+ height: item.height
101
+ },
102
+ targetZoom: defaultZoom,
103
+ negative: true
104
+ }), () => {
105
+ activePans.current--;
106
+ }, defaultZoom);
107
+ }, [
108
+ panToOffset,
109
+ onReset,
110
+ width,
111
+ height,
112
+ defaultZoom,
113
+ setNextTargetSection
114
+ ]);
115
+ useEffect(() => {
116
+ if (animationStage < 2) return;
117
+ if (homeItem) handlePan(homeItem);
118
+ return () => {
119
+ if (panTimeout.current) clearTimeout(panTimeout.current);
120
+ if (debounceCooldownTimeout.current) clearTimeout(debounceCooldownTimeout.current);
121
+ };
122
+ }, [
123
+ handlePan,
124
+ animationStage,
125
+ homeItem
126
+ ]);
127
+ const containerStyle = {
128
+ position: "fixed",
129
+ zIndex: 1e3,
130
+ pointerEvents: "auto",
131
+ display: "flex",
132
+ justifyContent: "center",
133
+ alignItems: "center",
134
+ ...positionStyles[position]
135
+ };
136
+ const innerStyle = {
137
+ ...gap !== void 0 && { gap: `${gap}px` },
138
+ ...padding !== void 0 && { padding: `${padding}px` },
139
+ ...isVertical && { flexDirection: "column" },
140
+ ...style
141
+ };
142
+ return /* @__PURE__ */ jsx("div", {
143
+ className: responsivePositionClasses[position],
144
+ style: containerStyle,
145
+ children: /* @__PURE__ */ jsx("div", {
146
+ className: isVertical ? "py-4 md:py-8" : "px-4 md:px-8",
147
+ children: /* @__PURE__ */ jsx(motion.div, {
148
+ className: cn("flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]", !style?.backgroundColor && "bg-white", !style?.borderColor && "border-neutral-200", isVertical && "flex-col", className),
149
+ style: innerStyle,
150
+ children: /* @__PURE__ */ jsx("div", {
151
+ className: cn("flex items-center gap-1", isVertical && "flex-col"),
152
+ children: items.map((item) => /* @__PURE__ */ jsx(SingleButton, {
153
+ label: item.label,
154
+ icon: item.icon,
155
+ onClick: () => handlePan(item),
156
+ isPushed: expandedButton === item.id,
157
+ onDebouncedClick: handleDebouncedClick,
158
+ displayMode: display,
159
+ buttonConfig,
160
+ tooltipConfig,
161
+ isVertical
162
+ }, item.id))
163
+ })
164
+ })
165
+ })
166
+ });
148
167
  }
168
+
169
+ //#endregion
170
+ export { Navbar as default };
149
171
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/canvas/navbar/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["useWindowDimensions"],"sources":["../../../../src/components/canvas/navbar/index.tsx"],"sourcesContent":["import { motion, useMotionValueEvent } from \"framer-motion\";\nimport { useState, useRef, useEffect, useCallback, useMemo } from \"react\";\nimport SingleButton from \"./single-button\";\nimport type { NavItem, NavbarConfig, NavbarPosition } from \"../../../types\";\nimport { useCanvasContext } from \"../../../contexts/CanvasContext\";\nimport useWindowDimensions from \"../../../hooks/useWindowDimensions\";\nimport { usePerformanceMode } from \"../../../hooks/usePerformanceMode\";\nimport {\n getScreenSizeEnum,\n getSectionPanCoordinates,\n} from \"../../../lib/canvas\";\nimport {\n RESPONSIVE_ZOOM_MAP,\n NAVBAR_DEBOUNCE_MS,\n} from \"../../../lib/constants\";\nimport { cn } from \"../../../lib/utils\";\n\ninterface NavbarProps {\n panToOffset: (\n offset: { x: number; y: number },\n onComplete?: () => void,\n zoom?: number,\n ) => void;\n onReset: () => void;\n /** Array of navigation items defining sections, their icons, and coordinates */\n items: NavItem[];\n /** Navbar configuration options */\n config?: NavbarConfig;\n}\n\nconst positionStyles: Record<NavbarPosition, React.CSSProperties> = {\n top: {\n top: \"1rem\",\n bottom: \"auto\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n },\n bottom: {\n bottom: \"1rem\",\n top: \"auto\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n },\n left: {\n left: \"1rem\",\n right: \"auto\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n },\n right: {\n right: \"1rem\",\n left: \"auto\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n },\n};\n\n// Responsive position adjustments (mobile vs desktop)\nconst responsivePositionClasses: Record<NavbarPosition, string> = {\n top: \"top-12 md:top-4\",\n bottom: \"bottom-12 md:bottom-4\",\n left: \"left-4\",\n right: \"right-4\",\n};\n\nexport default function Navbar({\n panToOffset,\n onReset,\n items,\n config = {},\n}: NavbarProps) {\n const { x, y, scale, animationStage, setNextTargetSection } =\n useCanvasContext();\n const [expandedButton, setExpandedButton] = useState<string | null>(null);\n const activePans = useRef(0);\n const panTimeout = useRef<NodeJS.Timeout | null>(null);\n\n // Debounce state\n const debounceBlocked = useRef(false);\n const debounceCooldownTimeout = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n\n const { height, width } = useWindowDimensions();\n const { mode } = usePerformanceMode();\n\n const defaultZoom = RESPONSIVE_ZOOM_MAP[getScreenSizeEnum(width)];\n\n // Derive debounce duration from performance mode\n const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;\n\n // Extract config values\n const {\n display = \"icons\",\n position = \"bottom\",\n className,\n style,\n buttonConfig,\n tooltipConfig,\n gap,\n padding,\n } = config;\n\n const isVertical = position === \"left\" || position === \"right\";\n\n // Find the home section from items\n const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);\n\n // Leading-edge debounce handler\n const handleDebouncedClick = useCallback(\n (callback: () => void) => {\n if (debounceMs === 0) {\n callback();\n return;\n }\n\n if (debounceBlocked.current) {\n // We're in the cooldown window; ignore this click\n return;\n }\n\n // Enter cooldown and perform the click immediately\n debounceBlocked.current = true;\n callback();\n\n if (debounceCooldownTimeout.current) {\n clearTimeout(debounceCooldownTimeout.current);\n }\n\n debounceCooldownTimeout.current = setTimeout(() => {\n debounceBlocked.current = false;\n debounceCooldownTimeout.current = null;\n }, debounceMs);\n },\n [debounceMs],\n );\n\n const updateExpandedButton = () => {\n // reset activePans if no movement has occurred recently\n if (panTimeout.current) clearTimeout(panTimeout.current);\n panTimeout.current = setTimeout(() => {\n activePans.current = 0;\n }, 500);\n\n if (activePans.current == 0) setExpandedButton(null);\n };\n\n useMotionValueEvent(x, \"change\", updateExpandedButton);\n useMotionValueEvent(y, \"change\", updateExpandedButton);\n useMotionValueEvent(scale, \"change\", updateExpandedButton);\n\n const handlePan = useCallback(\n function handlePan(item: NavItem) {\n setExpandedButton(item.id);\n activePans.current++;\n\n // Predictive pre-render hint: mark the target section so its CanvasComponent can\n // render even before it comes fully into view.\n setNextTargetSection(item.id);\n\n if (item.isHome) {\n onReset();\n return;\n }\n\n const panCoords = getSectionPanCoordinates({\n windowDimensions: { width, height },\n coords: { x: item.x, y: item.y, width: item.width, height: item.height },\n targetZoom: defaultZoom,\n negative: true,\n });\n\n panToOffset(\n panCoords,\n () => {\n activePans.current--;\n },\n defaultZoom,\n );\n },\n [panToOffset, onReset, width, height, defaultZoom, setNextTargetSection],\n );\n\n // Clean up timers on unmount and pan to home on animation complete\n useEffect(() => {\n if (animationStage < 2) return;\n if (homeItem) {\n handlePan(homeItem);\n }\n return () => {\n if (panTimeout.current) clearTimeout(panTimeout.current);\n if (debounceCooldownTimeout.current)\n clearTimeout(debounceCooldownTimeout.current);\n };\n }, [handlePan, animationStage, homeItem]);\n\n // Compute container styles (positioning only)\n const containerStyle: React.CSSProperties = {\n position: \"fixed\",\n zIndex: 1000,\n pointerEvents: \"auto\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n ...positionStyles[position],\n };\n\n // Compute inner container styles (visual styling)\n const innerStyle: React.CSSProperties = {\n ...(gap !== undefined && { gap: `${gap}px` }),\n ...(padding !== undefined && { padding: `${padding}px` }),\n ...(isVertical && { flexDirection: \"column\" }),\n ...style,\n };\n\n return (\n <div\n className={responsivePositionClasses[position]}\n style={containerStyle}\n >\n {/* padding to prevent edge bug */}\n <div className={isVertical ? \"py-4 md:py-8\" : \"px-4 md:px-8\"}>\n <motion.div\n className={cn(\n \"flex select-none items-center justify-center gap-1 rounded-[10px] border p-1 shadow-[0_6px_12px_rgba(0,0,0,0.08)]\",\n !style?.backgroundColor && \"bg-white\",\n !style?.borderColor && \"border-neutral-200\",\n isVertical && \"flex-col\",\n className,\n )}\n style={innerStyle}\n >\n <div className={cn(\"flex items-center gap-1\", isVertical && \"flex-col\")}>\n {items.map((item) => (\n <SingleButton\n key={item.id}\n label={item.label}\n icon={item.icon}\n onClick={() => handlePan(item)}\n isPushed={expandedButton === item.id}\n onDebouncedClick={handleDebouncedClick}\n displayMode={display}\n buttonConfig={buttonConfig}\n tooltipConfig={tooltipConfig}\n isVertical={isVertical}\n />\n ))}\n </div>\n </motion.div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,MAAM,iBAA8D;CAClE,KAAK;EACH,KAAK;EACL,QAAQ;EACR,MAAM;EACN,WAAW;EACZ;CACD,QAAQ;EACN,QAAQ;EACR,KAAK;EACL,MAAM;EACN,WAAW;EACZ;CACD,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,WAAW;EACZ;CACD,OAAO;EACL,OAAO;EACP,MAAM;EACN,KAAK;EACL,WAAW;EACZ;CACF;AAGD,MAAM,4BAA4D;CAChE,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACR;AAED,SAAwB,OAAO,EAC7B,aACA,SACA,OACA,SAAS,EAAE,IACG;CACd,MAAM,EAAE,GAAG,GAAG,OAAO,gBAAgB,yBACnC,kBAAkB;CACpB,MAAM,CAAC,gBAAgB,qBAAqB,SAAwB,KAAK;CACzE,MAAM,aAAa,OAAO,EAAE;CAC5B,MAAM,aAAa,OAA8B,KAAK;CAGtD,MAAM,kBAAkB,OAAO,MAAM;CACrC,MAAM,0BAA0B,OAC9B,KACD;CAED,MAAM,EAAE,QAAQ,UAAUA,6BAAqB;CAC/C,MAAM,EAAE,SAAS,oBAAoB;CAErC,MAAM,cAAc,oBAAoB,kBAAkB,MAAM;CAGhE,MAAM,aAAa,mBAAmB,SAAS;CAG/C,MAAM,EACJ,UAAU,SACV,WAAW,UACX,WACA,OACA,cACA,eACA,KACA,YACE;CAEJ,MAAM,aAAa,aAAa,UAAU,aAAa;CAGvD,MAAM,WAAW,cAAc,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE,CAAC,MAAM,CAAC;CAG1E,MAAM,uBAAuB,aAC1B,aAAyB;AACxB,MAAI,eAAe,GAAG;AACpB,aAAU;AACV;;AAGF,MAAI,gBAAgB,QAElB;AAIF,kBAAgB,UAAU;AAC1B,YAAU;AAEV,MAAI,wBAAwB,QAC1B,cAAa,wBAAwB,QAAQ;AAG/C,0BAAwB,UAAU,iBAAiB;AACjD,mBAAgB,UAAU;AAC1B,2BAAwB,UAAU;KACjC,WAAW;IAEhB,CAAC,WAAW,CACb;CAED,MAAM,6BAA6B;AAEjC,MAAI,WAAW,QAAS,cAAa,WAAW,QAAQ;AACxD,aAAW,UAAU,iBAAiB;AACpC,cAAW,UAAU;KACpB,IAAI;AAEP,MAAI,WAAW,WAAW,EAAG,mBAAkB,KAAK;;AAGtD,qBAAoB,GAAG,UAAU,qBAAqB;AACtD,qBAAoB,GAAG,UAAU,qBAAqB;AACtD,qBAAoB,OAAO,UAAU,qBAAqB;CAE1D,MAAM,YAAY,YAChB,SAAS,UAAU,MAAe;AAChC,oBAAkB,KAAK,GAAG;AAC1B,aAAW;AAIX,uBAAqB,KAAK,GAAG;AAE7B,MAAI,KAAK,QAAQ;AACf,YAAS;AACT;;AAUF,cAPkB,yBAAyB;GACzC,kBAAkB;IAAE;IAAO;IAAQ;GACnC,QAAQ;IAAE,GAAG,KAAK;IAAG,GAAG,KAAK;IAAG,OAAO,KAAK;IAAO,QAAQ,KAAK;IAAQ;GACxE,YAAY;GACZ,UAAU;GACX,CAAC,QAIM;AACJ,cAAW;KAEb,YACD;IAEH;EAAC;EAAa;EAAS;EAAO;EAAQ;EAAa;EAAqB,CACzE;AAGD,iBAAgB;AACd,MAAI,iBAAiB,EAAG;AACxB,MAAI,SACF,WAAU,SAAS;AAErB,eAAa;AACX,OAAI,WAAW,QAAS,cAAa,WAAW,QAAQ;AACxD,OAAI,wBAAwB,QAC1B,cAAa,wBAAwB,QAAQ;;IAEhD;EAAC;EAAW;EAAgB;EAAS,CAAC;CAGzC,MAAM,iBAAsC;EAC1C,UAAU;EACV,QAAQ;EACR,eAAe;EACf,SAAS;EACT,gBAAgB;EAChB,YAAY;EACZ,GAAG,eAAe;EACnB;CAGD,MAAM,aAAkC;EACtC,GAAI,QAAQ,UAAa,EAAE,KAAK,GAAG,IAAI,KAAK;EAC5C,GAAI,YAAY,UAAa,EAAE,SAAS,GAAG,QAAQ,KAAK;EACxD,GAAI,cAAc,EAAE,eAAe,UAAU;EAC7C,GAAG;EACJ;AAED,QACE,oBAAC;EACC,WAAW,0BAA0B;EACrC,OAAO;YAGP,oBAAC;GAAI,WAAW,aAAa,iBAAiB;aAC5C,oBAAC,OAAO;IACN,WAAW,GACT,qHACA,CAAC,OAAO,mBAAmB,YAC3B,CAAC,OAAO,eAAe,sBACvB,cAAc,YACd,UACD;IACD,OAAO;cAEP,oBAAC;KAAI,WAAW,GAAG,2BAA2B,cAAc,WAAW;eACpE,MAAM,KAAK,SACV,oBAAC;MAEC,OAAO,KAAK;MACZ,MAAM,KAAK;MACX,eAAe,UAAU,KAAK;MAC9B,UAAU,mBAAmB,KAAK;MAClC,kBAAkB;MAClB,aAAa;MACC;MACC;MACH;QATP,KAAK,GAUV,CACF;MACE;KACK;IACT;GACF"}