@steez-ui/ui 0.1.5 → 0.1.7

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 (51) hide show
  1. package/README.md +3 -3
  2. package/dist/components/LoadingOverlayCrystalline.d.ts +10 -0
  3. package/dist/components/LoadingOverlayCrystalline.d.ts.map +1 -0
  4. package/dist/components/LoadingOverlayCrystalline.js +7 -0
  5. package/dist/components/LoadingOverlayCrystalline.js.map +1 -0
  6. package/dist/components/LoadingOverlayCrystalline.module.css +39 -0
  7. package/dist/components/LoadingScreen.d.ts +17 -0
  8. package/dist/components/LoadingScreen.d.ts.map +1 -0
  9. package/dist/components/LoadingScreen.js +246 -0
  10. package/dist/components/LoadingScreen.js.map +1 -0
  11. package/dist/components/LoadingScreen.module.css +108 -0
  12. package/dist/components/OverlayButton.d.ts +7 -0
  13. package/dist/components/OverlayButton.d.ts.map +1 -0
  14. package/dist/components/OverlayButton.js +8 -0
  15. package/dist/components/OverlayButton.js.map +1 -0
  16. package/dist/components/OverlayButton.module.css +32 -0
  17. package/dist/components/PixelTooltip.d.ts +10 -0
  18. package/dist/components/PixelTooltip.d.ts.map +1 -0
  19. package/dist/components/PixelTooltip.js +41 -0
  20. package/dist/components/PixelTooltip.js.map +1 -0
  21. package/dist/components/PixelTooltip.module.css +81 -0
  22. package/dist/components/QuickInfoCard.d.ts +20 -0
  23. package/dist/components/QuickInfoCard.d.ts.map +1 -0
  24. package/dist/components/QuickInfoCard.js +38 -0
  25. package/dist/components/QuickInfoCard.js.map +1 -0
  26. package/dist/components/QuickInfoCard.module.css +138 -0
  27. package/dist/components/Section.d.ts +9 -0
  28. package/dist/components/Section.d.ts.map +1 -0
  29. package/dist/components/Section.js +7 -0
  30. package/dist/components/Section.js.map +1 -0
  31. package/dist/components/Section.module.css +20 -0
  32. package/dist/components/SectionHeader.d.ts +10 -0
  33. package/dist/components/SectionHeader.d.ts.map +1 -0
  34. package/dist/components/SectionHeader.js +7 -0
  35. package/dist/components/SectionHeader.js.map +1 -0
  36. package/dist/components/SectionHeader.module.css +54 -0
  37. package/dist/components/StatCard.d.ts +11 -0
  38. package/dist/components/StatCard.d.ts.map +1 -0
  39. package/dist/components/StatCard.js +14 -0
  40. package/dist/components/StatCard.js.map +1 -0
  41. package/dist/components/StatCard.module.css +41 -0
  42. package/dist/components/WidgetCard.d.ts +12 -0
  43. package/dist/components/WidgetCard.d.ts.map +1 -0
  44. package/dist/components/WidgetCard.js +17 -0
  45. package/dist/components/WidgetCard.js.map +1 -0
  46. package/dist/components/WidgetCard.module.css +75 -0
  47. package/dist/index.d.ts +9 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +9 -0
  50. package/dist/index.js.map +1 -1
  51. package/package.json +3 -3
package/README.md CHANGED
@@ -101,9 +101,9 @@ GitHub Actions mirror the same CI, npm publish, and Cloudflare Pages deploy path
101
101
 
102
102
  ## Current Packages
103
103
 
104
- - `@steez-ui/theme@0.1.5`
105
- - `@steez-ui/icons@0.1.5`
106
- - `@steez-ui/ui@0.1.5`
104
+ - `@steez-ui/theme@0.1.7`
105
+ - `@steez-ui/icons@0.1.7`
106
+ - `@steez-ui/ui@0.1.7`
107
107
 
108
108
  ## External Setup
109
109
 
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export interface LoadingOverlayCrystallineProps {
3
+ message: string;
4
+ subtext?: string;
5
+ icon?: React.ReactNode;
6
+ showEffect?: boolean;
7
+ }
8
+ export declare function LoadingOverlayCrystalline({ message, subtext, icon, showEffect, }: LoadingOverlayCrystallineProps): import("react/jsx-runtime").JSX.Element;
9
+ export default LoadingOverlayCrystalline;
10
+ //# sourceMappingURL=LoadingOverlayCrystalline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingOverlayCrystalline.d.ts","sourceRoot":"","sources":["../../src/components/LoadingOverlayCrystalline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,yBAAyB,CAAC,EACxC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,UAAkB,GACnB,EAAE,8BAA8B,2CAYhC;AAED,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styles from "./LoadingOverlayCrystalline.module.css";
3
+ export function LoadingOverlayCrystalline({ message, subtext, icon, showEffect = false, }) {
4
+ return (_jsx("div", { className: `${styles.root} ${showEffect ? "" : styles.noEffect}`.trim(), children: _jsxs("div", { className: styles.card, children: [icon ? _jsx("span", { className: styles.icon, children: icon }) : null, _jsxs("div", { children: [_jsx("div", { children: message }), subtext ? _jsx("div", { className: styles.subtext, children: subtext }) : null] })] }) }));
5
+ }
6
+ export default LoadingOverlayCrystalline;
7
+ //# sourceMappingURL=LoadingOverlayCrystalline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingOverlayCrystalline.js","sourceRoot":"","sources":["../../src/components/LoadingOverlayCrystalline.tsx"],"names":[],"mappings":";AAEA,OAAO,MAAM,MAAM,wCAAwC,CAAC;AAS5D,MAAM,UAAU,yBAAyB,CAAC,EACxC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,UAAU,GAAG,KAAK,GACa;IAC/B,OAAO,CACL,cAAK,SAAS,EAAE,GAAG,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAC1E,eAAK,SAAS,EAAE,MAAM,CAAC,IAAI,aACxB,IAAI,CAAC,CAAC,CAAC,eAAM,SAAS,EAAE,MAAM,CAAC,IAAI,YAAG,IAAI,GAAQ,CAAC,CAAC,CAAC,IAAI,EAC1D,0BACE,wBAAM,OAAO,GAAO,EACnB,OAAO,CAAC,CAAC,CAAC,cAAK,SAAS,EAAE,MAAM,CAAC,OAAO,YAAG,OAAO,GAAO,CAAC,CAAC,CAAC,IAAI,IAC7D,IACF,GACF,CACP,CAAC;AACJ,CAAC;AAED,eAAe,yBAAyB,CAAC","sourcesContent":["import React from \"react\";\n\nimport styles from \"./LoadingOverlayCrystalline.module.css\";\n\nexport interface LoadingOverlayCrystallineProps {\n message: string;\n subtext?: string;\n icon?: React.ReactNode;\n showEffect?: boolean;\n}\n\nexport function LoadingOverlayCrystalline({\n message,\n subtext,\n icon,\n showEffect = false,\n}: LoadingOverlayCrystallineProps) {\n return (\n <div className={`${styles.root} ${showEffect ? \"\" : styles.noEffect}`.trim()}>\n <div className={styles.card}>\n {icon ? <span className={styles.icon}>{icon}</span> : null}\n <div>\n <div>{message}</div>\n {subtext ? <div className={styles.subtext}>{subtext}</div> : null}\n </div>\n </div>\n </div>\n );\n}\n\nexport default LoadingOverlayCrystalline;\n"]}
@@ -0,0 +1,39 @@
1
+ .root {
2
+ position: absolute;
3
+ inset: 0;
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ z-index: var(--z-viewer-quick-tools);
8
+ background: transparent;
9
+ }
10
+
11
+ .noEffect {
12
+ background: transparent;
13
+ }
14
+
15
+ .card {
16
+ position: relative;
17
+ z-index: 20;
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 10px;
21
+ padding: 14px 16px;
22
+ background: color-mix(in srgb, var(--bg-primary) 85%, transparent);
23
+ border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
24
+ border-radius: 10px;
25
+ color: var(--text-primary);
26
+ font-size: 14px;
27
+ font-weight: 600;
28
+ }
29
+
30
+ .icon {
31
+ opacity: 0.9;
32
+ }
33
+
34
+ .subtext {
35
+ margin-top: 2px;
36
+ color: color-mix(in srgb, var(--text-secondary) 92%, transparent);
37
+ font-size: 12px;
38
+ font-weight: 500;
39
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ export interface LoadingScreenProps {
3
+ progress: number;
4
+ message?: string;
5
+ logo?: React.ReactNode;
6
+ title?: React.ReactNode;
7
+ audioFeedback?: boolean;
8
+ fullscreen?: boolean;
9
+ themeMode?: "auto" | "light" | "dark";
10
+ }
11
+ export declare function LoadingScreen({ progress, message, logo, title, audioFeedback, fullscreen, themeMode, }: LoadingScreenProps): import("react/jsx-runtime").JSX.Element;
12
+ export type LoadingStage = "init" | "auth" | "agent" | "config" | "ready";
13
+ export declare function useLoadingProgress(isLoading: boolean, _phase?: string, stage?: LoadingStage): {
14
+ progress: number;
15
+ message: string;
16
+ };
17
+ //# sourceMappingURL=LoadingScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingScreen.d.ts","sourceRoot":"","sources":["../../src/components/LoadingScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACvC;AAmCD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,KAAiB,EACjB,aAAqB,EACrB,UAAiB,EACjB,SAAkB,GACnB,EAAE,kBAAkB,2CAmPpB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAkB1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,OAAO,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,GAAE,YAAqB;;;EAgD7B"}
@@ -0,0 +1,246 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { LightCrossIcon } from "@steez-ui/icons";
4
+ import { LoadingProgressBar, LOADING_PROGRESS_SEGMENT_COUNT, } from "./LoadingProgressBar.js";
5
+ import styles from "./LoadingScreen.module.css";
6
+ function randomFloat() {
7
+ return Math.random();
8
+ }
9
+ function resolveIsLightMode(themeMode) {
10
+ if (themeMode === "light") {
11
+ return true;
12
+ }
13
+ if (themeMode === "dark") {
14
+ return false;
15
+ }
16
+ if (typeof window === "undefined") {
17
+ return false;
18
+ }
19
+ return (document.documentElement.getAttribute("data-theme") === "light" ||
20
+ window.localStorage.getItem("theme") === "light");
21
+ }
22
+ export function LoadingScreen({ progress, message, logo, title = "LOADING", audioFeedback = false, fullscreen = true, themeMode = "auto", }) {
23
+ const [displayProgress, setDisplayProgress] = React.useState(0);
24
+ const [ellipsisCount, setEllipsisCount] = React.useState(0);
25
+ const [crosses, setCrosses] = React.useState([]);
26
+ const [bounds, setBounds] = React.useState({ width: 0, height: 0 });
27
+ const prevFilledBars = React.useRef(0);
28
+ const crossIdRef = React.useRef(0);
29
+ const isLoadingRef = React.useRef(true);
30
+ const audioCtxRef = React.useRef(null);
31
+ const audioEnabledRef = React.useRef(false);
32
+ const containerRef = React.useRef(null);
33
+ const updateBounds = React.useCallback(() => {
34
+ const nextWidth = containerRef.current?.clientWidth || window.innerWidth || 0;
35
+ const nextHeight = containerRef.current?.clientHeight || window.innerHeight || 0;
36
+ setBounds((current) => {
37
+ if (current.width === nextWidth && current.height === nextHeight) {
38
+ return current;
39
+ }
40
+ return {
41
+ width: nextWidth,
42
+ height: nextHeight,
43
+ };
44
+ });
45
+ }, []);
46
+ const spawnCross = React.useCallback(() => {
47
+ if (!isLoadingRef.current || typeof window === "undefined") {
48
+ return;
49
+ }
50
+ const tilt = (randomFloat() - 0.5) * 6;
51
+ const availableWidth = Math.max(bounds.width || window.innerWidth, 160);
52
+ const nextCross = {
53
+ id: crossIdRef.current += 1,
54
+ x: randomFloat() * Math.max(availableWidth - 120, 24),
55
+ rotation: tilt,
56
+ scale: 0.4 + randomFloat() * 0.4,
57
+ groundOffset: randomFloat() * 30,
58
+ delay: 0,
59
+ opacity: 0.4 + randomFloat() * 0.3,
60
+ };
61
+ setCrosses((current) => [...current.slice(-17), nextCross]);
62
+ }, [bounds.width]);
63
+ React.useEffect(() => {
64
+ if (typeof window === "undefined") {
65
+ return undefined;
66
+ }
67
+ updateBounds();
68
+ window.addEventListener("resize", updateBounds);
69
+ const observer = typeof ResizeObserver === "undefined" || !containerRef.current
70
+ ? null
71
+ : new ResizeObserver(() => {
72
+ updateBounds();
73
+ });
74
+ if (observer && containerRef.current) {
75
+ observer.observe(containerRef.current);
76
+ }
77
+ return () => {
78
+ window.removeEventListener("resize", updateBounds);
79
+ observer?.disconnect();
80
+ };
81
+ }, [updateBounds]);
82
+ React.useEffect(() => {
83
+ isLoadingRef.current = true;
84
+ const scheduleNextCross = () => {
85
+ const delay = 400 + randomFloat() * 300;
86
+ return setTimeout(() => {
87
+ spawnCross();
88
+ if (isLoadingRef.current) {
89
+ scheduleNextCross();
90
+ }
91
+ }, delay);
92
+ };
93
+ const timeoutId = scheduleNextCross();
94
+ return () => {
95
+ clearTimeout(timeoutId);
96
+ isLoadingRef.current = false;
97
+ };
98
+ }, [spawnCross]);
99
+ React.useEffect(() => {
100
+ if (!audioFeedback) {
101
+ return undefined;
102
+ }
103
+ const enableAudio = async () => {
104
+ const AudioContextClass = window.AudioContext || window.webkitAudioContext;
105
+ if (!AudioContextClass) {
106
+ return;
107
+ }
108
+ try {
109
+ const ctx = audioCtxRef.current ?? new AudioContextClass();
110
+ audioCtxRef.current = ctx;
111
+ if (ctx.state === "suspended") {
112
+ await ctx.resume();
113
+ }
114
+ audioEnabledRef.current = ctx.state === "running";
115
+ }
116
+ catch {
117
+ audioEnabledRef.current = false;
118
+ }
119
+ };
120
+ window.addEventListener("pointerdown", enableAudio, {
121
+ once: true,
122
+ passive: true,
123
+ });
124
+ window.addEventListener("keydown", enableAudio, { once: true });
125
+ return () => {
126
+ window.removeEventListener("pointerdown", enableAudio);
127
+ window.removeEventListener("keydown", enableAudio);
128
+ if (audioCtxRef.current) {
129
+ void audioCtxRef.current.close().catch(() => undefined);
130
+ audioCtxRef.current = null;
131
+ }
132
+ audioEnabledRef.current = false;
133
+ };
134
+ }, [audioFeedback]);
135
+ React.useEffect(() => {
136
+ if (!audioFeedback) {
137
+ return undefined;
138
+ }
139
+ const playBeep = () => {
140
+ const audioCtx = audioCtxRef.current;
141
+ if (!audioCtx || !audioEnabledRef.current) {
142
+ return;
143
+ }
144
+ const osc = audioCtx.createOscillator();
145
+ const gain = audioCtx.createGain();
146
+ osc.type = "triangle";
147
+ osc.frequency.value = 523.25;
148
+ gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
149
+ gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.08);
150
+ osc.connect(gain);
151
+ gain.connect(audioCtx.destination);
152
+ osc.start();
153
+ osc.stop(audioCtx.currentTime + 0.08);
154
+ };
155
+ const filledBars = Math.round((displayProgress / 100) * LOADING_PROGRESS_SEGMENT_COUNT);
156
+ if (filledBars > prevFilledBars.current) {
157
+ playBeep();
158
+ prevFilledBars.current = filledBars;
159
+ }
160
+ return undefined;
161
+ }, [audioFeedback, displayProgress]);
162
+ React.useEffect(() => {
163
+ const ellipsisInterval = setInterval(() => {
164
+ setEllipsisCount((count) => (count + 1) % 4);
165
+ }, 400);
166
+ return () => clearInterval(ellipsisInterval);
167
+ }, []);
168
+ React.useEffect(() => {
169
+ const timeout = setTimeout(() => {
170
+ setDisplayProgress(progress);
171
+ }, 50);
172
+ return () => clearTimeout(timeout);
173
+ }, [progress]);
174
+ const ellipsis = ".".repeat(ellipsisCount);
175
+ const cleanMessage = message ? message.replace(/\.+$/, "") : "";
176
+ const isLightMode = resolveIsLightMode(themeMode);
177
+ const screenWidth = bounds.width || (typeof window === "undefined" ? 0 : window.innerWidth);
178
+ const screenHeight = bounds.height || (typeof window === "undefined" ? 0 : window.innerHeight);
179
+ return (_jsxs("div", { ref: containerRef, className: `${styles.screen} ${fullscreen ? styles.fullscreen : styles.contained} ${isLightMode ? styles.lightTheme : styles.darkTheme}`.trim(), style: {
180
+ "--screen-height": `${screenHeight}px`,
181
+ "--screen-width": `${screenWidth}px`,
182
+ }, children: [crosses.map((cross) => (_jsx("div", { className: styles.crossContainer, style: {
183
+ "--cross-left": `${cross.x}px`,
184
+ "--cross-delay": `${cross.delay}ms`,
185
+ "--rotation": `${cross.rotation}deg`,
186
+ "--ground": `${cross.groundOffset}%`,
187
+ "--cross-scale": String(cross.scale),
188
+ "--cross-opacity": String(cross.opacity),
189
+ }, onAnimationEnd: (event) => {
190
+ if (event.animationName === "crossFall") {
191
+ event.currentTarget.classList.add(styles.crossLanded);
192
+ }
193
+ }, children: _jsx("div", { className: styles.crossSvg, children: _jsx(LightCrossIcon, { className: styles.crossShape }) }) }, cross.id))), _jsxs("div", { className: styles.centerStack, children: [logo ? _jsx("div", { className: styles.logoWrap, children: logo }) : null, _jsx("div", { className: styles.title, children: title })] }), _jsx(LoadingProgressBar, { progress: displayProgress, className: styles.progressRow }), _jsxs("div", { className: styles.footerMessage, children: [cleanMessage, ellipsis] })] }));
194
+ }
195
+ const STAGE_PROGRESS = {
196
+ init: 0,
197
+ auth: 25,
198
+ agent: 50,
199
+ config: 75,
200
+ ready: 100,
201
+ };
202
+ const STAGE_MESSAGES = {
203
+ init: "Initializing...",
204
+ auth: "Authenticating...",
205
+ agent: "Loading agent...",
206
+ config: "Loading configuration...",
207
+ ready: "Preparing interface...",
208
+ };
209
+ export function useLoadingProgress(isLoading, _phase, stage = "init") {
210
+ const [progress, setProgress] = React.useState(0);
211
+ const [currentMessage, setCurrentMessage] = React.useState("Initializing...");
212
+ const progressRef = React.useRef(0);
213
+ React.useEffect(() => {
214
+ progressRef.current = progress;
215
+ }, [progress]);
216
+ React.useEffect(() => {
217
+ if (!isLoading) {
218
+ setProgress(100);
219
+ progressRef.current = 100;
220
+ setCurrentMessage("Complete!");
221
+ return undefined;
222
+ }
223
+ const targetProgress = STAGE_PROGRESS[stage];
224
+ const targetMessage = STAGE_MESSAGES[stage];
225
+ setCurrentMessage(targetMessage);
226
+ const currentProgressSnapshot = progressRef.current;
227
+ const diff = targetProgress - currentProgressSnapshot;
228
+ const steps = 20;
229
+ const stepDuration = 100;
230
+ let step = 0;
231
+ const interval = setInterval(() => {
232
+ step += 1;
233
+ const easedStep = step / steps;
234
+ const easedProgress = diff * easedStep;
235
+ const nextProgress = Math.min(currentProgressSnapshot + easedProgress, targetProgress);
236
+ progressRef.current = nextProgress;
237
+ setProgress(nextProgress);
238
+ if (step >= steps) {
239
+ clearInterval(interval);
240
+ }
241
+ }, stepDuration);
242
+ return () => clearInterval(interval);
243
+ }, [isLoading, stage]);
244
+ return { progress, message: currentMessage };
245
+ }
246
+ //# sourceMappingURL=LoadingScreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingScreen.js","sourceRoot":"","sources":["../../src/components/LoadingScreen.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from \"react\";\n\nimport { LightCrossIcon } from \"@steez-ui/icons\";\n\nimport {\n LoadingProgressBar,\n LOADING_PROGRESS_SEGMENT_COUNT,\n} from \"./LoadingProgressBar.js\";\nimport styles from \"./LoadingScreen.module.css\";\n\nexport interface LoadingScreenProps {\n progress: number;\n message?: string;\n logo?: React.ReactNode;\n title?: React.ReactNode;\n audioFeedback?: boolean;\n fullscreen?: boolean;\n themeMode?: \"auto\" | \"light\" | \"dark\";\n}\n\ninterface Cross {\n id: number;\n x: number;\n rotation: number;\n scale: number;\n groundOffset: number;\n delay: number;\n opacity: number;\n}\n\nfunction randomFloat() {\n return Math.random();\n}\n\nfunction resolveIsLightMode(themeMode: LoadingScreenProps[\"themeMode\"]) {\n if (themeMode === \"light\") {\n return true;\n }\n\n if (themeMode === \"dark\") {\n return false;\n }\n\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return (\n document.documentElement.getAttribute(\"data-theme\") === \"light\" ||\n window.localStorage.getItem(\"theme\") === \"light\"\n );\n}\n\nexport function LoadingScreen({\n progress,\n message,\n logo,\n title = \"LOADING\",\n audioFeedback = false,\n fullscreen = true,\n themeMode = \"auto\",\n}: LoadingScreenProps) {\n const [displayProgress, setDisplayProgress] = React.useState(0);\n const [ellipsisCount, setEllipsisCount] = React.useState(0);\n const [crosses, setCrosses] = React.useState<Cross[]>([]);\n const [bounds, setBounds] = React.useState({ width: 0, height: 0 });\n const prevFilledBars = React.useRef(0);\n const crossIdRef = React.useRef(0);\n const isLoadingRef = React.useRef(true);\n const audioCtxRef = React.useRef<AudioContext | null>(null);\n const audioEnabledRef = React.useRef(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n const updateBounds = React.useCallback(() => {\n const nextWidth =\n containerRef.current?.clientWidth || window.innerWidth || 0;\n const nextHeight =\n containerRef.current?.clientHeight || window.innerHeight || 0;\n\n setBounds((current) => {\n if (current.width === nextWidth && current.height === nextHeight) {\n return current;\n }\n\n return {\n width: nextWidth,\n height: nextHeight,\n };\n });\n }, []);\n\n const spawnCross = React.useCallback(() => {\n if (!isLoadingRef.current || typeof window === \"undefined\") {\n return;\n }\n\n const tilt = (randomFloat() - 0.5) * 6;\n const availableWidth = Math.max(bounds.width || window.innerWidth, 160);\n const nextCross: Cross = {\n id: crossIdRef.current += 1,\n x: randomFloat() * Math.max(availableWidth - 120, 24),\n rotation: tilt,\n scale: 0.4 + randomFloat() * 0.4,\n groundOffset: randomFloat() * 30,\n delay: 0,\n opacity: 0.4 + randomFloat() * 0.3,\n };\n setCrosses((current) => [...current.slice(-17), nextCross]);\n }, [bounds.width]);\n\n React.useEffect(() => {\n if (typeof window === \"undefined\") {\n return undefined;\n }\n\n updateBounds();\n window.addEventListener(\"resize\", updateBounds);\n\n const observer =\n typeof ResizeObserver === \"undefined\" || !containerRef.current\n ? null\n : new ResizeObserver(() => {\n updateBounds();\n });\n\n if (observer && containerRef.current) {\n observer.observe(containerRef.current);\n }\n\n return () => {\n window.removeEventListener(\"resize\", updateBounds);\n observer?.disconnect();\n };\n }, [updateBounds]);\n\n React.useEffect(() => {\n isLoadingRef.current = true;\n\n const scheduleNextCross = () => {\n const delay = 400 + randomFloat() * 300;\n return setTimeout(() => {\n spawnCross();\n if (isLoadingRef.current) {\n scheduleNextCross();\n }\n }, delay);\n };\n\n const timeoutId = scheduleNextCross();\n return () => {\n clearTimeout(timeoutId);\n isLoadingRef.current = false;\n };\n }, [spawnCross]);\n\n React.useEffect(() => {\n if (!audioFeedback) {\n return undefined;\n }\n\n const enableAudio = async () => {\n const AudioContextClass =\n window.AudioContext || (window as Window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioContextClass) {\n return;\n }\n\n try {\n const ctx = audioCtxRef.current ?? new AudioContextClass();\n audioCtxRef.current = ctx;\n if (ctx.state === \"suspended\") {\n await ctx.resume();\n }\n audioEnabledRef.current = ctx.state === \"running\";\n } catch {\n audioEnabledRef.current = false;\n }\n };\n\n window.addEventListener(\"pointerdown\", enableAudio, {\n once: true,\n passive: true,\n });\n window.addEventListener(\"keydown\", enableAudio, { once: true });\n\n return () => {\n window.removeEventListener(\"pointerdown\", enableAudio);\n window.removeEventListener(\"keydown\", enableAudio);\n if (audioCtxRef.current) {\n void audioCtxRef.current.close().catch(() => undefined);\n audioCtxRef.current = null;\n }\n audioEnabledRef.current = false;\n };\n }, [audioFeedback]);\n\n React.useEffect(() => {\n if (!audioFeedback) {\n return undefined;\n }\n\n const playBeep = () => {\n const audioCtx = audioCtxRef.current;\n if (!audioCtx || !audioEnabledRef.current) {\n return;\n }\n\n const osc = audioCtx.createOscillator();\n const gain = audioCtx.createGain();\n osc.type = \"triangle\";\n osc.frequency.value = 523.25;\n gain.gain.setValueAtTime(0.15, audioCtx.currentTime);\n gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.08);\n osc.connect(gain);\n gain.connect(audioCtx.destination);\n osc.start();\n osc.stop(audioCtx.currentTime + 0.08);\n };\n\n const filledBars = Math.round(\n (displayProgress / 100) * LOADING_PROGRESS_SEGMENT_COUNT,\n );\n if (filledBars > prevFilledBars.current) {\n playBeep();\n prevFilledBars.current = filledBars;\n }\n\n return undefined;\n }, [audioFeedback, displayProgress]);\n\n React.useEffect(() => {\n const ellipsisInterval = setInterval(() => {\n setEllipsisCount((count) => (count + 1) % 4);\n }, 400);\n return () => clearInterval(ellipsisInterval);\n }, []);\n\n React.useEffect(() => {\n const timeout = setTimeout(() => {\n setDisplayProgress(progress);\n }, 50);\n return () => clearTimeout(timeout);\n }, [progress]);\n\n const ellipsis = \".\".repeat(ellipsisCount);\n const cleanMessage = message ? message.replace(/\\.+$/, \"\") : \"\";\n const isLightMode = resolveIsLightMode(themeMode);\n const screenWidth =\n bounds.width || (typeof window === \"undefined\" ? 0 : window.innerWidth);\n const screenHeight =\n bounds.height || (typeof window === \"undefined\" ? 0 : window.innerHeight);\n\n return (\n <div\n ref={containerRef}\n className={`${styles.screen} ${fullscreen ? styles.fullscreen : styles.contained} ${\n isLightMode ? styles.lightTheme : styles.darkTheme\n }`.trim()}\n style={\n {\n \"--screen-height\": `${screenHeight}px`,\n \"--screen-width\": `${screenWidth}px`,\n } as React.CSSProperties\n }\n >\n {crosses.map((cross) => (\n <div\n key={cross.id}\n className={styles.crossContainer}\n style={\n {\n \"--cross-left\": `${cross.x}px`,\n \"--cross-delay\": `${cross.delay}ms`,\n \"--rotation\": `${cross.rotation}deg`,\n \"--ground\": `${cross.groundOffset}%`,\n \"--cross-scale\": String(cross.scale),\n \"--cross-opacity\": String(cross.opacity),\n } as React.CSSProperties\n }\n onAnimationEnd={(event) => {\n if (event.animationName === \"crossFall\") {\n event.currentTarget.classList.add(styles.crossLanded);\n }\n }}\n >\n <div className={styles.crossSvg}>\n <LightCrossIcon className={styles.crossShape} />\n </div>\n </div>\n ))}\n\n <div className={styles.centerStack}>\n {logo ? <div className={styles.logoWrap}>{logo}</div> : null}\n <div className={styles.title}>{title}</div>\n </div>\n\n <LoadingProgressBar progress={displayProgress} className={styles.progressRow} />\n\n <div className={styles.footerMessage}>\n {cleanMessage}\n {ellipsis}\n </div>\n </div>\n );\n}\n\nexport type LoadingStage = \"init\" | \"auth\" | \"agent\" | \"config\" | \"ready\";\n\nconst STAGE_PROGRESS: Record<LoadingStage, number> = {\n init: 0,\n auth: 25,\n agent: 50,\n config: 75,\n ready: 100,\n};\n\nconst STAGE_MESSAGES: Record<LoadingStage, string> = {\n init: \"Initializing...\",\n auth: \"Authenticating...\",\n agent: \"Loading agent...\",\n config: \"Loading configuration...\",\n ready: \"Preparing interface...\",\n};\n\nexport function useLoadingProgress(\n isLoading: boolean,\n _phase?: string,\n stage: LoadingStage = \"init\",\n) {\n const [progress, setProgress] = React.useState(0);\n const [currentMessage, setCurrentMessage] = React.useState(\"Initializing...\");\n const progressRef = React.useRef(0);\n\n React.useEffect(() => {\n progressRef.current = progress;\n }, [progress]);\n\n React.useEffect(() => {\n if (!isLoading) {\n setProgress(100);\n progressRef.current = 100;\n setCurrentMessage(\"Complete!\");\n return undefined;\n }\n\n const targetProgress = STAGE_PROGRESS[stage];\n const targetMessage = STAGE_MESSAGES[stage];\n setCurrentMessage(targetMessage);\n\n const currentProgressSnapshot = progressRef.current;\n const diff = targetProgress - currentProgressSnapshot;\n const steps = 20;\n const stepDuration = 100;\n let step = 0;\n\n const interval = setInterval(() => {\n step += 1;\n const easedStep = step / steps;\n const easedProgress = diff * easedStep;\n const nextProgress = Math.min(\n currentProgressSnapshot + easedProgress,\n targetProgress,\n );\n progressRef.current = nextProgress;\n setProgress(nextProgress);\n\n if (step >= steps) {\n clearInterval(interval);\n }\n }, stepDuration);\n\n return () => clearInterval(interval);\n }, [isLoading, stage]);\n\n return { progress, message: currentMessage };\n}\n"]}
@@ -0,0 +1,108 @@
1
+ .screen {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 9999;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: center;
8
+ justify-content: center;
9
+ padding: 20px;
10
+ overflow: hidden;
11
+ font-family: var(--font-primary);
12
+ }
13
+
14
+ .fullscreen {
15
+ position: fixed;
16
+ }
17
+
18
+ .contained {
19
+ position: absolute;
20
+ border: 1px solid var(--border-color);
21
+ }
22
+
23
+ .lightTheme {
24
+ background: #fafbfc;
25
+ color: #1a1a1a;
26
+ }
27
+
28
+ .darkTheme {
29
+ background: #0a0a0a;
30
+ color: #cbcbcc;
31
+ }
32
+
33
+ @keyframes crossFall {
34
+ 0% {
35
+ transform: translateY(-450px) rotate(var(--rotation));
36
+ opacity: 0;
37
+ }
38
+
39
+ 10% {
40
+ opacity: 1;
41
+ }
42
+
43
+ 100% {
44
+ transform: translateY(calc(var(--screen-height, 100vh) - var(--ground) - 50px))
45
+ rotate(var(--rotation));
46
+ opacity: 1;
47
+ }
48
+ }
49
+
50
+ .crossContainer {
51
+ position: absolute;
52
+ top: 0;
53
+ left: var(--cross-left, 0px);
54
+ z-index: 1;
55
+ will-change: transform;
56
+ animation: crossFall 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
57
+ animation-delay: var(--cross-delay, 0ms);
58
+ }
59
+
60
+ .crossSvg {
61
+ transform: scale(var(--cross-scale, 1));
62
+ transform-origin: center top;
63
+ opacity: var(--cross-opacity, 0.6);
64
+ }
65
+
66
+ .crossLanded .crossSvg {
67
+ opacity: 1 !important;
68
+ }
69
+
70
+ .crossShape {
71
+ color: currentColor;
72
+ }
73
+
74
+ .centerStack {
75
+ position: relative;
76
+ z-index: 10;
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: center;
80
+ gap: 24px;
81
+ }
82
+
83
+ .logoWrap {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ }
88
+
89
+ .title {
90
+ font-size: clamp(24px, 8vw, 48px);
91
+ font-weight: 700;
92
+ letter-spacing: 8px;
93
+ }
94
+
95
+ .progressRow {
96
+ position: relative;
97
+ z-index: 10;
98
+ }
99
+
100
+ .footerMessage {
101
+ position: absolute;
102
+ right: 40px;
103
+ bottom: 40px;
104
+ z-index: 10;
105
+ opacity: 0.5;
106
+ font-family: var(--font-mono);
107
+ font-size: 14px;
108
+ }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export interface OverlayButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3
+ active?: boolean;
4
+ }
5
+ export declare function OverlayButton({ active, className, ...props }: OverlayButtonProps): import("react/jsx-runtime").JSX.Element;
6
+ export default OverlayButton;
7
+ //# sourceMappingURL=OverlayButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayButton.d.ts","sourceRoot":"","sources":["../../src/components/OverlayButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,WAAW,kBACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,SAAc,EACd,GAAG,KAAK,EACT,EAAE,kBAAkB,2CAKpB;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styles from "./OverlayButton.module.css";
3
+ export function OverlayButton({ active, className = "", ...props }) {
4
+ const classes = `${styles.overlayBtn} ${active ? styles.overlayBtnActive : ""} ${className}`.trim();
5
+ return _jsx("button", { type: "button", className: classes, ...props });
6
+ }
7
+ export default OverlayButton;
8
+ //# sourceMappingURL=OverlayButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverlayButton.js","sourceRoot":"","sources":["../../src/components/OverlayButton.tsx"],"names":[],"mappings":";AAEA,OAAO,MAAM,MAAM,4BAA4B,CAAC;AAOhD,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,SAAS,GAAG,EAAE,EACd,GAAG,KAAK,EACW;IACnB,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,UAAU,IAClC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EACrC,IAAI,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;IACvB,OAAO,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,OAAO,KAAM,KAAK,GAAI,CAAC;AACjE,CAAC;AAED,eAAe,aAAa,CAAC","sourcesContent":["import React from \"react\";\n\nimport styles from \"./OverlayButton.module.css\";\n\nexport interface OverlayButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n active?: boolean;\n}\n\nexport function OverlayButton({\n active,\n className = \"\",\n ...props\n}: OverlayButtonProps) {\n const classes = `${styles.overlayBtn} ${\n active ? styles.overlayBtnActive : \"\"\n } ${className}`.trim();\n return <button type=\"button\" className={classes} {...props} />;\n}\n\nexport default OverlayButton;\n"]}
@@ -0,0 +1,32 @@
1
+ .overlayBtn {
2
+ width: var(--overlay-btn-size, 2rem);
3
+ height: var(--overlay-btn-size, 2rem);
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ border: 1px solid var(--border-color);
8
+ border-radius: var(--overlay-btn-radius, 0.5rem);
9
+ background: color-mix(in srgb, var(--bg-primary) 72%, transparent);
10
+ backdrop-filter: blur(2px);
11
+ color: var(--text-secondary);
12
+ cursor: pointer;
13
+ opacity: 0.8;
14
+ transition:
15
+ opacity 150ms ease,
16
+ background-color 150ms ease,
17
+ color 150ms ease,
18
+ border-color 150ms ease;
19
+ }
20
+
21
+ .overlayBtn:hover {
22
+ background: color-mix(in srgb, var(--bg-primary) 92%, transparent);
23
+ color: var(--text-primary);
24
+ opacity: 1;
25
+ }
26
+
27
+ .overlayBtnActive,
28
+ .overlayBtnActive:hover {
29
+ background: var(--text-primary);
30
+ border-color: var(--text-primary);
31
+ color: var(--bg-primary);
32
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export interface PixelTooltipProps {
3
+ content: string;
4
+ children: React.ReactNode;
5
+ position?: "top" | "bottom" | "left" | "right";
6
+ delay?: number;
7
+ }
8
+ export declare function PixelTooltip({ content, children, position, delay, }: PixelTooltipProps): import("react/jsx-runtime").JSX.Element;
9
+ export default PixelTooltip;
10
+ //# sourceMappingURL=PixelTooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PixelTooltip.d.ts","sourceRoot":"","sources":["../../src/components/PixelTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,QAAgB,EAChB,KAAW,GACZ,EAAE,iBAAiB,2CAgEnB;AAED,eAAe,YAAY,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import styles from "./PixelTooltip.module.css";
4
+ export function PixelTooltip({ content, children, position = "top", delay = 200, }) {
5
+ const [isVisible, setIsVisible] = React.useState(false);
6
+ const [coords, setCoords] = React.useState({ x: 0, y: 0, width: 0 });
7
+ const timeoutRef = React.useRef(null);
8
+ const triggerRef = React.useRef(null);
9
+ const handleMouseEnter = React.useCallback(() => {
10
+ timeoutRef.current = setTimeout(() => {
11
+ if (triggerRef.current) {
12
+ const rect = triggerRef.current.getBoundingClientRect();
13
+ setCoords({
14
+ x: rect.left,
15
+ y: rect.bottom,
16
+ width: rect.width,
17
+ });
18
+ }
19
+ setIsVisible(true);
20
+ }, delay);
21
+ }, [delay]);
22
+ const handleMouseLeave = React.useCallback(() => {
23
+ if (timeoutRef.current) {
24
+ clearTimeout(timeoutRef.current);
25
+ timeoutRef.current = null;
26
+ }
27
+ setIsVisible(false);
28
+ }, []);
29
+ React.useEffect(() => () => {
30
+ if (timeoutRef.current) {
31
+ clearTimeout(timeoutRef.current);
32
+ }
33
+ }, []);
34
+ return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, className: styles.trigger, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: children }), isVisible ? (_jsx("div", { className: `${styles.tooltip} ${styles[position]} ${styles.show}`.trim(), style: {
35
+ left: `${coords.x + 5}px`,
36
+ top: `${coords.y + 8}px`,
37
+ width: `${Math.max(coords.width - 20, 60)}px`,
38
+ }, children: _jsx("div", { className: styles.pixelContainer, children: _jsx("div", { className: styles.content, children: content }) }) })) : null] }));
39
+ }
40
+ export default PixelTooltip;
41
+ //# sourceMappingURL=PixelTooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PixelTooltip.js","sourceRoot":"","sources":["../../src/components/PixelTooltip.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAS/C,MAAM,UAAU,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,QAAQ,GAAG,KAAK,EAChB,KAAK,GAAG,GAAG,GACO;IAClB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAuC,IAAI,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEtD,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QAC9C,UAAU,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACnC,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;gBACxD,SAAS,CAAC;oBACR,CAAC,EAAE,IAAI,CAAC,IAAI;oBACZ,CAAC,EAAE,IAAI,CAAC,MAAM;oBACd,KAAK,EAAE,IAAI,CAAC,KAAK;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QAC9C,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACjC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,CAAC;QACD,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,KAAK,CAAC,SAAS,CACb,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO,CACL,8BACE,cACE,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,CAAC,OAAO,EACzB,YAAY,EAAE,gBAAgB,EAC9B,YAAY,EAAE,gBAAgB,YAE7B,QAAQ,GACL,EAEL,SAAS,CAAC,CAAC,CAAC,CACX,cACE,SAAS,EAAE,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EACxE,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI;oBACzB,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI;oBACxB,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,IAAI;iBAC9C,YAED,cAAK,SAAS,EAAE,MAAM,CAAC,cAAc,YACnC,cAAK,SAAS,EAAE,MAAM,CAAC,OAAO,YAAG,OAAO,GAAO,GAC3C,GACF,CACP,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAC;AACJ,CAAC;AAED,eAAe,YAAY,CAAC","sourcesContent":["import React from \"react\";\n\nimport styles from \"./PixelTooltip.module.css\";\n\nexport interface PixelTooltipProps {\n content: string;\n children: React.ReactNode;\n position?: \"top\" | \"bottom\" | \"left\" | \"right\";\n delay?: number;\n}\n\nexport function PixelTooltip({\n content,\n children,\n position = \"top\",\n delay = 200,\n}: PixelTooltipProps) {\n const [isVisible, setIsVisible] = React.useState(false);\n const [coords, setCoords] = React.useState({ x: 0, y: 0, width: 0 });\n const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n const triggerRef = React.useRef<HTMLDivElement>(null);\n\n const handleMouseEnter = React.useCallback(() => {\n timeoutRef.current = setTimeout(() => {\n if (triggerRef.current) {\n const rect = triggerRef.current.getBoundingClientRect();\n setCoords({\n x: rect.left,\n y: rect.bottom,\n width: rect.width,\n });\n }\n setIsVisible(true);\n }, delay);\n }, [delay]);\n\n const handleMouseLeave = React.useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n setIsVisible(false);\n }, []);\n\n React.useEffect(\n () => () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n },\n [],\n );\n\n return (\n <>\n <div\n ref={triggerRef}\n className={styles.trigger}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n >\n {children}\n </div>\n\n {isVisible ? (\n <div\n className={`${styles.tooltip} ${styles[position]} ${styles.show}`.trim()}\n style={{\n left: `${coords.x + 5}px`,\n top: `${coords.y + 8}px`,\n width: `${Math.max(coords.width - 20, 60)}px`,\n }}\n >\n <div className={styles.pixelContainer}>\n <div className={styles.content}>{content}</div>\n </div>\n </div>\n ) : null}\n </>\n );\n}\n\nexport default PixelTooltip;\n"]}