react-os-shell 0.1.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/Calculator-BNBRNV4P.js +184 -0
  4. package/dist/Calculator-BNBRNV4P.js.map +1 -0
  5. package/dist/Calendar-5EYUVGUU.js +423 -0
  6. package/dist/Calendar-5EYUVGUU.js.map +1 -0
  7. package/dist/Checkers-MIAHIKJH.js +214 -0
  8. package/dist/Checkers-MIAHIKJH.js.map +1 -0
  9. package/dist/Chess-C5BY45NA.js +190 -0
  10. package/dist/Chess-C5BY45NA.js.map +1 -0
  11. package/dist/ConfirmDialog-ZP4AHVUD.js +3 -0
  12. package/dist/ConfirmDialog-ZP4AHVUD.js.map +1 -0
  13. package/dist/CurrencyConverter-TYPU2IRF.js +223 -0
  14. package/dist/CurrencyConverter-TYPU2IRF.js.map +1 -0
  15. package/dist/Email-JEYYJ3YV.js +1835 -0
  16. package/dist/Email-JEYYJ3YV.js.map +1 -0
  17. package/dist/Game2048-3RH3ELRD.js +191 -0
  18. package/dist/Game2048-3RH3ELRD.js.map +1 -0
  19. package/dist/GeminiChat-BXLBJFT4.js +184 -0
  20. package/dist/GeminiChat-BXLBJFT4.js.map +1 -0
  21. package/dist/Minesweeper-VQGLAZON.js +270 -0
  22. package/dist/Minesweeper-VQGLAZON.js.map +1 -0
  23. package/dist/Notepad-YTZRCAXX.js +389 -0
  24. package/dist/Notepad-YTZRCAXX.js.map +1 -0
  25. package/dist/PomodoroTimer-HARIJN4S.js +196 -0
  26. package/dist/PomodoroTimer-HARIJN4S.js.map +1 -0
  27. package/dist/Spreadsheet-IOKEDNS6.js +446 -0
  28. package/dist/Spreadsheet-IOKEDNS6.js.map +1 -0
  29. package/dist/Sudoku-XHLYCEVT.js +197 -0
  30. package/dist/Sudoku-XHLYCEVT.js.map +1 -0
  31. package/dist/Tetris-ZHCZYL24.js +243 -0
  32. package/dist/Tetris-ZHCZYL24.js.map +1 -0
  33. package/dist/Weather-ROZ7TRNW.js +310 -0
  34. package/dist/Weather-ROZ7TRNW.js.map +1 -0
  35. package/dist/apps/index.d.ts +55 -0
  36. package/dist/apps/index.js +48 -0
  37. package/dist/apps/index.js.map +1 -0
  38. package/dist/chunk-5O2KEISQ.js +155 -0
  39. package/dist/chunk-5O2KEISQ.js.map +1 -0
  40. package/dist/chunk-D7PYW2QS.js +265 -0
  41. package/dist/chunk-D7PYW2QS.js.map +1 -0
  42. package/dist/chunk-GP4Y3VCB.js +806 -0
  43. package/dist/chunk-GP4Y3VCB.js.map +1 -0
  44. package/dist/chunk-NSU7OHPC.js +39 -0
  45. package/dist/chunk-NSU7OHPC.js.map +1 -0
  46. package/dist/chunk-PDFQNHW7.js +24 -0
  47. package/dist/chunk-PDFQNHW7.js.map +1 -0
  48. package/dist/chunk-RFTLYCSF.js +144 -0
  49. package/dist/chunk-RFTLYCSF.js.map +1 -0
  50. package/dist/chunk-SVBID2P6.js +142 -0
  51. package/dist/chunk-SVBID2P6.js.map +1 -0
  52. package/dist/chunk-TFGOLXGD.js +41 -0
  53. package/dist/chunk-TFGOLXGD.js.map +1 -0
  54. package/dist/chunk-WIJ45SYD.js +120 -0
  55. package/dist/chunk-WIJ45SYD.js.map +1 -0
  56. package/dist/chunk-WQIS72NL.js +1470 -0
  57. package/dist/chunk-WQIS72NL.js.map +1 -0
  58. package/dist/index.d.ts +642 -0
  59. package/dist/index.js +3443 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/sounds-NT4DEZGD.js +3 -0
  62. package/dist/sounds-NT4DEZGD.js.map +1 -0
  63. package/dist/styles.css +174 -0
  64. package/dist/types-CFIZ1_xt.d.ts +67 -0
  65. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,3443 @@
1
+ import { useEmailUnreadCount } from './chunk-PDFQNHW7.js';
2
+ import { useGoogleAuth } from './chunk-5O2KEISQ.js';
3
+ import { useShellPrefs } from './chunk-TFGOLXGD.js';
4
+ export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-TFGOLXGD.js';
5
+ import { formatDate } from './chunk-NSU7OHPC.js';
6
+ export { formatDate } from './chunk-NSU7OHPC.js';
7
+ import { toast_default } from './chunk-WIJ45SYD.js';
8
+ export { toast_default as toast } from './chunk-WIJ45SYD.js';
9
+ import { playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
10
+ import { useWindowManager, glassStyle, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons, ModalActions, useModalActive } from './chunk-WQIS72NL.js';
11
+ export { CancelButton, CopyButton, DocFavStar, GLASS_DIVIDER, GLASS_INPUT_BG, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, glassStyle, isEntityEntry, isPageEntry, setShellApiClient, setShellNavIcons, setShellWindowRegistry, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-WQIS72NL.js';
12
+ export { ConfirmProvider, confirm, confirmDestructive } from './chunk-RFTLYCSF.js';
13
+ import { createContext, useState, useRef, useEffect, useCallback, useLayoutEffect, useContext, isValidElement, cloneElement, useSyncExternalStore } from 'react';
14
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
15
+ import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react';
16
+ import { createPortal } from 'react-dom';
17
+ import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
18
+ import 'react-router-dom';
19
+
20
+ // src/windowRegistry/createWindowRegistry.ts
21
+ function createWindowRegistry(...partials) {
22
+ return Object.assign({}, ...partials);
23
+ }
24
+ var DEFAULT_MAGNIFIER = "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z";
25
+ function GlobalSearch({ providers, typeIcons, placeholder = "Search..." } = {}) {
26
+ const [open, setOpen] = useState(false);
27
+ const [query, setQuery] = useState("");
28
+ const [results, setResults] = useState([]);
29
+ const [loading, setLoading] = useState(false);
30
+ const [activeIdx, setActiveIdx] = useState(0);
31
+ const inputRef = useRef(null);
32
+ const { openEntity } = useWindowManager();
33
+ const debounceRef = useRef();
34
+ useEffect(() => {
35
+ const handler = (e) => {
36
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
37
+ e.preventDefault();
38
+ setOpen((prev) => !prev);
39
+ }
40
+ if (e.key === "Escape" && open) {
41
+ setOpen(false);
42
+ }
43
+ };
44
+ window.addEventListener("keydown", handler);
45
+ return () => window.removeEventListener("keydown", handler);
46
+ }, [open]);
47
+ useEffect(() => {
48
+ if (open) {
49
+ setTimeout(() => inputRef.current?.focus(), 50);
50
+ } else {
51
+ setQuery("");
52
+ setResults([]);
53
+ }
54
+ }, [open]);
55
+ useEffect(() => {
56
+ if (debounceRef.current) clearTimeout(debounceRef.current);
57
+ if (query.length < 2) {
58
+ setResults([]);
59
+ return;
60
+ }
61
+ if (!providers || providers.length === 0) {
62
+ setResults([]);
63
+ return;
64
+ }
65
+ debounceRef.current = setTimeout(async () => {
66
+ setLoading(true);
67
+ try {
68
+ const responses = await Promise.all(providers.map((p) => p(query).catch(() => [])));
69
+ setResults(responses.flat());
70
+ setActiveIdx(0);
71
+ } catch {
72
+ setResults([]);
73
+ }
74
+ setLoading(false);
75
+ }, 250);
76
+ return () => {
77
+ if (debounceRef.current) clearTimeout(debounceRef.current);
78
+ };
79
+ }, [query, providers]);
80
+ const handleSelect = useCallback((result) => {
81
+ setOpen(false);
82
+ openEntity(result.entity_type, result.entity_id, null, result.label);
83
+ }, [openEntity]);
84
+ const handleKeyDown = (e) => {
85
+ if (e.key === "ArrowDown") {
86
+ e.preventDefault();
87
+ setActiveIdx((i) => Math.min(i + 1, results.length - 1));
88
+ } else if (e.key === "ArrowUp") {
89
+ e.preventDefault();
90
+ setActiveIdx((i) => Math.max(i - 1, 0));
91
+ } else if (e.key === "Enter" && results[activeIdx]) {
92
+ handleSelect(results[activeIdx]);
93
+ }
94
+ };
95
+ if (!open) return null;
96
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[200] flex items-start justify-center pt-[15vh]", onClick: () => setOpen(false), children: [
97
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
98
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-xl rounded-2xl overflow-hidden", onClick: (e) => e.stopPropagation(), style: glassStyle(), children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-gray-200", children: [
100
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: DEFAULT_MAGNIFIER }) }),
101
+ /* @__PURE__ */ jsx(
102
+ "input",
103
+ {
104
+ ref: inputRef,
105
+ type: "text",
106
+ value: query,
107
+ onChange: (e) => setQuery(e.target.value),
108
+ onKeyDown: handleKeyDown,
109
+ placeholder,
110
+ className: "flex-1 text-sm bg-transparent border-0 outline-none placeholder-gray-400"
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsx("kbd", { className: "hidden sm:inline-flex items-center rounded border border-gray-300 px-1.5 py-0.5 text-[10px] font-medium text-gray-400", children: "ESC" })
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[50vh] overflow-y-auto", children: [
116
+ loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Searching..." }),
117
+ !loading && query.length >= 2 && results.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "No results found." }),
118
+ !loading && results.length > 0 && /* @__PURE__ */ jsx("ul", { className: "py-2", children: results.map((r, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ className: `flex items-center gap-3 w-full px-4 py-2.5 text-left transition-colors ${i === activeIdx ? "bg-blue-50" : "hover:bg-gray-50"}`,
122
+ onClick: () => handleSelect(r),
123
+ onMouseEnter: () => setActiveIdx(i),
124
+ children: [
125
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: typeIcons?.[r.type] || DEFAULT_MAGNIFIER }) }),
126
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
127
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: r.label }),
128
+ r.sub && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 truncate", children: r.sub })
129
+ ] }),
130
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-500", children: r.type })
131
+ ]
132
+ }
133
+ ) }, `${r.type}-${r.label}-${i}`)) }),
134
+ !loading && query.length < 2 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Type at least 2 characters to search..." })
135
+ ] }),
136
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-t border-gray-100 bg-gray-50 text-[11px] text-gray-400", children: [
137
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
138
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
139
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2191" }),
140
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2193" }),
141
+ " navigate"
142
+ ] }),
143
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
144
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Enter" }),
145
+ " open"
146
+ ] })
147
+ ] }),
148
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
149
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Esc" }),
150
+ " close"
151
+ ] })
152
+ ] })
153
+ ] })
154
+ ] });
155
+ }
156
+
157
+ // src/shell/Kbd.tsx
158
+ var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
159
+ var MOD = isMac ? "\u2318" : "Ctrl";
160
+ var ALT = isMac ? "\u2325" : "Alt";
161
+ var SHIFT = "\u21E7";
162
+ var ENTER = "\u23CE";
163
+ var CMD_ENTER = isMac ? "\u2318\u23CE" : "Ctrl\u23CE";
164
+ var CMD_S = isMac ? "\u2318S" : "Ctrl+S";
165
+ var CMD_K = isMac ? "\u2318K" : "Ctrl+K";
166
+ var CMD_DOT = isMac ? "\u2318." : "Ctrl+.";
167
+ var CMD_A = isMac ? "\u2318A" : "Ctrl+A";
168
+ var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
169
+ var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
170
+ var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
171
+ var sections = [
172
+ {
173
+ title: "Global",
174
+ shortcuts: [
175
+ { keys: CMD_K, description: "Search" },
176
+ { keys: CMD_DOT, description: "Toggle sidebar" },
177
+ { keys: "Ctrl F11", description: "Toggle fullscreen" },
178
+ { keys: "ESC", description: "Exit fullscreen (when no windows open)" },
179
+ { keys: "?", description: "Show keyboard shortcuts" }
180
+ ]
181
+ },
182
+ {
183
+ title: "Lists",
184
+ shortcuts: [
185
+ { keys: "J / \u2193", description: "Next row" },
186
+ { keys: "K / \u2191", description: "Previous row" },
187
+ { keys: "\u23CE", description: "Open selected row" },
188
+ { keys: "\u21E7J / \u21E7K", description: "Move and select" },
189
+ { keys: "\u21E7 Click", description: "Range select" },
190
+ { keys: "Space", description: "Toggle row checkbox" },
191
+ { keys: CMD_A, description: "Select / deselect all" },
192
+ { keys: ALT_SHIFT_N, description: "Create new item" },
193
+ { keys: ALT_SHIFT_E, description: "Edit selected item" }
194
+ ]
195
+ },
196
+ {
197
+ title: "Modals / Forms",
198
+ shortcuts: [
199
+ { keys: CMD_ENTER, description: "Submit" },
200
+ { keys: CMD_S, description: "Save" },
201
+ { keys: ALT_SHIFT_D, description: "Save as new (duplicate)" },
202
+ { keys: "ESC", description: "Close modal" }
203
+ ]
204
+ }
205
+ ];
206
+ function ShortcutHelp() {
207
+ const [open, setOpen] = useState(false);
208
+ useEffect(() => {
209
+ const toggle = () => setOpen((prev) => !prev);
210
+ document.addEventListener("toggle-shortcut-help", toggle);
211
+ return () => document.removeEventListener("toggle-shortcut-help", toggle);
212
+ }, []);
213
+ useEffect(() => {
214
+ const handler = (e) => {
215
+ if (e.key === "?" && !e.metaKey && !e.ctrlKey && !e.altKey) {
216
+ const tag = e.target?.tagName;
217
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
218
+ if (e.target?.isContentEditable) return;
219
+ e.preventDefault();
220
+ setOpen((prev) => !prev);
221
+ }
222
+ };
223
+ window.addEventListener("keydown", handler);
224
+ return () => window.removeEventListener("keydown", handler);
225
+ }, []);
226
+ return /* @__PURE__ */ jsxs(Dialog, { open, onClose: () => setOpen(false), className: "relative z-[9999]", children: [
227
+ /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
228
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-6", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "w-full max-w-md rounded-lg bg-white shadow-xl", children: [
229
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 pt-5 pb-3 border-b border-gray-200", children: [
230
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-gray-900", children: "Keyboard Shortcuts" }),
231
+ /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "text-gray-400 hover:text-gray-500 text-sm", children: "ESC" })
232
+ ] }),
233
+ /* @__PURE__ */ jsx("div", { className: "px-5 py-4 space-y-5 max-h-[60vh] overflow-y-auto", children: sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
234
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2", children: section.title }),
235
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: section.shortcuts.map((s) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
236
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: s.description }),
237
+ /* @__PURE__ */ jsx("kbd", { className: "inline-flex items-center rounded border border-gray-300 bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 font-mono", children: s.keys })
238
+ ] }, s.keys + s.description)) })
239
+ ] }, section.title)) }),
240
+ /* @__PURE__ */ jsx("div", { className: "px-5 py-3 border-t border-gray-100 text-center", children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-400", children: [
241
+ "Press ",
242
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-50 px-1.5 py-0.5 text-[10px] font-medium text-gray-500", children: "?" }),
243
+ " to toggle"
244
+ ] }) })
245
+ ] }) })
246
+ ] });
247
+ }
248
+ function timeAgo(dateStr) {
249
+ const diff = Date.now() - new Date(dateStr).getTime();
250
+ const mins = Math.floor(diff / 6e4);
251
+ if (mins < 1) return "just now";
252
+ if (mins < 60) return `${mins}m ago`;
253
+ const hrs = Math.floor(mins / 60);
254
+ if (hrs < 24) return `${hrs}h ago`;
255
+ const days = Math.floor(hrs / 24);
256
+ if (days < 7) return `${days}d ago`;
257
+ return formatDate(dateStr);
258
+ }
259
+ function NotificationBell({
260
+ useUnreadCount,
261
+ list,
262
+ markRead,
263
+ markAllRead,
264
+ onItemClick,
265
+ onViewAll,
266
+ popDirection
267
+ }) {
268
+ const [open, setOpen] = useState(false);
269
+ const dropdownRef = useRef(null);
270
+ const buttonRef = useRef(null);
271
+ const queryClient = useQueryClient();
272
+ const unreadCount = useUnreadCount();
273
+ useEffect(() => {
274
+ if ("Notification" in window && Notification.permission === "default") Notification.requestPermission();
275
+ }, []);
276
+ const prevCountRef = useRef(null);
277
+ useEffect(() => {
278
+ if (prevCountRef.current === null) {
279
+ prevCountRef.current = unreadCount;
280
+ return;
281
+ }
282
+ if (unreadCount > prevCountRef.current) {
283
+ list({ page_size: 1 }).then((data) => {
284
+ const latest = data?.results?.[0];
285
+ if (!latest) return;
286
+ const title = latest.title || "New Notification";
287
+ const body = latest.message || latest.entity_label || "";
288
+ if (document.hidden) {
289
+ if ("Notification" in window && Notification.permission === "granted") {
290
+ const n = new Notification(title, { body, icon: "/favicon.svg", tag: `notif-${latest.id}` });
291
+ n.onclick = () => {
292
+ window.focus();
293
+ onItemClick(latest);
294
+ n.close();
295
+ };
296
+ }
297
+ } else {
298
+ toast_default.info(title, { duration: 5e3 });
299
+ }
300
+ }).catch(() => {
301
+ });
302
+ }
303
+ prevCountRef.current = unreadCount;
304
+ }, [unreadCount, list, onItemClick]);
305
+ const { data: notifData } = useQuery({
306
+ queryKey: ["notifications-dropdown"],
307
+ queryFn: () => list({ page_size: 30 }),
308
+ staleTime: 3e4,
309
+ refetchInterval: 3e4
310
+ });
311
+ const notifications = notifData?.results ?? [];
312
+ useEffect(() => {
313
+ if (!open) return;
314
+ const handler = (e) => {
315
+ if (e.target.closest("[data-menu-toggle]")) return;
316
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) setOpen(false);
317
+ };
318
+ window.addEventListener("pointerdown", handler);
319
+ return () => window.removeEventListener("pointerdown", handler);
320
+ }, [open]);
321
+ const handleClick = (notif) => {
322
+ if (!notif.is_read) {
323
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: Math.max(0, (old.count || 0) - 1) } : old);
324
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
325
+ if (!old?.results) return old;
326
+ return { ...old, results: old.results.map((n) => n.id === notif.id ? { ...n, is_read: true } : n) };
327
+ });
328
+ markRead(notif.id).catch(() => {
329
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
330
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
331
+ });
332
+ }
333
+ setOpen(false);
334
+ onItemClick(notif);
335
+ };
336
+ const handleMarkAllRead = () => {
337
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: 0 } : old);
338
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
339
+ if (!old?.results) return old;
340
+ return { ...old, results: old.results.map((n) => ({ ...n, is_read: true })) };
341
+ });
342
+ markAllRead().then(() => {
343
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
344
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
345
+ });
346
+ };
347
+ const [dropdownPos, setDropdownPos] = useState({});
348
+ const calcPos = useCallback(() => {
349
+ const taskbarPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
350
+ const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 56;
351
+ const taskbarW = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-width")) || 0;
352
+ const verticalReserve = taskbarPos === "top" || taskbarPos === "bottom" ? taskbarH + 16 : 16;
353
+ const maxHeight = `calc(100vh - ${verticalReserve}px)`;
354
+ if (popDirection === "right") setDropdownPos({ left: taskbarW + 8, bottom: 8, maxHeight });
355
+ else if (popDirection === "left") setDropdownPos({ right: taskbarW + 8, bottom: 8, maxHeight });
356
+ else if (taskbarPos === "top") setDropdownPos({ right: 8, top: taskbarH + 8, maxHeight });
357
+ else setDropdownPos({ right: 8, bottom: taskbarH + 8, maxHeight });
358
+ }, [popDirection]);
359
+ useLayoutEffect(() => {
360
+ if (!open) return;
361
+ calcPos();
362
+ }, [open, calcPos]);
363
+ useEffect(() => {
364
+ if (!open) return;
365
+ window.addEventListener("resize", calcPos);
366
+ return () => window.removeEventListener("resize", calcPos);
367
+ }, [open, calcPos]);
368
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "relative", children: [
369
+ /* @__PURE__ */ jsxs(
370
+ "button",
371
+ {
372
+ ref: buttonRef,
373
+ "data-menu-toggle": true,
374
+ onMouseEnter: () => queryClient.prefetchQuery({ queryKey: ["notifications-dropdown"], queryFn: () => list({ page_size: 30 }) }),
375
+ onClick: () => setOpen((prev) => !prev),
376
+ title: "Notifications",
377
+ className: "relative shrink-0 rounded-md p-2 text-gray-900 hover:text-black hover:bg-white/20 transition-colors",
378
+ children: [
379
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" }) }),
380
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute top-0.5 right-0.5 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-red-500 px-1 text-[10px] font-bold text-white", children: unreadCount > 99 ? "99+" : unreadCount })
381
+ ]
382
+ }
383
+ ),
384
+ open && createPortal(
385
+ /* @__PURE__ */ jsxs(PopupMenu, { minWidth: 320, className: "w-80 flex flex-col overflow-hidden", style: { ...dropdownPos }, onClose: () => setOpen(false), children: [
386
+ /* @__PURE__ */ jsx(PopupMenuLabel, { children: /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between w-full", children: [
387
+ /* @__PURE__ */ jsx("span", { children: "Notifications" }),
388
+ unreadCount > 0 && /* @__PURE__ */ jsx("button", { onClick: handleMarkAllRead, className: "text-[10px] text-blue-600 hover:text-blue-700 font-medium normal-case tracking-normal", children: "Mark all read" })
389
+ ] }) }),
390
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
391
+ /* @__PURE__ */ jsx("div", { className: "overflow-y-auto", style: { flex: "1 1 auto", minHeight: 0 }, children: notifications.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 px-4 text-center", children: [
392
+ /* @__PURE__ */ jsx("svg", { className: "h-8 w-8 text-gray-300 mb-2", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" }) }),
393
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 font-medium", children: "All caught up" }),
394
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "No notifications yet" })
395
+ ] }) : notifications.map((notif) => /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => handleClick(notif), children: [
396
+ /* @__PURE__ */ jsx("div", { className: "pt-0.5 shrink-0", children: !notif.is_read ? /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-blue-500" }) : /* @__PURE__ */ jsx("div", { className: "h-2 w-2" }) }),
397
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
398
+ /* @__PURE__ */ jsx("p", { className: `text-sm leading-tight ${!notif.is_read ? "font-medium" : ""}`, children: notif.title }),
399
+ notif.message && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-0.5 truncate", children: notif.message }),
400
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
401
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400", children: timeAgo(notif.created_at) }),
402
+ notif.actor_name && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-400", children: [
403
+ "by ",
404
+ notif.actor_name
405
+ ] })
406
+ ] })
407
+ ] })
408
+ ] }, notif.id)) }),
409
+ onViewAll && /* @__PURE__ */ jsxs(Fragment, { children: [
410
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
411
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
412
+ setOpen(false);
413
+ onViewAll();
414
+ }, children: /* @__PURE__ */ jsx("span", { className: "w-full text-center text-xs text-blue-600 font-medium", children: "View all notifications" }) })
415
+ ] })
416
+ ] }),
417
+ document.body
418
+ )
419
+ ] });
420
+ }
421
+ var BugReportContext = createContext(null);
422
+ function BugReportConfigProvider({ value, children }) {
423
+ return /* @__PURE__ */ jsx(BugReportContext.Provider, { value, children });
424
+ }
425
+ function useBugReport() {
426
+ return useContext(BugReportContext);
427
+ }
428
+ var globalOpen = () => Promise.resolve(null);
429
+ var openBugReportDialog = (screenshot) => globalOpen(screenshot);
430
+ function BugReportProvider({ children }) {
431
+ const [open, setOpen] = useState(false);
432
+ const [screenshot, setScreenshot] = useState(null);
433
+ const [previewUrl, setPreviewUrl] = useState(null);
434
+ const [description, setDescription] = useState("");
435
+ const resolveRef = useRef();
436
+ const openFn = useCallback((s) => {
437
+ setScreenshot(s);
438
+ setDescription("");
439
+ setOpen(true);
440
+ return new Promise((resolve) => {
441
+ resolveRef.current = resolve;
442
+ });
443
+ }, []);
444
+ useEffect(() => {
445
+ globalOpen = openFn;
446
+ }, [openFn]);
447
+ useEffect(() => {
448
+ if (!screenshot) {
449
+ setPreviewUrl(null);
450
+ return;
451
+ }
452
+ const url = URL.createObjectURL(screenshot);
453
+ setPreviewUrl(url);
454
+ return () => URL.revokeObjectURL(url);
455
+ }, [screenshot]);
456
+ const handleSubmit = () => {
457
+ setOpen(false);
458
+ resolveRef.current?.({ description: description.trim() });
459
+ };
460
+ const handleCancel = () => {
461
+ setOpen(false);
462
+ resolveRef.current?.(null);
463
+ };
464
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
465
+ children,
466
+ /* @__PURE__ */ jsxs(Dialog, { open, onClose: handleCancel, className: "relative z-[9999]", children: [
467
+ /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40" }),
468
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "w-full max-w-2xl rounded-lg bg-white p-6 shadow-xl", children: [
469
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900", children: "Report a bug" }),
470
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500", children: "A screenshot of your current view will be sent to the admin team." }),
471
+ previewUrl && /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-md border border-gray-200 overflow-hidden bg-gray-50 max-h-64", children: /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Screenshot preview", className: "w-full h-auto max-h-64 object-contain" }) }),
472
+ !previewUrl && /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-md border border-dashed border-gray-300 px-4 py-6 text-center text-sm text-gray-500", children: "Screenshot capture failed \u2014 your description will still be sent." }),
473
+ /* @__PURE__ */ jsxs("label", { className: "mt-4 block text-sm font-medium text-gray-700", children: [
474
+ "What went wrong?",
475
+ /* @__PURE__ */ jsx("span", { className: "font-normal text-gray-400 ml-1", children: "(optional)" })
476
+ ] }),
477
+ /* @__PURE__ */ jsx(
478
+ "textarea",
479
+ {
480
+ autoFocus: true,
481
+ value: description,
482
+ onChange: (e) => setDescription(e.target.value),
483
+ onKeyDown: (e) => {
484
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleSubmit();
485
+ },
486
+ placeholder: "Briefly describe the issue, what you were doing, what you expected to happen\u2026",
487
+ rows: 3,
488
+ className: "mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none resize-none"
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
492
+ /* @__PURE__ */ jsx(
493
+ "button",
494
+ {
495
+ type: "button",
496
+ onClick: handleCancel,
497
+ className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
498
+ children: "Cancel"
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsx(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ onClick: handleSubmit,
506
+ className: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 text-sm font-medium rounded-lg",
507
+ children: "Send Report"
508
+ }
509
+ )
510
+ ] })
511
+ ] }) })
512
+ ] })
513
+ ] });
514
+ }
515
+ function StatePill({ resolved }) {
516
+ return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${resolved ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`, children: resolved ? "Resolved" : "Open" });
517
+ }
518
+ function BugReportDetail({ report }) {
519
+ const qc = useQueryClient();
520
+ const config = useBugReport();
521
+ const [resolveOpen, setResolveOpen] = useState(false);
522
+ const [note, setNote] = useState("");
523
+ const resolve = useMutation({
524
+ mutationFn: ({ is_resolved, resolution_note }) => {
525
+ if (!config?.resolve) {
526
+ return Promise.reject(new Error("Bug report resolve is not configured."));
527
+ }
528
+ return config.resolve(report.id, is_resolved, resolution_note);
529
+ },
530
+ onSuccess: () => {
531
+ qc.invalidateQueries({ queryKey: ["bug-reports"] });
532
+ qc.invalidateQueries({ queryKey: ["entity", "bug_report", report.id] });
533
+ setResolveOpen(false);
534
+ setNote("");
535
+ },
536
+ meta: { success: (d) => d.is_resolved ? "Bug report marked resolved." : "Bug report reopened." }
537
+ });
538
+ const handleAction = () => {
539
+ if (report.is_resolved) {
540
+ resolve.mutate({ is_resolved: false, resolution_note: "" });
541
+ } else {
542
+ setNote("");
543
+ setResolveOpen(true);
544
+ }
545
+ };
546
+ const submitResolve = () => {
547
+ resolve.mutate({ is_resolved: true, resolution_note: note.trim() });
548
+ };
549
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
550
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-sm", children: [
551
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-gray-900", children: report.reporter_name || "Unknown" }),
552
+ /* @__PURE__ */ jsx(StatePill, { resolved: report.is_resolved }),
553
+ /* @__PURE__ */ jsx("span", { className: "text-gray-500 ml-auto", children: formatDate(report.created_at) })
554
+ ] }),
555
+ report.description && /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-gray-50 border border-gray-200 p-3 text-sm text-gray-800 whitespace-pre-wrap", children: report.description }),
556
+ report.is_resolved && report.resolution_note && /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-green-50 border border-green-200 p-3 text-sm text-green-900 whitespace-pre-wrap", children: [
557
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-green-700 mb-1", children: "Resolution note" }),
558
+ report.resolution_note
559
+ ] }),
560
+ /* @__PURE__ */ jsxs("dl", { className: "text-xs text-gray-600 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1.5", children: [
561
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-gray-700", children: "Page" }),
562
+ /* @__PURE__ */ jsx("dd", { className: "break-all", children: /* @__PURE__ */ jsx("a", { href: report.url, target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 hover:underline", children: report.url }) }),
563
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-gray-700", children: "Viewport" }),
564
+ /* @__PURE__ */ jsx("dd", { children: report.viewport }),
565
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-gray-700", children: "Browser" }),
566
+ /* @__PURE__ */ jsx("dd", { className: "break-all", children: report.user_agent })
567
+ ] }),
568
+ report.screenshot_url ? /* @__PURE__ */ jsx("a", { href: report.screenshot_url, target: "_blank", rel: "noopener noreferrer", className: "block", children: /* @__PURE__ */ jsx(
569
+ "img",
570
+ {
571
+ src: report.screenshot_url,
572
+ alt: "Screenshot at time of report",
573
+ className: "w-full rounded-lg border border-gray-200 hover:border-blue-400 transition-colors"
574
+ }
575
+ ) }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-gray-300 p-6 text-center text-sm text-gray-400", children: "No screenshot was captured for this report." }),
576
+ config?.resolve && /* @__PURE__ */ jsx("div", { className: "flex justify-end pt-2 border-t border-gray-200", children: /* @__PURE__ */ jsx(
577
+ "button",
578
+ {
579
+ onClick: handleAction,
580
+ disabled: resolve.isPending,
581
+ className: `px-3 py-1.5 text-sm rounded-lg font-medium transition-colors ${report.is_resolved ? "bg-white border border-gray-300 text-gray-700 hover:bg-gray-50" : "bg-blue-600 text-white hover:bg-blue-700"}`,
582
+ children: resolve.isPending ? "..." : report.is_resolved ? "Reopen" : "Mark Resolved"
583
+ }
584
+ ) }),
585
+ /* @__PURE__ */ jsx(Modal, { open: resolveOpen, onClose: () => !resolve.isPending && setResolveOpen(false), title: `Resolve ${report.report_code}`, size: "md", compact: true, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
586
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-600", children: [
587
+ "Add a short note for ",
588
+ report.reporter_name || "the reporter",
589
+ " (optional)."
590
+ ] }),
591
+ /* @__PURE__ */ jsx(
592
+ "textarea",
593
+ {
594
+ value: note,
595
+ onChange: (e) => setNote(e.target.value),
596
+ onKeyDown: (e) => {
597
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) submitResolve();
598
+ },
599
+ rows: 3,
600
+ autoFocus: true,
601
+ placeholder: "e.g. Fixed in v1256 \u2014 let me know if you still see it.",
602
+ className: "w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-2 border-t border-gray-200", children: [
606
+ /* @__PURE__ */ jsx(
607
+ "button",
608
+ {
609
+ onClick: () => setResolveOpen(false),
610
+ disabled: resolve.isPending,
611
+ className: "px-3 py-1.5 text-sm rounded-lg font-medium bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors",
612
+ children: "Cancel"
613
+ }
614
+ ),
615
+ /* @__PURE__ */ jsx(
616
+ "button",
617
+ {
618
+ onClick: submitResolve,
619
+ disabled: resolve.isPending,
620
+ className: "px-3 py-1.5 text-sm rounded-lg font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors",
621
+ children: resolve.isPending ? "Resolving..." : "Resolve"
622
+ }
623
+ )
624
+ ] })
625
+ ] }) })
626
+ ] });
627
+ }
628
+ var GROUP_COLORS = {
629
+ success: "bg-green-100 text-green-800",
630
+ active: "bg-blue-100 text-blue-800",
631
+ queued: "bg-indigo-100 text-indigo-800",
632
+ info: "bg-sky-100 text-sky-800",
633
+ pending: "bg-yellow-100 text-yellow-800",
634
+ warning: "bg-orange-100 text-orange-800",
635
+ danger: "bg-red-100 text-red-800",
636
+ draft: "bg-gray-300 text-gray-800",
637
+ neutral: "bg-gray-100 text-gray-800"
638
+ };
639
+ var StatusGroupsContext = createContext({});
640
+ function StatusBadgeProvider({
641
+ groups,
642
+ children
643
+ }) {
644
+ return /* @__PURE__ */ jsx(StatusGroupsContext.Provider, { value: groups, children });
645
+ }
646
+ function StatusBadge({ status }) {
647
+ const groups = useContext(StatusGroupsContext);
648
+ const group = groups[status] ?? "neutral";
649
+ const color = GROUP_COLORS[group];
650
+ const label = status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
651
+ return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${color}`, children: label });
652
+ }
653
+
654
+ // src/version.ts
655
+ var APP_VERSION = "";
656
+
657
+ // src/changelog.ts
658
+ var changelog = [];
659
+ var changelog_default = changelog;
660
+
661
+ // src/utils/reportBug.ts
662
+ async function captureViewport() {
663
+ try {
664
+ const stream = await navigator.mediaDevices.getDisplayMedia({
665
+ video: { displaySurface: "browser" },
666
+ audio: false,
667
+ // Chromium-only hints — ignored by other engines, no harm done.
668
+ preferCurrentTab: true,
669
+ selfBrowserSurface: "include",
670
+ surfaceSwitching: "exclude"
671
+ });
672
+ const video = document.createElement("video");
673
+ video.srcObject = stream;
674
+ video.muted = true;
675
+ await video.play();
676
+ const canvas = document.createElement("canvas");
677
+ canvas.width = video.videoWidth;
678
+ canvas.height = video.videoHeight;
679
+ canvas.getContext("2d").drawImage(video, 0, 0);
680
+ stream.getTracks().forEach((t) => t.stop());
681
+ return await new Promise((resolve) => {
682
+ canvas.toBlob((b) => resolve(b), "image/jpeg", 0.9);
683
+ });
684
+ } catch (err) {
685
+ console.error("Bug report screenshot failed:", err);
686
+ return null;
687
+ }
688
+ }
689
+ async function reportBug(submit) {
690
+ const screenshot = await captureViewport();
691
+ const submission = await openBugReportDialog(screenshot);
692
+ if (submission === null) return;
693
+ try {
694
+ await submit({
695
+ description: submission.description || void 0,
696
+ screenshot: screenshot ?? void 0,
697
+ url: window.location.href,
698
+ userAgent: navigator.userAgent,
699
+ viewport: `${window.innerWidth}x${window.innerHeight}`
700
+ });
701
+ toast_default.success("Bug report sent to admins.");
702
+ } catch (err) {
703
+ toast_default.error(err?.response?.data?.detail || "Failed to send bug report.");
704
+ }
705
+ }
706
+ var ENTITY_ICON_COLORS = {
707
+ order: "text-blue-600",
708
+ purchase_order: "text-purple-600",
709
+ invoice: "text-green-600",
710
+ client: "text-indigo-600",
711
+ manufacturer: "text-orange-600",
712
+ shipment: "text-teal-600",
713
+ part_number: "text-gray-600",
714
+ project: "text-pink-600",
715
+ mould: "text-red-600",
716
+ design: "text-cyan-600",
717
+ brand: "text-amber-600",
718
+ price_sheet: "text-emerald-600",
719
+ folder: "text-yellow-600",
720
+ page: "text-blue-500"
721
+ };
722
+ var ENTITY_ICONS = {
723
+ order: "SO",
724
+ purchase_order: "PO",
725
+ invoice: "INV",
726
+ client: "CLI",
727
+ manufacturer: "MFR",
728
+ shipment: "DN",
729
+ part_number: "PN",
730
+ project: "PRJ",
731
+ mould: "MLD",
732
+ design: "DSN",
733
+ brand: "BRD",
734
+ price_sheet: "PS",
735
+ vendor_invoice: "VI",
736
+ vendor_payment: "VP",
737
+ warranty_claim: "WC",
738
+ qc_report: "QC",
739
+ vendor_shipment: "GRN",
740
+ bank_account: "BA",
741
+ wheel_finish: "WF",
742
+ weight_log: "WL",
743
+ production_progress: "PP",
744
+ vendor_price_sheet: "VPS",
745
+ proposal: "PR",
746
+ folder: "FLD"
747
+ };
748
+ var GRID = 90;
749
+ function snapToGrid(x, y) {
750
+ return { x: Math.round(x / GRID) * GRID, y: Math.round(y / GRID) * GRID };
751
+ }
752
+ var DesktopHostContext = createContext({});
753
+ function DesktopHostProvider({ value, children }) {
754
+ return /* @__PURE__ */ jsx(DesktopHostContext.Provider, { value, children });
755
+ }
756
+ function useDesktopHost() {
757
+ return useContext(DesktopHostContext);
758
+ }
759
+ function Desktop({ profile }) {
760
+ useQueryClient();
761
+ const { openEntity, openPage } = useWindowManager();
762
+ const bugReport = useBugReport();
763
+ const containerRef = useRef(null);
764
+ const favDocs = (profile?.preferences || {}).favorite_documents || [];
765
+ const folders = (profile?.preferences || {}).desktop_folders || [];
766
+ const snapEnabled = (profile?.preferences || {}).desktop_snap ?? false;
767
+ const allNotes = (profile?.preferences || {}).notepad_notes || [];
768
+ const stickyNotes = allNotes.filter((n) => n.sticky);
769
+ const host = useDesktopHost();
770
+ const openStickyRef = async (prefix, number) => {
771
+ const refNum = `${prefix}#${number}`;
772
+ if (!host.stickyResolver) {
773
+ toast_default.error(`Unknown reference: ${refNum}`);
774
+ return;
775
+ }
776
+ try {
777
+ const result = await host.stickyResolver(prefix, number);
778
+ if (result) openEntity(result.entityType, result.entityId, result.snapshot, result.label ?? refNum);
779
+ else toast_default.error(`${refNum} not found`);
780
+ } catch {
781
+ toast_default.error(`Failed to open ${refNum}`);
782
+ }
783
+ };
784
+ const toggleStickyCheckbox = (noteId, charIndex) => {
785
+ const note = allNotes.find((n) => n.id === noteId);
786
+ if (!note) return;
787
+ const content = note.content;
788
+ const match = content.slice(charIndex).match(/^\[([ xX]?)\]/);
789
+ if (!match) return;
790
+ const isChecked = match[1] === "x" || match[1] === "X";
791
+ const replacement = isChecked ? "[ ]" : "[x]";
792
+ const updated = content.slice(0, charIndex) + replacement + content.slice(charIndex + match[0].length);
793
+ saveNotes(allNotes.map((n) => n.id === noteId ? { ...n, content: updated, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : n));
794
+ };
795
+ const renderStickyContent = (noteId, text) => {
796
+ if (!text) return [];
797
+ const lines = text.split("\n");
798
+ let charOffset = 0;
799
+ return lines.map((line, li) => {
800
+ const lineStart = charOffset;
801
+ charOffset += line.length + 1;
802
+ const tokens = [];
803
+ const refRegex = /([A-Z]{2,4})#(\d{4,6})/g;
804
+ let m;
805
+ while ((m = refRegex.exec(line)) !== null) {
806
+ if (host.stickyResolver) {
807
+ const prefix = m[1], num = m[2], startIdx = m.index, matchText = m[0];
808
+ tokens.push({ idx: startIdx, len: matchText.length, render: () => /* @__PURE__ */ jsx(
809
+ "button",
810
+ {
811
+ onClick: (e) => {
812
+ e.stopPropagation();
813
+ openStickyRef(prefix, num);
814
+ },
815
+ className: "text-blue-700 hover:underline font-medium cursor-pointer",
816
+ children: matchText
817
+ },
818
+ `r-${li}-${startIdx}`
819
+ ) });
820
+ }
821
+ }
822
+ const cbRegex = /\[([ xX]?)\]/g;
823
+ while ((m = cbRegex.exec(line)) !== null) {
824
+ const isChecked = m[1] === "x" || m[1] === "X";
825
+ const startIdx = m.index;
826
+ const contentCharIdx = lineStart + startIdx;
827
+ tokens.push({ idx: startIdx, len: m[0].length, render: () => /* @__PURE__ */ jsx(
828
+ "button",
829
+ {
830
+ onClick: (e) => {
831
+ e.stopPropagation();
832
+ toggleStickyCheckbox(noteId, contentCharIdx);
833
+ },
834
+ className: `inline-flex items-center justify-center w-3.5 h-3.5 rounded border ${isChecked ? "bg-blue-500 border-blue-500 text-white" : "border-gray-500 bg-white/50 hover:border-blue-400"} cursor-pointer align-text-bottom mr-0.5`,
835
+ children: isChecked && /* @__PURE__ */ jsx("svg", { className: "w-2.5 h-2.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) })
836
+ },
837
+ `c-${li}-${startIdx}`
838
+ ) });
839
+ }
840
+ tokens.sort((a, b) => a.idx - b.idx);
841
+ const parts = [];
842
+ let lastIdx = 0;
843
+ for (const t of tokens) {
844
+ if (t.idx > lastIdx) parts.push(/* @__PURE__ */ jsx("span", { children: line.slice(lastIdx, t.idx) }, `t-${li}-${lastIdx}`));
845
+ parts.push(t.render());
846
+ lastIdx = t.idx + t.len;
847
+ }
848
+ if (lastIdx < line.length) parts.push(/* @__PURE__ */ jsx("span", { children: line.slice(lastIdx) }, `t-${li}-${lastIdx}`));
849
+ if (parts.length === 0) parts.push(/* @__PURE__ */ jsx("span", { children: "\u200B" }, `e-${li}`));
850
+ const lineHasChecked = /^\[x\]/i.test(line.trimStart());
851
+ return /* @__PURE__ */ jsx("div", { className: lineHasChecked ? "line-through opacity-50" : "", children: parts }, li);
852
+ });
853
+ };
854
+ const [dragging, setDragging] = useState(null);
855
+ const [contextMenu, setContextMenu] = useState(null);
856
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
857
+ const [rubberBand, setRubberBand] = useState(null);
858
+ const [openFolder, setOpenFolder] = useState(null);
859
+ const [aboutOpen, setAboutOpen] = useState(false);
860
+ const [whatsNewOpen, setWhatsNewOpen] = useState(false);
861
+ const [renamingFolder, setRenamingFolder] = useState(null);
862
+ const [renameValue, setRenameValue] = useState("");
863
+ const [editingStickyId, setEditingStickyId] = useState(null);
864
+ const [stickyDrag, setStickyDrag] = useState(null);
865
+ const [stickyResize, setStickyResize] = useState(null);
866
+ const saveDocs = useCallback((docs) => {
867
+ host.saveShortcuts?.(docs);
868
+ }, [host]);
869
+ const saveFolders = useCallback((f) => {
870
+ host.saveFolders?.(f);
871
+ }, [host]);
872
+ useCallback((v) => {
873
+ host.saveSnap?.(v);
874
+ }, [host]);
875
+ const getDefaultPos = (idx) => {
876
+ const col = Math.floor(idx / 8);
877
+ const row = idx % 8;
878
+ return { right: 20 + col * GRID, top: 20 + row * GRID };
879
+ };
880
+ const getItemPos = (item, idx) => {
881
+ if (item.x != null && item.y != null) return { right: item.x, top: item.y };
882
+ return getDefaultPos(idx);
883
+ };
884
+ const getFolderPos = (folder, idx) => {
885
+ if (folder.x != null && folder.y != null) return { right: folder.x, top: folder.y };
886
+ return getDefaultPos(favDocs.filter((d) => !d.folderId).length + idx);
887
+ };
888
+ const desktopItems = favDocs.filter((d) => !d.folderId);
889
+ const folderItems = (folderId) => favDocs.filter((d) => d.folderId === folderId);
890
+ const [localPositions, setLocalPositions] = useState({});
891
+ const dragElRef = useRef(null);
892
+ const startDrag = (type, idx, e) => {
893
+ if (e.button !== 0) return;
894
+ const items = type === "item" ? desktopItems : folders;
895
+ const item = items[idx];
896
+ const pos = type === "item" ? getItemPos(item, idx) : getFolderPos(item, idx);
897
+ setDragging({ type, idx, startX: e.clientX, startY: e.clientY, origX: pos.right, origY: pos.top });
898
+ dragElRef.current = e.target.closest("[data-desktop-icon]");
899
+ e.preventDefault();
900
+ };
901
+ useEffect(() => {
902
+ if (!dragging) return;
903
+ const el = dragElRef.current;
904
+ const move = (e) => {
905
+ const nr = dragging.origX - (e.clientX - dragging.startX);
906
+ const nt = dragging.origY + e.clientY - dragging.startY;
907
+ if (el) {
908
+ el.style.right = `${nr}px`;
909
+ el.style.top = `${nt}px`;
910
+ el.style.left = "auto";
911
+ el.style.zIndex = "100";
912
+ el.style.opacity = "0.7";
913
+ }
914
+ };
915
+ const up = (e) => {
916
+ let finalRight = dragging.origX - (e.clientX - dragging.startX);
917
+ let finalTop = Math.max(0, dragging.origY + e.clientY - dragging.startY);
918
+ if (snapEnabled) {
919
+ const s = snapToGrid(finalRight, finalTop);
920
+ finalRight = s.x;
921
+ finalTop = s.y;
922
+ }
923
+ finalRight = Math.max(0, finalRight);
924
+ if (el) {
925
+ el.style.zIndex = "";
926
+ el.style.opacity = "";
927
+ }
928
+ if (dragging.type === "item") {
929
+ const droppedOnFolder = folders.find((f, fi) => {
930
+ const fp = getFolderPos(f, fi);
931
+ return Math.abs(finalRight - fp.right) < 40 && Math.abs(finalTop - fp.top) < 40;
932
+ });
933
+ const updated = [...favDocs];
934
+ const desktopIdx = favDocs.indexOf(desktopItems[dragging.idx]);
935
+ if (droppedOnFolder) {
936
+ updated[desktopIdx] = { ...updated[desktopIdx], folderId: droppedOnFolder.id, x: void 0, y: void 0 };
937
+ } else {
938
+ updated[desktopIdx] = { ...updated[desktopIdx], x: finalRight, y: finalTop, folderId: void 0 };
939
+ setLocalPositions((prev) => ({ ...prev, [`item-${desktopIdx}`]: { right: finalRight, top: finalTop } }));
940
+ }
941
+ saveDocs(updated);
942
+ } else {
943
+ const updated = [...folders];
944
+ updated[dragging.idx] = { ...updated[dragging.idx], x: finalRight, y: finalTop };
945
+ setLocalPositions((prev) => ({ ...prev, [`folder-${dragging.idx}`]: { right: finalRight, top: finalTop } }));
946
+ saveFolders(updated);
947
+ }
948
+ setDragging(null);
949
+ dragElRef.current = null;
950
+ };
951
+ window.addEventListener("pointermove", move);
952
+ window.addEventListener("pointerup", up);
953
+ return () => {
954
+ window.removeEventListener("pointermove", move);
955
+ window.removeEventListener("pointerup", up);
956
+ };
957
+ }, [dragging, snapEnabled, favDocs, folders, desktopItems]);
958
+ const favDocsKey = JSON.stringify(favDocs.map((d) => d.entityId));
959
+ const foldersKey = JSON.stringify(folders.map((f) => f.id));
960
+ useEffect(() => {
961
+ setLocalPositions({});
962
+ }, [favDocsKey, foldersKey]);
963
+ const startRubberBand = (e) => {
964
+ if (e.button !== 0 || e.target !== containerRef.current) return;
965
+ const rect = containerRef.current.getBoundingClientRect();
966
+ const x = e.clientX - rect.left;
967
+ const y = e.clientY - rect.top;
968
+ setRubberBand({ startX: x, startY: y, endX: x, endY: y });
969
+ setSelected(/* @__PURE__ */ new Set());
970
+ };
971
+ useEffect(() => {
972
+ if (!rubberBand) return;
973
+ const move = (e) => {
974
+ const rect = containerRef.current?.getBoundingClientRect();
975
+ if (!rect) return;
976
+ setRubberBand((prev) => prev ? { ...prev, endX: e.clientX - rect.left, endY: e.clientY - rect.top } : null);
977
+ };
978
+ const up = () => {
979
+ if (rubberBand) {
980
+ const minX = Math.min(rubberBand.startX, rubberBand.endX);
981
+ const maxX = Math.max(rubberBand.startX, rubberBand.endX);
982
+ const minY = Math.min(rubberBand.startY, rubberBand.endY);
983
+ const maxY = Math.max(rubberBand.startY, rubberBand.endY);
984
+ const sel = /* @__PURE__ */ new Set();
985
+ const cw = containerRef.current?.clientWidth || 800;
986
+ desktopItems.forEach((item, i) => {
987
+ const pos = getItemPos(item, i);
988
+ const leftX = cw - pos.right - 80;
989
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
990
+ sel.add(`item-${i}`);
991
+ }
992
+ });
993
+ folders.forEach((f, i) => {
994
+ const pos = getFolderPos(f, i);
995
+ const leftX = cw - pos.right - 80;
996
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
997
+ sel.add(`folder-${i}`);
998
+ }
999
+ });
1000
+ setSelected(sel);
1001
+ }
1002
+ setRubberBand(null);
1003
+ };
1004
+ window.addEventListener("pointermove", move);
1005
+ window.addEventListener("pointerup", up);
1006
+ return () => {
1007
+ window.removeEventListener("pointermove", move);
1008
+ window.removeEventListener("pointerup", up);
1009
+ };
1010
+ }, [rubberBand]);
1011
+ const handleDesktopContextMenu = (e) => {
1012
+ e.preventDefault();
1013
+ setContextMenu({ x: e.clientX, y: e.clientY });
1014
+ };
1015
+ const handleItemContextMenu = (e, itemIdx) => {
1016
+ e.preventDefault();
1017
+ e.stopPropagation();
1018
+ setContextMenu({ x: e.clientX, y: e.clientY, itemIdx });
1019
+ };
1020
+ const handleFolderContextMenu = (e, folderIdx) => {
1021
+ e.preventDefault();
1022
+ e.stopPropagation();
1023
+ setContextMenu({ x: e.clientX, y: e.clientY, folderIdx });
1024
+ };
1025
+ useEffect(() => {
1026
+ if (!contextMenu) return;
1027
+ const handler = () => setContextMenu(null);
1028
+ window.addEventListener("click", handler);
1029
+ return () => window.removeEventListener("click", handler);
1030
+ }, [contextMenu]);
1031
+ const createFolder = () => {
1032
+ const rect = containerRef.current?.getBoundingClientRect();
1033
+ const x = contextMenu ? contextMenu.x - (rect?.left || 0) : 100;
1034
+ const y = contextMenu ? contextMenu.y - (rect?.top || 0) : 100;
1035
+ const id = `folder-${Date.now()}`;
1036
+ saveFolders([...folders, { id, name: "New Folder", x, y }]);
1037
+ setContextMenu(null);
1038
+ setRenamingFolder(id);
1039
+ setRenameValue("New Folder");
1040
+ };
1041
+ const doSnapAll = () => {
1042
+ const rect = containerRef.current?.getBoundingClientRect();
1043
+ const h = rect?.height || 600;
1044
+ const colCount = Math.max(1, Math.floor(h / GRID));
1045
+ let slot = 0;
1046
+ const getSlotPos = () => {
1047
+ const col = Math.floor(slot / colCount);
1048
+ const row = slot % colCount;
1049
+ slot++;
1050
+ return { right: 20 + col * GRID, top: 10 + row * GRID };
1051
+ };
1052
+ const updatedF = folders.map((f) => {
1053
+ const pos = getSlotPos();
1054
+ return { ...f, x: pos.right, y: pos.top };
1055
+ });
1056
+ saveFolders(updatedF);
1057
+ const updated = favDocs.map((d) => {
1058
+ if (d.folderId) return d;
1059
+ const pos = getSlotPos();
1060
+ return { ...d, x: pos.right, y: pos.top };
1061
+ });
1062
+ saveDocs(updated);
1063
+ setContextMenu(null);
1064
+ };
1065
+ const removeItem = (idx) => {
1066
+ const desktopIdx = favDocs.indexOf(desktopItems[idx]);
1067
+ saveDocs(favDocs.filter((_, i) => i !== desktopIdx));
1068
+ setContextMenu(null);
1069
+ };
1070
+ const removeFolder = (idx) => {
1071
+ const folder = folders[idx];
1072
+ const updated = favDocs.map((d) => d.folderId === folder.id ? { ...d, folderId: void 0 } : d);
1073
+ saveDocs(updated);
1074
+ saveFolders(folders.filter((_, i) => i !== idx));
1075
+ setContextMenu(null);
1076
+ };
1077
+ const renameFolder = (id, name) => {
1078
+ saveFolders(folders.map((f) => f.id === id ? { ...f, name } : f));
1079
+ setRenamingFolder(null);
1080
+ };
1081
+ const saveNotes = useCallback((updated) => {
1082
+ host.saveNotes?.(updated);
1083
+ }, [host]);
1084
+ const createStickyNote = () => {
1085
+ const rect = containerRef.current?.getBoundingClientRect();
1086
+ const x = contextMenu ? contextMenu.x - (rect?.left || 0) : 100;
1087
+ const y = contextMenu ? contextMenu.y - (rect?.top || 0) : 100;
1088
+ const n = {
1089
+ id: `note-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
1090
+ title: "",
1091
+ content: "",
1092
+ color: "yellow",
1093
+ sticky: true,
1094
+ sticky_x: x,
1095
+ sticky_y: y,
1096
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1097
+ };
1098
+ saveNotes([n, ...allNotes]);
1099
+ setEditingStickyId(n.id);
1100
+ setContextMenu(null);
1101
+ };
1102
+ const updateStickyContent = (id, content) => {
1103
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, content, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : n));
1104
+ };
1105
+ const removeStickyFromDesktop = (id) => {
1106
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, sticky: false } : n));
1107
+ };
1108
+ const deleteStickyNote = (id) => {
1109
+ saveNotes(allNotes.filter((n) => n.id !== id));
1110
+ };
1111
+ const cycleStickyColor = (id) => {
1112
+ const STICKY_COLORS = ["yellow", "blue", "green", "pink", "purple", "orange"];
1113
+ const note = allNotes.find((n) => n.id === id);
1114
+ if (!note) return;
1115
+ const idx = STICKY_COLORS.indexOf(note.color);
1116
+ const next = STICKY_COLORS[(idx + 1) % STICKY_COLORS.length];
1117
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, color: next } : n));
1118
+ };
1119
+ const toggleStickyOnTop = (id) => {
1120
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, sticky_on_top: !n.sticky_on_top } : n));
1121
+ };
1122
+ useEffect(() => {
1123
+ if (!stickyDrag) return;
1124
+ const move = (e) => {
1125
+ const el = document.querySelector(`[data-sticky-id="${stickyDrag.id}"]`);
1126
+ if (el) {
1127
+ el.style.left = `${stickyDrag.origX + e.clientX - stickyDrag.startX}px`;
1128
+ el.style.right = "auto";
1129
+ el.style.top = `${stickyDrag.origY + e.clientY - stickyDrag.startY}px`;
1130
+ }
1131
+ };
1132
+ const up = (e) => {
1133
+ const finalX = stickyDrag.origX + e.clientX - stickyDrag.startX;
1134
+ const finalY = Math.max(0, stickyDrag.origY + e.clientY - stickyDrag.startY);
1135
+ const el = document.querySelector(`[data-sticky-id="${stickyDrag.id}"]`);
1136
+ const noteW = el?.offsetWidth ?? 192;
1137
+ const centerX = finalX + noteW / 2;
1138
+ const anchor = centerX > window.innerWidth / 2 ? "right" : "left";
1139
+ const xVal = anchor === "right" ? window.innerWidth - finalX - noteW : finalX;
1140
+ saveNotes(allNotes.map((n) => n.id === stickyDrag.id ? { ...n, sticky_x: xVal, sticky_y: finalY, sticky_anchor: anchor } : n));
1141
+ if (el) {
1142
+ el.style.left = "";
1143
+ el.style.right = "";
1144
+ }
1145
+ setStickyDrag(null);
1146
+ };
1147
+ window.addEventListener("pointermove", move);
1148
+ window.addEventListener("pointerup", up);
1149
+ return () => {
1150
+ window.removeEventListener("pointermove", move);
1151
+ window.removeEventListener("pointerup", up);
1152
+ };
1153
+ }, [stickyDrag, allNotes]);
1154
+ useEffect(() => {
1155
+ if (!stickyResize) return;
1156
+ const move = (e) => {
1157
+ const el = document.querySelector(`[data-sticky-id="${stickyResize.id}"]`);
1158
+ if (el) {
1159
+ el.style.width = `${Math.max(140, stickyResize.origW + e.clientX - stickyResize.startX)}px`;
1160
+ el.style.height = `${Math.max(100, stickyResize.origH + e.clientY - stickyResize.startY)}px`;
1161
+ }
1162
+ };
1163
+ const up = (e) => {
1164
+ const finalW = Math.max(140, stickyResize.origW + e.clientX - stickyResize.startX);
1165
+ const finalH = Math.max(100, stickyResize.origH + e.clientY - stickyResize.startY);
1166
+ saveNotes(allNotes.map((n) => n.id === stickyResize.id ? { ...n, sticky_w: finalW, sticky_h: finalH } : n));
1167
+ setStickyResize(null);
1168
+ };
1169
+ window.addEventListener("pointermove", move);
1170
+ window.addEventListener("pointerup", up);
1171
+ return () => {
1172
+ window.removeEventListener("pointermove", move);
1173
+ window.removeEventListener("pointerup", up);
1174
+ };
1175
+ }, [stickyResize, allNotes]);
1176
+ const STICKY_BG = {
1177
+ yellow: "bg-yellow-100 border-yellow-300",
1178
+ blue: "bg-blue-100 border-blue-300",
1179
+ green: "bg-green-100 border-green-300",
1180
+ pink: "bg-pink-100 border-pink-300",
1181
+ purple: "bg-purple-100 border-purple-300",
1182
+ orange: "bg-orange-100 border-orange-300"
1183
+ };
1184
+ const renderIcon = (entityType, label, isSelected, entityId) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1185
+ entityType === "folder" ? /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]", viewBox: "0 0 48 48", children: [
1186
+ /* @__PURE__ */ jsx("path", { d: "M6 12a4 4 0 014-4h10l4 4h14a4 4 0 014 4v20a4 4 0 01-4 4H10a4 4 0 01-4-4V12z", fill: "white", stroke: "#eab308", strokeWidth: "2", strokeLinejoin: "round" }),
1187
+ /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1188
+ ] }) }) : entityType === "page" ? /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: (() => {
1189
+ const icon = navIcons[label] || (entityId ? navIcons[entityId] : void 0);
1190
+ if (icon && isValidElement(icon)) {
1191
+ return cloneElement(icon, { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]" });
1192
+ }
1193
+ return /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z" }) });
1194
+ })() }) : /* @__PURE__ */ jsxs("div", { className: `w-12 h-12 relative flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: [
1195
+ /* @__PURE__ */ jsxs("svg", { className: `w-10 h-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)] ${ENTITY_ICON_COLORS[entityType] || "text-gray-500"}`, viewBox: "0 0 40 48", fill: "none", children: [
1196
+ /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", fill: "white", fillOpacity: "0.92" }),
1197
+ /* @__PURE__ */ jsx("path", { d: "M26 0l10 10H30a4 4 0 01-4-4V0z", fill: "currentColor", fillOpacity: "0.2" }),
1198
+ /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.5" })
1199
+ ] }),
1200
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 flex items-center justify-center text-[9px] font-bold pt-2 ${ENTITY_ICON_COLORS[entityType] || "text-gray-600"}`, children: ENTITY_ICONS[entityType] || entityType.slice(0, 3).toUpperCase() })
1201
+ ] }),
1202
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium text-center leading-tight w-full drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)] ${isSelected ? "text-blue-200 bg-blue-600/60 rounded px-1" : "text-white"}`, children: label })
1203
+ ] });
1204
+ const menuStyle = (x, y) => ({
1205
+ ...x + 180 > window.innerWidth ? { right: window.innerWidth - x } : { left: x },
1206
+ ...y + 250 > window.innerHeight ? { bottom: window.innerHeight - y } : { top: y }
1207
+ });
1208
+ return /* @__PURE__ */ jsxs(
1209
+ "div",
1210
+ {
1211
+ ref: containerRef,
1212
+ className: "flex-1 relative overflow-hidden",
1213
+ onPointerDown: startRubberBand,
1214
+ onContextMenu: handleDesktopContextMenu,
1215
+ onClick: () => {
1216
+ setSelected(/* @__PURE__ */ new Set());
1217
+ setContextMenu(null);
1218
+ },
1219
+ children: [
1220
+ desktopItems.map((doc, i) => {
1221
+ const docIdx = favDocs.indexOf(doc);
1222
+ const pos = localPositions[`item-${docIdx}`] || getItemPos(doc, i);
1223
+ const isSelected = selected.has(`item-${i}`);
1224
+ return /* @__PURE__ */ jsx(
1225
+ "div",
1226
+ {
1227
+ "data-desktop-icon": true,
1228
+ style: { position: "absolute", right: pos.right, top: pos.top, zIndex: 1 },
1229
+ onPointerDown: (e) => {
1230
+ e.stopPropagation();
1231
+ startDrag("item", i, e);
1232
+ },
1233
+ onClick: (e) => {
1234
+ e.stopPropagation();
1235
+ setSelected(/* @__PURE__ */ new Set([`item-${i}`]));
1236
+ },
1237
+ onContextMenu: (e) => handleItemContextMenu(e, i),
1238
+ onDoubleClick: (e) => {
1239
+ e.stopPropagation();
1240
+ doc.entityType === "page" ? openPage(doc.entityId) : openEntity(doc.entityType, doc.entityId, null, doc.label);
1241
+ },
1242
+ className: "cursor-default select-none",
1243
+ children: renderIcon(doc.entityType, doc.label, isSelected, doc.entityId)
1244
+ },
1245
+ `item-${doc.entityType}-${doc.entityId}-${i}`
1246
+ );
1247
+ }),
1248
+ folders.map((folder, i) => {
1249
+ const pos = localPositions[`folder-${i}`] || getFolderPos(folder, i);
1250
+ const isSelected = selected.has(`folder-${i}`);
1251
+ const itemCount = folderItems(folder.id).length;
1252
+ return /* @__PURE__ */ jsx(
1253
+ "div",
1254
+ {
1255
+ "data-desktop-icon": true,
1256
+ style: { position: "absolute", right: pos.right, top: pos.top, zIndex: 1 },
1257
+ onPointerDown: (e) => {
1258
+ e.stopPropagation();
1259
+ startDrag("folder", i, e);
1260
+ },
1261
+ onClick: (e) => {
1262
+ e.stopPropagation();
1263
+ setSelected(/* @__PURE__ */ new Set([`folder-${i}`]));
1264
+ },
1265
+ onContextMenu: (e) => handleFolderContextMenu(e, i),
1266
+ onDoubleClick: (e) => {
1267
+ e.stopPropagation();
1268
+ setOpenFolder(folder.id);
1269
+ },
1270
+ className: "cursor-default select-none",
1271
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1272
+ /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]", viewBox: "0 0 48 48", children: [
1273
+ /* @__PURE__ */ jsx("path", { d: "M6 12a4 4 0 014-4h10l4 4h14a4 4 0 014 4v20a4 4 0 01-4 4H10a4 4 0 01-4-4V12z", fill: "white", stroke: "#eab308", strokeWidth: "2", strokeLinejoin: "round" }),
1274
+ /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1275
+ ] }) }),
1276
+ renamingFolder === folder.id ? /* @__PURE__ */ jsx(
1277
+ "input",
1278
+ {
1279
+ autoFocus: true,
1280
+ value: renameValue,
1281
+ onChange: (e) => setRenameValue(e.target.value),
1282
+ onBlur: () => renameFolder(folder.id, renameValue),
1283
+ onKeyDown: (e) => {
1284
+ if (e.key === "Enter") renameFolder(folder.id, renameValue);
1285
+ if (e.key === "Escape") setRenamingFolder(null);
1286
+ },
1287
+ className: "text-[10px] w-full text-center bg-white/80 rounded px-1 outline-none",
1288
+ onClick: (e) => e.stopPropagation()
1289
+ }
1290
+ ) : /* @__PURE__ */ jsxs("span", { className: `text-[10px] font-medium text-center leading-tight w-full drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)] ${isSelected ? "text-blue-200 bg-blue-600/60 rounded px-1" : "text-white"}`, children: [
1291
+ folder.name,
1292
+ itemCount > 0 ? ` (${itemCount})` : ""
1293
+ ] })
1294
+ ] })
1295
+ },
1296
+ `folder-${folder.id}`
1297
+ );
1298
+ }),
1299
+ stickyNotes.map((note) => {
1300
+ const xVal = note.sticky_x ?? 100;
1301
+ const y = note.sticky_y ?? 100;
1302
+ const w = note.sticky_w ?? 192;
1303
+ const h = note.sticky_h ?? 148;
1304
+ const anchor = note.sticky_anchor || "left";
1305
+ const bg = STICKY_BG[note.color] || STICKY_BG.yellow;
1306
+ const isEditing = editingStickyId === note.id;
1307
+ return /* @__PURE__ */ jsxs(
1308
+ "div",
1309
+ {
1310
+ "data-sticky-id": note.id,
1311
+ style: { position: "absolute", ...anchor === "right" ? { right: xVal } : { left: xVal }, top: y, width: w, height: h, zIndex: note.sticky_on_top ? 200 : isEditing ? 50 : 10 },
1312
+ onClick: (e) => e.stopPropagation(),
1313
+ className: `rounded-lg shadow-lg border ${bg} select-none flex flex-col`,
1314
+ children: [
1315
+ /* @__PURE__ */ jsxs(
1316
+ "div",
1317
+ {
1318
+ className: "flex items-center justify-between px-2 py-1 cursor-move border-b border-black/5",
1319
+ onPointerDown: (e) => {
1320
+ if (e.button !== 0) return;
1321
+ e.preventDefault();
1322
+ e.stopPropagation();
1323
+ const el = e.target.closest("[data-sticky-id]");
1324
+ const actualLeft = el ? el.getBoundingClientRect().left - (containerRef.current?.getBoundingClientRect().left ?? 0) : anchor === "right" ? window.innerWidth - xVal - w : xVal;
1325
+ setStickyDrag({ id: note.id, startX: e.clientX, startY: e.clientY, origX: actualLeft, origY: y });
1326
+ },
1327
+ children: [
1328
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
1329
+ "button",
1330
+ {
1331
+ onClick: (e) => {
1332
+ e.stopPropagation();
1333
+ cycleStickyColor(note.id);
1334
+ },
1335
+ title: "Change color",
1336
+ className: "w-3 h-3 rounded-full bg-black/10 hover:bg-black/20 transition-colors"
1337
+ }
1338
+ ) }),
1339
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1340
+ /* @__PURE__ */ jsx(
1341
+ "button",
1342
+ {
1343
+ onClick: (e) => {
1344
+ e.stopPropagation();
1345
+ toggleStickyOnTop(note.id);
1346
+ },
1347
+ title: note.sticky_on_top ? "Remove from top" : "Always on top",
1348
+ className: `p-0.5 rounded transition-colors ${note.sticky_on_top ? "text-blue-500" : "text-black/20 hover:text-black/50"}`,
1349
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: [
1350
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.914 5.046a5.25 5.25 0 01-1.414 0M15.75 9v6" }),
1351
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12.75 12l3 3 3-3" })
1352
+ ] })
1353
+ }
1354
+ ),
1355
+ /* @__PURE__ */ jsx(
1356
+ "button",
1357
+ {
1358
+ onClick: (e) => {
1359
+ e.stopPropagation();
1360
+ removeStickyFromDesktop(note.id);
1361
+ },
1362
+ title: "Unpin from desktop",
1363
+ className: "p-0.5 rounded text-black/20 hover:text-black/50 transition-colors",
1364
+ children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 12h-15" }) })
1365
+ }
1366
+ ),
1367
+ /* @__PURE__ */ jsx(
1368
+ "button",
1369
+ {
1370
+ onClick: (e) => {
1371
+ e.stopPropagation();
1372
+ deleteStickyNote(note.id);
1373
+ },
1374
+ title: "Delete note",
1375
+ className: "p-0.5 rounded text-black/20 hover:text-red-500 transition-colors",
1376
+ children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
1377
+ }
1378
+ )
1379
+ ] })
1380
+ ]
1381
+ }
1382
+ ),
1383
+ isEditing ? /* @__PURE__ */ jsx(
1384
+ "textarea",
1385
+ {
1386
+ autoFocus: true,
1387
+ defaultValue: note.content,
1388
+ onBlur: (e) => {
1389
+ updateStickyContent(note.id, e.target.value);
1390
+ setEditingStickyId(null);
1391
+ },
1392
+ onKeyDown: (e) => {
1393
+ if (e.key === "Escape") {
1394
+ updateStickyContent(note.id, e.target.value);
1395
+ setEditingStickyId(null);
1396
+ }
1397
+ },
1398
+ className: "flex-1 w-full p-2 text-xs leading-relaxed bg-transparent outline-none resize-none placeholder:text-black/30",
1399
+ placeholder: "Write something..."
1400
+ }
1401
+ ) : /* @__PURE__ */ jsx("div", { className: "flex-1 p-2 cursor-text overflow-hidden", onDoubleClick: (e) => {
1402
+ e.stopPropagation();
1403
+ setEditingStickyId(note.id);
1404
+ }, children: /* @__PURE__ */ jsx("div", { className: "text-xs leading-relaxed text-black/70", children: note.content ? renderStickyContent(note.id, note.content) : /* @__PURE__ */ jsx("span", { className: "text-black/30 italic", children: "Double-click to edit..." }) }) }),
1405
+ /* @__PURE__ */ jsx(
1406
+ "div",
1407
+ {
1408
+ className: "absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize",
1409
+ onPointerDown: (e) => {
1410
+ if (e.button !== 0) return;
1411
+ e.preventDefault();
1412
+ e.stopPropagation();
1413
+ const el = e.target.closest("[data-sticky-id]");
1414
+ setStickyResize({ id: note.id, startX: e.clientX, startY: e.clientY, origW: el?.offsetWidth ?? w, origH: el?.offsetHeight ?? h });
1415
+ },
1416
+ children: /* @__PURE__ */ jsxs("svg", { className: "w-3 h-3 text-black/15 absolute bottom-0.5 right-0.5", viewBox: "0 0 12 12", fill: "currentColor", children: [
1417
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "1.5" }),
1418
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "10", r: "1.5" }),
1419
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "6", r: "1.5" })
1420
+ ] })
1421
+ }
1422
+ )
1423
+ ]
1424
+ },
1425
+ `sticky-${note.id}`
1426
+ );
1427
+ }),
1428
+ rubberBand && /* @__PURE__ */ jsx("div", { className: "absolute border border-blue-400 bg-blue-400/10 pointer-events-none", style: {
1429
+ left: Math.min(rubberBand.startX, rubberBand.endX),
1430
+ top: Math.min(rubberBand.startY, rubberBand.endY),
1431
+ width: Math.abs(rubberBand.endX - rubberBand.startX),
1432
+ height: Math.abs(rubberBand.endY - rubberBand.startY)
1433
+ } }),
1434
+ contextMenu && contextMenu.itemIdx == null && contextMenu.folderIdx == null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), children: [
1435
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: createStickyNote, children: [
1436
+ /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4 text-yellow-500", fill: "currentColor", viewBox: "0 0 24 24", children: [
1437
+ /* @__PURE__ */ jsx("path", { d: "M4 4a2 2 0 012-2h8a2 2 0 012 2v8a2 2 0 01-2 2H6a2 2 0 01-2-2V4z", opacity: "0.8" }),
1438
+ /* @__PURE__ */ jsx("path", { d: "M14 14v4a2 2 0 01-2 2H6a2 2 0 01-2-2v-6", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
1439
+ ] }),
1440
+ "New Sticky Note"
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: createFolder, children: [
1443
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-yellow-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" }) }),
1444
+ "Create Folder"
1445
+ ] }),
1446
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: doSnapAll, children: [
1447
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" }) }),
1448
+ "Snap to Grid"
1449
+ ] }),
1450
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
1451
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
1452
+ setContextMenu(null);
1453
+ openPage("/settings/customization");
1454
+ }, children: [
1455
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.53 16.122a3 3 0 00-5.78 1.128 2.25 2.25 0 01-2.4 2.245 4.5 4.5 0 008.4-2.245c0-.399-.078-.78-.22-1.128zm0 0a15.998 15.998 0 003.388-1.62m-5.043-.025a15.994 15.994 0 011.622-3.395m3.42 3.42a15.995 15.995 0 004.764-4.648l3.876-5.814a1.151 1.151 0 00-1.597-1.597L14.146 6.32a15.996 15.996 0 00-4.649 4.763m3.42 3.42a6.776 6.776 0 00-3.42-3.42" }) }),
1456
+ "Customization"
1457
+ ] }),
1458
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
1459
+ setContextMenu(null);
1460
+ openPage("/settings/favorites");
1461
+ }, children: [
1462
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" }) }),
1463
+ "Favorites"
1464
+ ] }),
1465
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
1466
+ setContextMenu(null);
1467
+ setAboutOpen(true);
1468
+ }, children: [
1469
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) }),
1470
+ "About ",
1471
+ host.productName ?? "this app"
1472
+ ] }),
1473
+ bugReport && /* @__PURE__ */ jsxs(Fragment, { children: [
1474
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
1475
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
1476
+ setContextMenu(null);
1477
+ reportBug(bugReport.submit);
1478
+ }, children: [
1479
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) }),
1480
+ "Report Bug"
1481
+ ] })
1482
+ ] })
1483
+ ] }),
1484
+ contextMenu && contextMenu.itemIdx != null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), minWidth: 160, children: [
1485
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
1486
+ const d = desktopItems[contextMenu.itemIdx];
1487
+ d.entityType === "page" ? openPage(d.entityId) : openEntity(d.entityType, d.entityId, null, d.label);
1488
+ setContextMenu(null);
1489
+ }, children: "Open" }),
1490
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
1491
+ /* @__PURE__ */ jsx(PopupMenuItem, { danger: true, onClick: () => removeItem(contextMenu.itemIdx), children: "Remove Shortcut" })
1492
+ ] }),
1493
+ contextMenu && contextMenu.folderIdx != null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), minWidth: 160, children: [
1494
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
1495
+ setOpenFolder(folders[contextMenu.folderIdx].id);
1496
+ setContextMenu(null);
1497
+ }, children: "Open" }),
1498
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
1499
+ setRenamingFolder(folders[contextMenu.folderIdx].id);
1500
+ setRenameValue(folders[contextMenu.folderIdx].name);
1501
+ setContextMenu(null);
1502
+ }, children: "Rename" }),
1503
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
1504
+ /* @__PURE__ */ jsx(PopupMenuItem, { danger: true, onClick: () => removeFolder(contextMenu.folderIdx), children: "Delete Folder" })
1505
+ ] }),
1506
+ openFolder && (() => {
1507
+ const folder = folders.find((f) => f.id === openFolder);
1508
+ if (!folder) return null;
1509
+ const items = folderItems(openFolder);
1510
+ return /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setOpenFolder(null), title: folder.name, size: "lg", children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 text-center py-8", children: "Folder is empty. Drag documents here." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3 p-2", children: items.map((item, i) => /* @__PURE__ */ jsxs(
1511
+ "div",
1512
+ {
1513
+ className: "group relative flex flex-col items-center gap-1 w-20 p-2 rounded-lg hover:bg-gray-100 cursor-default",
1514
+ onDoubleClick: () => openEntity(item.entityType, item.entityId, null, item.label),
1515
+ children: [
1516
+ /* @__PURE__ */ jsx(
1517
+ "button",
1518
+ {
1519
+ onClick: (e) => {
1520
+ e.stopPropagation();
1521
+ const updated = favDocs.map((d) => d.entityType === item.entityType && d.entityId === item.entityId && d.folderId === openFolder ? { ...d, folderId: void 0 } : d);
1522
+ saveDocs(updated);
1523
+ },
1524
+ title: "Move to Desktop",
1525
+ className: "absolute -top-1 -right-1 h-5 w-5 rounded-full bg-gray-200 text-gray-500 flex items-center justify-center text-[10px] opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-100 hover:text-red-600 shadow-sm z-10",
1526
+ children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" }) })
1527
+ }
1528
+ ),
1529
+ /* @__PURE__ */ jsx("div", { className: `w-12 h-12 rounded-lg bg-gray-50 shadow flex items-center justify-center text-xs font-bold ${ENTITY_ICON_COLORS[item.entityType] || "text-gray-600"}`, children: ENTITY_ICONS[item.entityType] || item.entityType.slice(0, 3).toUpperCase() }),
1530
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-700 font-medium text-center leading-tight truncate w-full", children: item.label })
1531
+ ]
1532
+ },
1533
+ i
1534
+ )) }) });
1535
+ })(),
1536
+ aboutOpen && (() => {
1537
+ const version = APP_VERSION;
1538
+ (profile?.preferences || {}).show_desktop_version ?? true;
1539
+ return /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setAboutOpen(false), title: `About ${host.productName ?? "this app"}`, size: "sm", bodyScroll: false, compact: true, dimensions: [340, 420], children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
1540
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 pt-4 pb-3 w-full", children: [
1541
+ /* @__PURE__ */ jsx("img", { src: host.productIcon ?? "/favicon.svg", alt: "", className: "h-16 w-16" }),
1542
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1543
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-gray-900 tracking-wide", children: host.productName ?? "react-os-shell" }),
1544
+ host.productTagline && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: host.productTagline }),
1545
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-gray-400 mt-1 font-mono", children: version })
1546
+ ] })
1547
+ ] }),
1548
+ /* @__PURE__ */ jsxs("div", { className: "py-3 border-t border-gray-200 w-full px-4", children: [
1549
+ /* @__PURE__ */ jsx("h3", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-wide mb-2 text-center", children: "Open Source Licenses" }),
1550
+ /* @__PURE__ */ jsx("div", { className: "max-h-40 overflow-y-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-[11px]", children: [
1551
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-gray-500 border-b border-gray-100", children: [
1552
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Package" }),
1553
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "License" })
1554
+ ] }) }),
1555
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-50", children: [
1556
+ ["React", "MIT", "facebook/react"],
1557
+ ["React DOM", "MIT", "facebook/react"],
1558
+ ["React Router", "MIT", "remix-run/react-router"],
1559
+ ["TanStack Query", "MIT", "TanStack/query"],
1560
+ ["TanStack Table", "MIT", "TanStack/table"],
1561
+ ["React Hook Form", "MIT", "react-hook-form"],
1562
+ ["Axios", "MIT", "axios/axios"],
1563
+ ["Tailwind CSS", "MIT", "tailwindlabs/tailwindcss"],
1564
+ ["Headless UI", "MIT", "tailwindlabs/headlessui"],
1565
+ ["Heroicons", "MIT", "tailwindlabs/heroicons"],
1566
+ ["Recharts", "MIT", "recharts/recharts"],
1567
+ ["Zod", "MIT", "colinhacks/zod"],
1568
+ ["Vite", "MIT", "vitejs/vite"],
1569
+ ["TypeScript", "Apache-2.0", "microsoft/TypeScript"],
1570
+ ["jSpreadsheet CE", "MIT", "jspreadsheet/ce"],
1571
+ ["Django", "BSD-3", "django/django"],
1572
+ ["Django REST Framework", "BSD-3", "encode/django-rest-framework"],
1573
+ ["PostgreSQL", "PostgreSQL", "postgres/postgres"],
1574
+ ["Gunicorn", "MIT", "benoitc/gunicorn"]
1575
+ ].map(([name, license, repo]) => /* @__PURE__ */ jsxs("tr", { className: "text-gray-600", children: [
1576
+ /* @__PURE__ */ jsx("td", { className: "py-1", children: /* @__PURE__ */ jsx("a", { href: `https://github.com/${repo}`, target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 hover:underline", children: name }) }),
1577
+ /* @__PURE__ */ jsx("td", { className: "py-1 text-gray-400", children: license })
1578
+ ] }, name)) })
1579
+ ] }) })
1580
+ ] }),
1581
+ (host.productCopyright || host.productWebsite) && /* @__PURE__ */ jsxs("div", { className: "pt-3 pb-2 border-t border-gray-200 w-full text-center", children: [
1582
+ host.productCopyright && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-400", children: host.productCopyright }),
1583
+ host.productWebsite && /* @__PURE__ */ jsx("a", { href: host.productWebsite, target: "_blank", rel: "noopener noreferrer", className: "text-[10px] text-blue-500 hover:underline", children: host.productWebsite.replace(/^https?:\/\//, "") })
1584
+ ] })
1585
+ ] }) });
1586
+ })(),
1587
+ ((profile?.preferences || {}).show_desktop_version ?? true) && /* @__PURE__ */ jsx(
1588
+ "button",
1589
+ {
1590
+ onClick: (e) => {
1591
+ e.stopPropagation();
1592
+ setWhatsNewOpen(true);
1593
+ },
1594
+ className: `absolute bottom-3 text-[10px] text-white/50 font-mono select-none drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)] hover:text-white/80 transition-colors cursor-pointer ${(profile?.preferences || {}).taskbar_position === "top" ? "right-3" : (profile?.preferences || {}).taskbar_position === "left" ? "right-3" : (profile?.preferences || {}).taskbar_position === "right" ? "left-3" : "right-3 !bottom-16"}`,
1595
+ children: APP_VERSION
1596
+ }
1597
+ ),
1598
+ whatsNewOpen && /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setWhatsNewOpen(false), title: "What's New", size: "md", bodyScroll: false, children: /* @__PURE__ */ jsx("div", { className: "space-y-5 max-h-[60vh] overflow-y-auto px-1", children: changelog_default.map((entry, i) => /* @__PURE__ */ jsxs("div", { children: [
1599
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-2", children: [
1600
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-gray-900 font-mono", children: entry.version }),
1601
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: formatDate(entry.date) })
1602
+ ] }),
1603
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1.5 ml-1", children: entry.changes.map((change, j) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm text-gray-600", children: [
1604
+ /* @__PURE__ */ jsx("span", { className: "text-blue-500 mt-1 shrink-0", children: "\u2022" }),
1605
+ change
1606
+ ] }, j)) }),
1607
+ i < changelog_default.length - 1 && /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 mt-4" })
1608
+ ] }, entry.version)) }) })
1609
+ ]
1610
+ }
1611
+ );
1612
+ }
1613
+ var DEFAULT = { hasAnyPerm: () => true };
1614
+ var ShellAuthContext = createContext(DEFAULT);
1615
+ function ShellAuthProvider({ value, children }) {
1616
+ return /* @__PURE__ */ jsx(ShellAuthContext.Provider, { value, children });
1617
+ }
1618
+ function useShellAuth() {
1619
+ return useContext(ShellAuthContext);
1620
+ }
1621
+
1622
+ // src/contexts/AuthContext.tsx
1623
+ var _authBridge = {};
1624
+ function setShellAuthBridge(bridge) {
1625
+ _authBridge = bridge;
1626
+ }
1627
+ function useAuth() {
1628
+ const { hasAnyPerm } = useShellAuth();
1629
+ return {
1630
+ user: _authBridge.user,
1631
+ logout: _authBridge.logout ?? (() => {
1632
+ }),
1633
+ hasAnyPerm
1634
+ };
1635
+ }
1636
+ function subscribeMediaQuery(cb) {
1637
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1638
+ mq.addEventListener("change", cb);
1639
+ return () => mq.removeEventListener("change", cb);
1640
+ }
1641
+ function getSystemIsDark() {
1642
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
1643
+ }
1644
+ function hexToHsl(hex) {
1645
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
1646
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
1647
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
1648
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
1649
+ let h = 0, s = 0;
1650
+ const l = (max + min) / 2;
1651
+ if (max !== min) {
1652
+ const d = max - min;
1653
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
1654
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
1655
+ else if (max === g) h = ((b - r) / d + 2) / 6;
1656
+ else h = ((r - g) / d + 4) / 6;
1657
+ }
1658
+ return [h * 360, s * 100, l * 100];
1659
+ }
1660
+ function hslToHex(h, s, l) {
1661
+ s /= 100;
1662
+ l /= 100;
1663
+ const a = s * Math.min(l, 1 - l);
1664
+ const f = (n) => {
1665
+ const k = (n + h / 30) % 12;
1666
+ return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
1667
+ };
1668
+ const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
1669
+ return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
1670
+ }
1671
+ function useTheme() {
1672
+ const { prefs } = useShellPrefs();
1673
+ const systemDark = useSyncExternalStore(subscribeMediaQuery, getSystemIsDark);
1674
+ const saved = prefs.theme || "system";
1675
+ const accentColor = prefs.accent_color || null;
1676
+ const customBgColor = prefs.custom_bg_color || null;
1677
+ const customTitleColor = prefs.custom_title_color || null;
1678
+ const customWindowColor = prefs.custom_window_color || null;
1679
+ const customButtonColor = prefs.custom_button_color || null;
1680
+ const resolved = saved === "system" ? systemDark ? "dark" : "light" : saved;
1681
+ useEffect(() => {
1682
+ document.documentElement.setAttribute("data-theme", resolved);
1683
+ return () => document.documentElement.removeAttribute("data-theme");
1684
+ }, [resolved]);
1685
+ useEffect(() => {
1686
+ const root = document.documentElement;
1687
+ if (accentColor && /^#[0-9a-fA-F]{6}$/.test(accentColor)) {
1688
+ const [h, s, l] = hexToHsl(accentColor);
1689
+ root.setAttribute("data-custom-accent", "true");
1690
+ root.style.setProperty("--accent-600", accentColor);
1691
+ root.style.setProperty("--accent-700", hslToHex(h, s, Math.max(l - 10, 5)));
1692
+ root.style.setProperty("--accent-500", hslToHex(h, s, Math.min(l + 10, 95)));
1693
+ root.style.setProperty("--accent-400", hslToHex(h, s, Math.min(l + 20, 95)));
1694
+ root.style.setProperty("--accent-300", hslToHex(h, Math.min(s + 10, 100), Math.min(l + 30, 92)));
1695
+ root.style.setProperty("--accent-200", hslToHex(h, Math.min(s + 15, 100), Math.min(l + 38, 94)));
1696
+ root.style.setProperty("--accent-100", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 42, 96)));
1697
+ root.style.setProperty("--accent-50", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 46, 98)));
1698
+ } else {
1699
+ root.removeAttribute("data-custom-accent");
1700
+ ["--accent-600", "--accent-700", "--accent-500", "--accent-400", "--accent-300", "--accent-200", "--accent-100", "--accent-50"].forEach((p) => root.style.removeProperty(p));
1701
+ }
1702
+ }, [accentColor]);
1703
+ useEffect(() => {
1704
+ const root = document.documentElement;
1705
+ if (accentColor) {
1706
+ const hexToRgb = (hex) => {
1707
+ const r = parseInt(hex.slice(1, 3), 16);
1708
+ const g = parseInt(hex.slice(3, 5), 16);
1709
+ const b = parseInt(hex.slice(5, 7), 16);
1710
+ return `${r} ${g} ${b}`;
1711
+ };
1712
+ if (customBgColor) root.style.setProperty("--custom-bg-color", customBgColor);
1713
+ if (customTitleColor) {
1714
+ root.style.setProperty("--window-header-rgb", hexToRgb(customTitleColor));
1715
+ root.style.setProperty("--window-footer-rgb", hexToRgb(customTitleColor));
1716
+ }
1717
+ if (customWindowColor) root.style.setProperty("--window-content-rgb", hexToRgb(customWindowColor));
1718
+ if (customButtonColor) {
1719
+ root.style.setProperty("--custom-button-color", customButtonColor);
1720
+ const [h, s, l] = hexToHsl(customButtonColor);
1721
+ root.style.setProperty("--custom-button-hover", hslToHex(h, s, Math.max(l - 10, 5)));
1722
+ }
1723
+ } else {
1724
+ ["--custom-bg-color", "--custom-button-color", "--custom-button-hover"].forEach((p) => root.style.removeProperty(p));
1725
+ }
1726
+ }, [accentColor, customBgColor, customTitleColor, customWindowColor, customButtonColor]);
1727
+ return { theme: saved, resolved };
1728
+ }
1729
+ function GoogleConnectModal({ open, onClose }) {
1730
+ const { isConnected, user, connect, disconnect, loading, error, hasClientId, setClientId } = useGoogleAuth();
1731
+ const [clientIdInput, setClientIdInput] = useState("");
1732
+ const clientIdRef = useRef(null);
1733
+ return /* @__PURE__ */ jsxs(Modal, { open, onClose, title: "Google Services", size: "md", children: [
1734
+ /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1735
+ /* @__PURE__ */ jsx("div", { className: `rounded-lg border p-4 ${isConnected ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1736
+ /* @__PURE__ */ jsx("div", { className: `h-10 w-10 rounded-full flex items-center justify-center ${isConnected ? "bg-green-100" : "bg-gray-200"}`, children: isConnected && user?.picture ? /* @__PURE__ */ jsx("img", { src: user.picture, alt: "", className: "h-10 w-10 rounded-full" }) : /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-gray-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) }) }),
1737
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: isConnected ? /* @__PURE__ */ jsxs(Fragment, { children: [
1738
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-900", children: user?.name || "Connected" }),
1739
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-green-700 truncate", children: user?.email })
1740
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1741
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: "Not connected" }),
1742
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: "Sign in to access Google services" })
1743
+ ] }) }),
1744
+ isConnected && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700", children: [
1745
+ /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }),
1746
+ "Connected"
1747
+ ] })
1748
+ ] }) }),
1749
+ /* @__PURE__ */ jsxs("div", { children: [
1750
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-2", children: "Available Services" }),
1751
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [
1752
+ { icon: /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
1753
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z", fill: "#4285F4" }),
1754
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
1755
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
1756
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
1757
+ ] }), label: "Gmail", desc: "Read, compose, and send emails" },
1758
+ { icon: /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
1759
+ /* @__PURE__ */ jsx("path", { d: "M19.5 3.75H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V6a2.25 2.25 0 00-2.25-2.25z", fill: "none", stroke: "#4285F4", strokeWidth: 1.5 }),
1760
+ /* @__PURE__ */ jsx("path", { d: "M2.25 9h19.5M6.75 3.75v3m4.5-3v3m4.5-3v3", fill: "none", stroke: "#4285F4", strokeWidth: 1.5, strokeLinecap: "round" })
1761
+ ] }), label: "Calendar", desc: "View and create calendar events" },
1762
+ { icon: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5", fill: "none", stroke: "#8B5CF6", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }) }), label: "Gemini AI", desc: "Chat with Google AI assistant" }
1763
+ ].map((svc) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-gray-200 px-4 py-3", children: [
1764
+ /* @__PURE__ */ jsx("div", { className: "shrink-0", children: svc.icon }),
1765
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1766
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: svc.label }),
1767
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: svc.desc })
1768
+ ] }),
1769
+ isConnected && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-green-500 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) })
1770
+ ] }, svc.label)) })
1771
+ ] }),
1772
+ !hasClientId && /* @__PURE__ */ jsxs("div", { className: "space-y-2 bg-amber-50 border border-amber-200 rounded-lg p-4", children: [
1773
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-amber-800", children: "Setup required: Google OAuth Client ID" }),
1774
+ /* @__PURE__ */ jsx(
1775
+ "input",
1776
+ {
1777
+ ref: clientIdRef,
1778
+ value: clientIdInput,
1779
+ onChange: (e) => setClientIdInput(e.target.value),
1780
+ onInput: (e) => setClientIdInput(e.target.value),
1781
+ placeholder: "123456789.apps.googleusercontent.com",
1782
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500"
1783
+ }
1784
+ ),
1785
+ /* @__PURE__ */ jsx(
1786
+ "button",
1787
+ {
1788
+ type: "button",
1789
+ onPointerDown: (e) => e.stopPropagation(),
1790
+ onClick: () => {
1791
+ const val = (clientIdRef.current?.value || clientIdInput).trim();
1792
+ if (val) {
1793
+ setClientId(val);
1794
+ setClientIdInput(val);
1795
+ }
1796
+ },
1797
+ className: "w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 cursor-pointer",
1798
+ children: "Save Client ID"
1799
+ }
1800
+ ),
1801
+ /* @__PURE__ */ jsxs("div", { className: "text-[10px] text-gray-500 space-y-0.5 mt-1", children: [
1802
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-600", children: "Setup instructions:" }),
1803
+ /* @__PURE__ */ jsxs("p", { children: [
1804
+ "1. Go to ",
1805
+ /* @__PURE__ */ jsx("a", { href: "https://console.cloud.google.com/apis/credentials", target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 hover:underline", children: "console.cloud.google.com > APIs & Services > Credentials" })
1806
+ ] }),
1807
+ /* @__PURE__ */ jsxs("p", { children: [
1808
+ "2. Click ",
1809
+ /* @__PURE__ */ jsx("b", { children: '"+ Create Credentials"' }),
1810
+ ", then select ",
1811
+ /* @__PURE__ */ jsx("b", { children: "OAuth client ID" })
1812
+ ] }),
1813
+ /* @__PURE__ */ jsxs("p", { children: [
1814
+ "3. Choose ",
1815
+ /* @__PURE__ */ jsx("b", { children: '"Web application"' }),
1816
+ ", give it any name, add the URL where this app runs (e.g. ",
1817
+ /* @__PURE__ */ jsx("b", { children: "http://localhost:5173" }),
1818
+ ") as an Authorized JavaScript origin, then click Create"
1819
+ ] }),
1820
+ /* @__PURE__ */ jsx("p", { children: "4. Copy your Client ID and paste it above" })
1821
+ ] })
1822
+ ] }),
1823
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: error })
1824
+ ] }),
1825
+ /* @__PURE__ */ jsx(ModalActions, { children: isConnected ? /* @__PURE__ */ jsx(
1826
+ "button",
1827
+ {
1828
+ onClick: disconnect,
1829
+ className: "inline-flex items-center gap-1.5 text-red-600 border border-red-200 bg-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-red-50",
1830
+ children: "Disconnect"
1831
+ }
1832
+ ) : hasClientId ? /* @__PURE__ */ jsxs(
1833
+ "button",
1834
+ {
1835
+ onClick: connect,
1836
+ disabled: loading,
1837
+ className: "inline-flex items-center gap-2 bg-white border border-gray-300 shadow-sm px-5 py-2 text-sm font-medium rounded-lg hover:bg-gray-50 disabled:opacity-50",
1838
+ children: [
1839
+ /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
1840
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z", fill: "#4285F4" }),
1841
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
1842
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
1843
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
1844
+ ] }),
1845
+ loading ? "Connecting..." : "Sign in with Google"
1846
+ ]
1847
+ }
1848
+ ) : null })
1849
+ ] });
1850
+ }
1851
+ function useClickOutside(ref, onClose) {
1852
+ useEffect(() => {
1853
+ const handler = (e) => {
1854
+ if (ref.current && !ref.current.contains(e.target)) onClose();
1855
+ };
1856
+ document.addEventListener("mousedown", handler);
1857
+ return () => document.removeEventListener("mousedown", handler);
1858
+ }, [ref, onClose]);
1859
+ }
1860
+ function StartupAnimation({ onComplete, ready = false, productName = "react-os-shell", subtitle }) {
1861
+ const [phase, setPhase] = useState("logo");
1862
+ const [minTimePassed, setMinTimePassed] = useState(false);
1863
+ const onCompleteRef = useRef(onComplete);
1864
+ onCompleteRef.current = onComplete;
1865
+ const fadingRef = useRef(false);
1866
+ useEffect(() => {
1867
+ const t1 = setTimeout(() => setPhase("text"), 600);
1868
+ const t2 = setTimeout(() => setMinTimePassed(true), 2e3);
1869
+ const t3 = setTimeout(() => {
1870
+ if (!fadingRef.current) {
1871
+ fadingRef.current = true;
1872
+ setPhase("fade");
1873
+ setTimeout(() => onCompleteRef.current(), 500);
1874
+ }
1875
+ }, 5e3);
1876
+ return () => {
1877
+ clearTimeout(t1);
1878
+ clearTimeout(t2);
1879
+ clearTimeout(t3);
1880
+ };
1881
+ }, []);
1882
+ useEffect(() => {
1883
+ if (!ready || !minTimePassed || fadingRef.current) return;
1884
+ fadingRef.current = true;
1885
+ setPhase("fade");
1886
+ const t = setTimeout(() => onCompleteRef.current(), 500);
1887
+ return () => clearTimeout(t);
1888
+ }, [ready, minTimePassed]);
1889
+ return /* @__PURE__ */ jsxs(
1890
+ "div",
1891
+ {
1892
+ className: `fixed inset-0 z-[9999] flex flex-col items-center justify-center transition-opacity duration-500 ${phase === "fade" ? "opacity-0 pointer-events-none" : "opacity-100"}`,
1893
+ style: { background: "linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)" },
1894
+ children: [
1895
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx(
1896
+ "div",
1897
+ {
1898
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full opacity-20",
1899
+ style: { background: "radial-gradient(circle, rgba(124,58,237,0.4) 0%, transparent 70%)", animation: "pulse-glow 2s ease-in-out infinite" }
1900
+ }
1901
+ ) }),
1902
+ /* @__PURE__ */ jsx("div", { className: `relative transition-all duration-700 ease-out ${phase === "logo" ? "scale-75 opacity-0" : "scale-100 opacity-100"}`, children: /* @__PURE__ */ jsx(
1903
+ "img",
1904
+ {
1905
+ src: "/favicon.svg",
1906
+ alt: "",
1907
+ className: "h-20 w-20 drop-shadow-[0_0_30px_rgba(124,58,237,0.5)]",
1908
+ style: { animation: "spin-in 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards" }
1909
+ }
1910
+ ) }),
1911
+ /* @__PURE__ */ jsxs("div", { className: `mt-6 text-center transition-all duration-500 ${phase !== "logo" ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`, children: [
1912
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-[0.3em] text-white/90 uppercase", children: productName }),
1913
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex items-center justify-center gap-1.5", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
1914
+ "div",
1915
+ {
1916
+ className: "w-1.5 h-1.5 rounded-full bg-purple-400",
1917
+ style: { animation: `dot-bounce 1s ease-in-out ${i * 0.15}s infinite` }
1918
+ },
1919
+ i
1920
+ )) })
1921
+ ] }),
1922
+ subtitle && /* @__PURE__ */ jsx("div", { className: `absolute bottom-8 transition-all duration-500 ${phase !== "logo" ? "opacity-40" : "opacity-0"}`, children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-white/40 font-mono tracking-wider", children: subtitle }) }),
1923
+ /* @__PURE__ */ jsx("style", { children: `
1924
+ @keyframes spin-in {
1925
+ 0% { transform: scale(0) rotate(-180deg); opacity: 0; }
1926
+ 100% { transform: scale(1) rotate(0deg); opacity: 1; }
1927
+ }
1928
+ @keyframes dot-bounce {
1929
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
1930
+ 40% { transform: scale(1); opacity: 1; }
1931
+ }
1932
+ @keyframes pulse-glow {
1933
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.15; }
1934
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.25; }
1935
+ }
1936
+ ` })
1937
+ ]
1938
+ }
1939
+ );
1940
+ }
1941
+ function LogoutAnimation({ onComplete, subtitle }) {
1942
+ const [phase, setPhase] = useState("show");
1943
+ useEffect(() => {
1944
+ playLogout();
1945
+ const t1 = setTimeout(() => setPhase("shrink"), 800);
1946
+ const t2 = setTimeout(() => setPhase("fade"), 1800);
1947
+ const t3 = setTimeout(onComplete, 2300);
1948
+ return () => {
1949
+ clearTimeout(t1);
1950
+ clearTimeout(t2);
1951
+ clearTimeout(t3);
1952
+ };
1953
+ }, [onComplete]);
1954
+ return /* @__PURE__ */ jsxs(
1955
+ "div",
1956
+ {
1957
+ className: `fixed inset-0 z-[9999] flex flex-col items-center justify-center transition-opacity duration-500 ${phase === "fade" ? "opacity-0" : "opacity-100"}`,
1958
+ style: { background: "linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)" },
1959
+ children: [
1960
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx(
1961
+ "div",
1962
+ {
1963
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full",
1964
+ style: {
1965
+ background: "radial-gradient(circle, rgba(124,58,237,0.4) 0%, transparent 70%)",
1966
+ animation: "logout-glow 2s ease-in-out infinite",
1967
+ opacity: phase === "shrink" ? 0 : 0.2,
1968
+ transition: "opacity 1s"
1969
+ }
1970
+ }
1971
+ ) }),
1972
+ /* @__PURE__ */ jsx(
1973
+ "div",
1974
+ {
1975
+ className: `relative transition-all duration-1000 ease-in ${phase === "shrink" ? "scale-0 opacity-0" : "scale-100 opacity-100"}`,
1976
+ style: phase === "shrink" ? { transform: "scale(0) rotate(180deg)" } : void 0,
1977
+ children: /* @__PURE__ */ jsx("img", { src: "/favicon.svg", alt: "", className: "h-20 w-20 drop-shadow-[0_0_30px_rgba(124,58,237,0.5)]" })
1978
+ }
1979
+ ),
1980
+ /* @__PURE__ */ jsxs("div", { className: `mt-6 text-center transition-all duration-700 ${phase === "show" ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`, children: [
1981
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-[0.3em] text-white/90 uppercase", children: "Goodbye" }),
1982
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-white/40", children: "See you next time" })
1983
+ ] }),
1984
+ subtitle && /* @__PURE__ */ jsx("div", { className: `absolute bottom-8 transition-all duration-500 ${phase === "show" ? "opacity-40" : "opacity-0"}`, children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-white/40 font-mono tracking-wider", children: subtitle }) }),
1985
+ /* @__PURE__ */ jsx("style", { children: `
1986
+ @keyframes logout-glow {
1987
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.15; }
1988
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.25; }
1989
+ }
1990
+ ` })
1991
+ ]
1992
+ }
1993
+ );
1994
+ }
1995
+ function StartMenu({
1996
+ open,
1997
+ onClose,
1998
+ openPage,
1999
+ profile,
2000
+ user,
2001
+ onLogout,
2002
+ taskbarPosition,
2003
+ taskbarH,
2004
+ taskbarW = 0,
2005
+ size = "medium",
2006
+ navSections: navSections2 = navSections,
2007
+ navIcons: navIcons2 = navIcons,
2008
+ sectionIcons: sectionIcons2 = sectionIcons,
2009
+ categories = startMenuCategories
2010
+ }) {
2011
+ const erpLabels = new Set(categories.erp);
2012
+ const systemLabels = new Set(categories.system);
2013
+ const virtualSections = categories.virtual ?? [];
2014
+ const virtualByLabel = Object.fromEntries(
2015
+ virtualSections.map((v) => [v.label, v])
2016
+ );
2017
+ const { hasAnyPerm } = useAuth();
2018
+ const [hoveredSection, setHoveredSection] = useState(null);
2019
+ const [hoveredY, setHoveredY] = useState(0);
2020
+ const [search, setSearch] = useState("");
2021
+ const [searchIdx, setSearchIdx] = useState(0);
2022
+ const menuRef = useRef(null);
2023
+ const hoverTimeout = useRef();
2024
+ useEffect(() => {
2025
+ if (!open) {
2026
+ setSearch("");
2027
+ setHoveredSection(null);
2028
+ setSearchIdx(0);
2029
+ }
2030
+ }, [open]);
2031
+ useEffect(() => {
2032
+ if (!open) return;
2033
+ const handler = (e) => {
2034
+ if (e.key === "Escape") onClose();
2035
+ };
2036
+ const click = (e) => {
2037
+ if (e.target.closest("[data-menu-toggle]")) return;
2038
+ if (menuRef.current && !menuRef.current.contains(e.target)) onClose();
2039
+ };
2040
+ window.addEventListener("keydown", handler);
2041
+ window.addEventListener("pointerdown", click);
2042
+ return () => {
2043
+ window.removeEventListener("keydown", handler);
2044
+ window.removeEventListener("pointerdown", click);
2045
+ };
2046
+ }, [open, onClose]);
2047
+ if (!open) return null;
2048
+ const handleClick = (path) => {
2049
+ openPage(path);
2050
+ onClose();
2051
+ };
2052
+ const isVertical = taskbarPosition !== "bottom";
2053
+ const topItems = navSections2.filter((item) => !isSection(item));
2054
+ const erpSections = navSections2.filter((item) => isSection(item) && erpLabels.has(item.label));
2055
+ const systemSections = navSections2.filter((item) => isSection(item) && systemLabels.has(item.label));
2056
+ const getVisibleItems = (section) => section.items.filter((item) => !item.perms || hasAnyPerm(item.perms));
2057
+ const searchResults = search.length >= 2 ? navSections2.flatMap((item) => {
2058
+ if (isSection(item)) {
2059
+ return item.items.filter((i) => (!i.perms || hasAnyPerm(i.perms)) && i.label.toLowerCase().includes(search.toLowerCase())).map((i) => ({ ...i, section: item.label }));
2060
+ }
2061
+ return item.label.toLowerCase().includes(search.toLowerCase()) ? [{ ...item, section: "" }] : [];
2062
+ }) : [];
2063
+ const posStyle = taskbarPosition === "top" ? { top: taskbarH + 8, left: 8 } : taskbarPosition === "left" ? { top: 8, left: taskbarW + 8 } : taskbarPosition === "right" ? { top: 8, right: taskbarW + 8 } : { bottom: taskbarH + 8, left: 8 };
2064
+ const iconEl = (path) => {
2065
+ const icon = navIcons2[path];
2066
+ if (icon && isValidElement(icon)) return cloneElement(icon, { className: "h-4 w-4" });
2067
+ return null;
2068
+ };
2069
+ const secIcon = (label) => {
2070
+ const icon = sectionIcons2[label];
2071
+ if (icon && isValidElement(icon)) return cloneElement(icon, { className: "h-4 w-4" });
2072
+ return null;
2073
+ };
2074
+ const hoveredVirtual = hoveredSection ? virtualByLabel[hoveredSection] : void 0;
2075
+ const hoveredData = hoveredVirtual ? null : hoveredSection ? [...erpSections, ...systemSections].find((s) => s.label === hoveredSection) : null;
2076
+ const flyoutItems = hoveredVirtual ? hoveredVirtual.items : hoveredData ? getVisibleItems(hoveredData) : [];
2077
+ const menuDensity = typeof document !== "undefined" ? getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal" : "normal";
2078
+ const tight = menuDensity === "tight";
2079
+ const sizeConfig = tight ? { small: { w: "w-52", fw: "w-44", text: "text-xs", py: "py-1", px: "px-3", mw: 208, itemH: 24 }, medium: { w: "w-56", fw: "w-48", text: "text-xs", py: "py-1", px: "px-3", mw: 224, itemH: 26 }, large: { w: "w-64", fw: "w-52", text: "text-sm", py: "py-1.5", px: "px-3", mw: 256, itemH: 30 } }[size] : { small: { w: "w-56", fw: "w-48", text: "text-xs", py: "py-1.5", px: "px-3", mw: 224, itemH: 30 }, medium: { w: "w-64", fw: "w-56", text: "text-sm", py: "py-2", px: "px-4", mw: 256, itemH: 36 }, large: { w: "w-72", fw: "w-60", text: "text-sm", py: "py-2.5", px: "px-4", mw: 288, itemH: 40 } }[size];
2080
+ const menuGlass = glassStyle();
2081
+ const itemCls = `w-full flex items-center gap-2 rounded-lg ${sizeConfig.px} ${sizeConfig.py} ${sizeConfig.text}`;
2082
+ const flyoutH = flyoutItems.length * sizeConfig.itemH + 12;
2083
+ const menuWidth = sizeConfig.mw;
2084
+ let flyoutTop = hoveredY - flyoutH / 2;
2085
+ const minTop = taskbarPosition === "top" ? taskbarH + 4 : 4;
2086
+ const maxBottom = taskbarPosition === "bottom" ? window.innerHeight - taskbarH - 4 : window.innerHeight - 4;
2087
+ if (flyoutTop < minTop) flyoutTop = minTop;
2088
+ if (flyoutTop + flyoutH > maxBottom) flyoutTop = maxBottom - flyoutH;
2089
+ const handleSectionHover = (label, e) => {
2090
+ clearTimeout(hoverTimeout.current);
2091
+ const rect = e.currentTarget.getBoundingClientRect();
2092
+ setHoveredY(rect.top + rect.height / 2);
2093
+ setHoveredSection(label);
2094
+ };
2095
+ const renderSection = (section, isErp) => {
2096
+ if (section.perms && !hasAnyPerm(section.perms)) return null;
2097
+ const items = getVisibleItems(section);
2098
+ if (items.length === 0) return null;
2099
+ const isHovered = hoveredSection === section.label;
2100
+ return /* @__PURE__ */ jsx(
2101
+ "div",
2102
+ {
2103
+ onMouseEnter: (e) => handleSectionHover(section.label, e),
2104
+ onMouseLeave: () => {
2105
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2106
+ },
2107
+ children: /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
2108
+ secIcon(section.label),
2109
+ /* @__PURE__ */ jsx("span", { className: isErp ? "font-medium" : "", children: section.label }),
2110
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
2111
+ ] })
2112
+ },
2113
+ section.label
2114
+ );
2115
+ };
2116
+ const renderVirtualSection = (v) => {
2117
+ if (v.items.length === 0) return null;
2118
+ const isHovered = hoveredSection === v.label;
2119
+ return /* @__PURE__ */ jsx(
2120
+ "div",
2121
+ {
2122
+ onMouseEnter: (e) => handleSectionHover(v.label, e),
2123
+ onMouseLeave: () => {
2124
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2125
+ },
2126
+ children: /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
2127
+ v.icon,
2128
+ /* @__PURE__ */ jsx("span", { children: v.label }),
2129
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
2130
+ ] })
2131
+ },
2132
+ v.label
2133
+ );
2134
+ };
2135
+ return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "fixed z-[260]", style: posStyle, children: [
2136
+ /* @__PURE__ */ jsxs("div", { className: "flex", children: [
2137
+ /* @__PURE__ */ jsxs(
2138
+ "div",
2139
+ {
2140
+ className: `${sizeConfig.w} rounded-2xl flex ${isVertical ? "flex-col-reverse" : "flex-col"} overflow-hidden`,
2141
+ style: { animation: "menu-in 0.15s ease-out", ...menuGlass },
2142
+ children: [
2143
+ /* @__PURE__ */ jsx("div", { className: `px-3 ${isVertical ? "pb-3 pt-2 border-t border-white/20" : "pt-3 pb-2"}`, children: /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${GLASS_INPUT_BG} rounded-lg px-2.5 py-1.5`, children: [
2144
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
2145
+ /* @__PURE__ */ jsx(
2146
+ "input",
2147
+ {
2148
+ value: search,
2149
+ onChange: (e) => {
2150
+ setSearch(e.target.value);
2151
+ setHoveredSection(null);
2152
+ setSearchIdx(0);
2153
+ },
2154
+ onKeyDown: (e) => {
2155
+ if (search.length >= 2 && searchResults.length > 0) {
2156
+ if (e.key === "ArrowDown") {
2157
+ e.preventDefault();
2158
+ setSearchIdx((i) => Math.min(i + 1, searchResults.length - 1));
2159
+ } else if (e.key === "ArrowUp") {
2160
+ e.preventDefault();
2161
+ setSearchIdx((i) => Math.max(i - 1, 0));
2162
+ } else if (e.key === "Enter") {
2163
+ e.preventDefault();
2164
+ handleClick(searchResults[searchIdx].to);
2165
+ }
2166
+ }
2167
+ },
2168
+ placeholder: "Search...",
2169
+ className: "flex-1 bg-transparent text-xs outline-none placeholder-gray-400",
2170
+ autoFocus: true
2171
+ }
2172
+ )
2173
+ ] }) }),
2174
+ search.length >= 2 ? /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-1 pb-2 max-h-[400px]", children: searchResults.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 text-center py-4", children: "No results" }) : searchResults.map((r, i) => /* @__PURE__ */ jsxs(
2175
+ "button",
2176
+ {
2177
+ onClick: () => handleClick(r.to),
2178
+ onMouseEnter: () => setSearchIdx(i),
2179
+ className: `${itemCls} transition-colors ${i === searchIdx ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`,
2180
+ children: [
2181
+ iconEl(r.to),
2182
+ /* @__PURE__ */ jsx("span", { children: r.label }),
2183
+ r.section && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 ml-auto", children: r.section })
2184
+ ]
2185
+ },
2186
+ i
2187
+ )) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-1 pb-1 flex flex-col", children: [
2188
+ isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
2189
+ erpSections.map((s) => renderSection(s, true)),
2190
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
2191
+ topItems.map((item) => /* @__PURE__ */ jsxs(
2192
+ "button",
2193
+ {
2194
+ onClick: () => handleClick(item.to),
2195
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2196
+ children: [
2197
+ iconEl(item.to),
2198
+ /* @__PURE__ */ jsx("span", { children: item.label })
2199
+ ]
2200
+ },
2201
+ item.to
2202
+ )),
2203
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleClick("/notifications"), className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`, children: [
2204
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" }) }),
2205
+ /* @__PURE__ */ jsx("span", { children: "Notifications" })
2206
+ ] }),
2207
+ systemSections.map((s) => renderSection(s, false)),
2208
+ virtualSections.map((v) => renderVirtualSection(v))
2209
+ ] }),
2210
+ !isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
2211
+ topItems.map((item) => /* @__PURE__ */ jsxs(
2212
+ "button",
2213
+ {
2214
+ onClick: () => handleClick(item.to),
2215
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2216
+ children: [
2217
+ iconEl(item.to),
2218
+ /* @__PURE__ */ jsx("span", { children: item.label })
2219
+ ]
2220
+ },
2221
+ item.to
2222
+ )),
2223
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleClick("/notifications"), className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`, children: [
2224
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" }) }),
2225
+ /* @__PURE__ */ jsx("span", { children: "Notifications" })
2226
+ ] }),
2227
+ systemSections.map((s) => renderSection(s, false)),
2228
+ virtualSections.map((v) => renderVirtualSection(v)),
2229
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
2230
+ erpSections.map((s) => renderSection(s, true))
2231
+ ] })
2232
+ ] }),
2233
+ /* @__PURE__ */ jsx("div", { className: `${isVertical ? "border-b" : "border-t"} border-white/20 p-1`, children: /* @__PURE__ */ jsxs(
2234
+ "div",
2235
+ {
2236
+ onClick: () => handleClick("/profile"),
2237
+ className: "rounded-lg px-2 py-1.5 flex items-center gap-2.5 hover:bg-blue-50 hover:text-blue-700 transition-colors cursor-pointer",
2238
+ children: [
2239
+ profile?.avatar_url ? /* @__PURE__ */ jsx("img", { src: profile.avatar_url, alt: "", className: "h-8 w-8 rounded-full object-cover border border-white/20 shrink-0" }) : /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center text-sm font-bold text-blue-700 shrink-0", children: (profile?.first_name?.charAt(0) || user?.email?.charAt(0) || "?").toUpperCase() }),
2240
+ /* @__PURE__ */ jsx("p", { className: "flex-1 min-w-0 text-sm font-medium text-gray-900 truncate", children: profile?.first_name ? `${profile.first_name} ${profile.last_name || ""}`.trim() : user?.email }),
2241
+ /* @__PURE__ */ jsx(
2242
+ "button",
2243
+ {
2244
+ onClick: (e) => {
2245
+ e.stopPropagation();
2246
+ onClose();
2247
+ onLogout();
2248
+ },
2249
+ title: "Sign Out",
2250
+ className: "shrink-0 p-1.5 rounded-md text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors",
2251
+ children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" }) })
2252
+ }
2253
+ )
2254
+ ]
2255
+ }
2256
+ ) })
2257
+ ]
2258
+ }
2259
+ ),
2260
+ hoveredSection && flyoutItems.length > 0 && search.length < 2 && /* @__PURE__ */ jsx(
2261
+ "div",
2262
+ {
2263
+ className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
2264
+ style: { left: menuRef.current ? menuRef.current.getBoundingClientRect().right + 4 : menuWidth + 12, top: flyoutTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
2265
+ onMouseEnter: () => clearTimeout(hoverTimeout.current),
2266
+ onMouseLeave: () => {
2267
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2268
+ },
2269
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1", children: flyoutItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
2270
+ /* @__PURE__ */ jsxs(
2271
+ "button",
2272
+ {
2273
+ onClick: () => handleClick(item.to),
2274
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2275
+ children: [
2276
+ iconEl(item.to),
2277
+ /* @__PURE__ */ jsx("span", { children: item.label })
2278
+ ]
2279
+ }
2280
+ ),
2281
+ item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
2282
+ ] }, item.to)) })
2283
+ }
2284
+ )
2285
+ ] }),
2286
+ /* @__PURE__ */ jsx("style", { children: `
2287
+ @keyframes menu-in { from { opacity: 0; transform: scale(0.95) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
2288
+ @keyframes submenu-in { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: translateX(0); } }
2289
+ ` })
2290
+ ] });
2291
+ }
2292
+ function useFavorites(wallpapers) {
2293
+ const { prefs, save } = useShellPrefs();
2294
+ const favorites = prefs.favorite_pages || [];
2295
+ const toggle = useCallback((path) => {
2296
+ const next = favorites.includes(path) ? favorites.filter((p) => p !== path) : [...favorites, path];
2297
+ save({ favorite_pages: next });
2298
+ }, [favorites, save]);
2299
+ const wallpaperPool = wallpapers && wallpapers.length > 0 ? wallpapers : [];
2300
+ const randomPickRef = useRef(wallpaperPool.length > 0 ? wallpaperPool[Math.floor(Math.random() * wallpaperPool.length)] : "none");
2301
+ const rawBg = prefs.desktop_bg || (wallpaperPool.length > 0 ? "random" : "none");
2302
+ const desktopBg = rawBg === "random" ? randomPickRef.current : rawBg;
2303
+ const setDesktopBg = useCallback((bg) => {
2304
+ save({ desktop_bg: bg });
2305
+ }, [save]);
2306
+ return { favorites, toggle, isFavorite: (path) => favorites.includes(path), desktopBg, setDesktopBg };
2307
+ }
2308
+ var ALL_TIMEZONES = [
2309
+ { tz: "Pacific/Auckland", label: "Auckland" },
2310
+ { tz: "Australia/Sydney", label: "Sydney" },
2311
+ { tz: "Australia/Adelaide", label: "Adelaide" },
2312
+ { tz: "Australia/Brisbane", label: "Brisbane" },
2313
+ { tz: "Australia/Perth", label: "Perth" },
2314
+ { tz: "Asia/Tokyo", label: "Tokyo" },
2315
+ { tz: "Asia/Seoul", label: "Seoul" },
2316
+ { tz: "Asia/Shanghai", label: "Shanghai" },
2317
+ { tz: "Asia/Hong_Kong", label: "Hong Kong" },
2318
+ { tz: "Asia/Singapore", label: "Singapore" },
2319
+ { tz: "Asia/Bangkok", label: "Bangkok" },
2320
+ { tz: "Asia/Kolkata", label: "Mumbai" },
2321
+ { tz: "Asia/Dubai", label: "Dubai" },
2322
+ { tz: "Europe/Moscow", label: "Moscow" },
2323
+ { tz: "Europe/Istanbul", label: "Istanbul" },
2324
+ { tz: "Europe/Berlin", label: "Berlin" },
2325
+ { tz: "Europe/Paris", label: "Paris" },
2326
+ { tz: "Europe/London", label: "London" },
2327
+ { tz: "America/Sao_Paulo", label: "Sao Paulo" },
2328
+ { tz: "America/New_York", label: "New York" },
2329
+ { tz: "America/Chicago", label: "Chicago" },
2330
+ { tz: "America/Denver", label: "Denver" },
2331
+ { tz: "America/Los_Angeles", label: "Los Angeles" },
2332
+ { tz: "Pacific/Honolulu", label: "Honolulu" },
2333
+ { tz: "UTC", label: "UTC" }
2334
+ ];
2335
+ function ClockContent({ localTz, worldClocks, now, fmtTime, fmtDate, fmtOffset, removeClock, adding, setAdding, addClock, availableToAdd, showAdd }) {
2336
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2337
+ /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-gray-200", children: [
2338
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide", children: "Local Time" }),
2339
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between mt-1", children: [
2340
+ /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-gray-900 tabular-nums", children: fmtTime(localTz) }),
2341
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: fmtOffset(localTz) })
2342
+ ] }),
2343
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mt-0.5", children: [
2344
+ fmtDate(localTz),
2345
+ " \xB7 ",
2346
+ localTz.replace(/_/g, " ")
2347
+ ] })
2348
+ ] }),
2349
+ /* @__PURE__ */ jsxs("div", { className: "max-h-60 overflow-y-auto", children: [
2350
+ worldClocks.map((tz) => {
2351
+ const info = ALL_TIMEZONES.find((t) => t.tz === tz);
2352
+ const label = info?.label || tz.split("/").pop()?.replace(/_/g, " ") || tz;
2353
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 hover:bg-gray-50 group", children: [
2354
+ /* @__PURE__ */ jsxs("div", { children: [
2355
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: label }),
2356
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-gray-400", children: [
2357
+ fmtDate(tz),
2358
+ " \xB7 ",
2359
+ fmtOffset(tz)
2360
+ ] })
2361
+ ] }),
2362
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2363
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-gray-700 tabular-nums", children: fmtTime(tz) }),
2364
+ /* @__PURE__ */ jsx("button", { onClick: () => removeClock(tz), className: "opacity-0 group-hover:opacity-100 text-gray-300 hover:text-red-500 transition-all", title: "Remove", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) })
2365
+ ] })
2366
+ ] }, tz);
2367
+ }),
2368
+ worldClocks.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 text-center py-4", children: "No world clocks added" })
2369
+ ] }),
2370
+ showAdd && /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 p-2", children: adding ? /* @__PURE__ */ jsxs(
2371
+ "select",
2372
+ {
2373
+ autoFocus: true,
2374
+ onChange: (e) => {
2375
+ if (e.target.value) addClock(e.target.value);
2376
+ },
2377
+ onBlur: () => setAdding(false),
2378
+ className: "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm focus:border-blue-500 focus:ring-blue-500",
2379
+ children: [
2380
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select timezone..." }),
2381
+ availableToAdd.map((t) => /* @__PURE__ */ jsxs("option", { value: t.tz, children: [
2382
+ t.label,
2383
+ " (",
2384
+ fmtOffset(t.tz),
2385
+ ")"
2386
+ ] }, t.tz))
2387
+ ]
2388
+ }
2389
+ ) : /* @__PURE__ */ jsxs("button", { onClick: () => setAdding(true), className: "w-full flex items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-700 transition-colors", children: [
2390
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }),
2391
+ "Add World Clock"
2392
+ ] }) })
2393
+ ] });
2394
+ }
2395
+ function TaskbarClock() {
2396
+ const [now, setNow] = useState(/* @__PURE__ */ new Date());
2397
+ const [open, setOpen] = useState(false);
2398
+ const [adding, setAdding] = useState(false);
2399
+ const [pinned, setPinned] = useState(false);
2400
+ const ref = useRef(null);
2401
+ const buttonRef = useRef(null);
2402
+ const { prefs, save } = useShellPrefs();
2403
+ const worldClocks = prefs.world_clocks || ["Europe/London", "Asia/Shanghai", "America/Los_Angeles", "America/New_York"];
2404
+ useEffect(() => {
2405
+ const t = setInterval(() => setNow(/* @__PURE__ */ new Date()), 1e4);
2406
+ return () => clearInterval(t);
2407
+ }, []);
2408
+ useClickOutside(ref, useCallback(() => {
2409
+ if (open && !pinned) {
2410
+ setOpen(false);
2411
+ setAdding(false);
2412
+ }
2413
+ }, [open, pinned]));
2414
+ const fmtTime = (tz) => now.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", timeZone: tz });
2415
+ const fmtDate = (tz) => now.toLocaleDateString(void 0, { weekday: "short", month: "short", day: "numeric", timeZone: tz });
2416
+ const fmtOffset = (tz) => {
2417
+ const parts = new Intl.DateTimeFormat("en", { timeZone: tz, timeZoneName: "shortOffset" }).formatToParts(now);
2418
+ return parts.find((p) => p.type === "timeZoneName")?.value || "";
2419
+ };
2420
+ const saveClocks = (clocks) => {
2421
+ save({ world_clocks: clocks });
2422
+ };
2423
+ const addClock = (tz) => {
2424
+ if (!worldClocks.includes(tz)) saveClocks([...worldClocks, tz]);
2425
+ setAdding(false);
2426
+ };
2427
+ const removeClock = (tz) => {
2428
+ saveClocks(worldClocks.filter((t) => t !== tz));
2429
+ };
2430
+ const localTz = localStorage.getItem("user_timezone") || Intl.DateTimeFormat().resolvedOptions().timeZone;
2431
+ const availableToAdd = ALL_TIMEZONES.filter((t) => t.tz !== localTz && !worldClocks.includes(t.tz));
2432
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative shrink-0", children: [
2433
+ /* @__PURE__ */ jsxs("button", { ref: buttonRef, onClick: () => setOpen(!open), className: "text-right leading-tight cursor-pointer hover:bg-gray-200/50 rounded px-1.5 py-0.5 transition-colors", children: [
2434
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-gray-800", children: now.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) }),
2435
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-700", children: now.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" }) })
2436
+ ] }),
2437
+ open && !pinned && (() => {
2438
+ const taskbarPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
2439
+ const rect = buttonRef.current?.getBoundingClientRect();
2440
+ const right = rect ? window.innerWidth - rect.right : 0;
2441
+ const posStyle = taskbarPos === "top" ? { right, top: (rect?.bottom ?? 0) + 4 } : { right, bottom: window.innerHeight - (rect?.top ?? 0) + 4 };
2442
+ return /* @__PURE__ */ jsxs("div", { className: "fixed z-[300] w-72 rounded-lg border border-gray-200 bg-white shadow-xl", style: posStyle, children: [
2443
+ /* @__PURE__ */ jsx(
2444
+ "button",
2445
+ {
2446
+ onClick: () => {
2447
+ setPinned(true);
2448
+ },
2449
+ title: "Pin as widget",
2450
+ className: "absolute top-2 right-2 text-gray-300 hover:text-gray-600 transition-colors z-10",
2451
+ children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 3.75L8.25 10.5m0 0l-3-1.5L3 12.75l5.25 5.25 3.75-2.25-1.5-3L17.25 6M8.25 10.5l-3 3M17.25 6l3 3" }) })
2452
+ }
2453
+ ),
2454
+ /* @__PURE__ */ jsx(
2455
+ ClockContent,
2456
+ {
2457
+ localTz,
2458
+ worldClocks,
2459
+ now,
2460
+ fmtTime,
2461
+ fmtDate,
2462
+ fmtOffset,
2463
+ removeClock,
2464
+ adding,
2465
+ setAdding,
2466
+ addClock,
2467
+ availableToAdd,
2468
+ showAdd: true
2469
+ }
2470
+ )
2471
+ ] });
2472
+ })(),
2473
+ pinned && /* @__PURE__ */ jsx(
2474
+ Modal,
2475
+ {
2476
+ open: true,
2477
+ onClose: () => {
2478
+ setPinned(false);
2479
+ setOpen(false);
2480
+ },
2481
+ title: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2482
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
2483
+ "World Clock"
2484
+ ] }),
2485
+ size: "sm",
2486
+ allowPinOnTop: true,
2487
+ initialPosition: "top-right",
2488
+ widget: true,
2489
+ children: /* @__PURE__ */ jsx(
2490
+ ClockContent,
2491
+ {
2492
+ localTz,
2493
+ worldClocks,
2494
+ now,
2495
+ fmtTime,
2496
+ fmtDate,
2497
+ fmtOffset,
2498
+ removeClock,
2499
+ adding,
2500
+ setAdding,
2501
+ addClock,
2502
+ availableToAdd,
2503
+ showAdd: false
2504
+ }
2505
+ )
2506
+ }
2507
+ )
2508
+ ] });
2509
+ }
2510
+ function TaskbarContextMenu({ x, y, position, size, onChangePosition, onChangeSize, onClose, onReportBug }) {
2511
+ const ref = useRef(null);
2512
+ useClickOutside(ref, onClose);
2513
+ const posStyle = position === "top" ? { left: Math.min(x, window.innerWidth - 200), top: y + 4 } : position === "left" ? { left: x + 4, top: Math.min(y, window.innerHeight - 300) } : position === "right" ? { right: window.innerWidth - x + 4, top: Math.min(y, window.innerHeight - 300) } : { left: Math.min(x, window.innerWidth - 200), bottom: window.innerHeight - y + 4 };
2514
+ const check = (active) => active ? /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 text-blue-600 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) }) : /* @__PURE__ */ jsx("span", { className: "w-3.5" });
2515
+ return /* @__PURE__ */ jsxs("div", { ref, className: "fixed z-[400] rounded-2xl py-1 min-w-[180px]", style: { ...posStyle, ...glassStyle() }, children: [
2516
+ /* @__PURE__ */ jsx("p", { className: "px-3 py-1 text-[10px] font-semibold text-gray-400 uppercase tracking-wide", children: "Position" }),
2517
+ ["bottom", "top", "left", "right"].map((pos) => /* @__PURE__ */ jsxs(
2518
+ "button",
2519
+ {
2520
+ onClick: () => onChangePosition(pos),
2521
+ className: "w-full text-left px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors rounded-lg mx-1 flex items-center gap-2",
2522
+ style: { width: "calc(100% - 8px)" },
2523
+ children: [
2524
+ check(position === pos),
2525
+ pos.charAt(0).toUpperCase() + pos.slice(1)
2526
+ ]
2527
+ },
2528
+ pos
2529
+ )),
2530
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1 mx-3" }),
2531
+ /* @__PURE__ */ jsx("p", { className: "px-3 py-1 text-[10px] font-semibold text-gray-400 uppercase tracking-wide", children: "Size" }),
2532
+ ["small", "medium", "large"].map((s) => /* @__PURE__ */ jsxs(
2533
+ "button",
2534
+ {
2535
+ onClick: () => onChangeSize(s),
2536
+ className: "w-full text-left px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors rounded-lg mx-1 flex items-center gap-2",
2537
+ style: { width: "calc(100% - 8px)" },
2538
+ children: [
2539
+ check(size === s),
2540
+ s.charAt(0).toUpperCase() + s.slice(1)
2541
+ ]
2542
+ },
2543
+ s
2544
+ )),
2545
+ onReportBug && /* @__PURE__ */ jsxs(Fragment, { children: [
2546
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1 mx-3" }),
2547
+ /* @__PURE__ */ jsxs(
2548
+ "button",
2549
+ {
2550
+ onClick: () => {
2551
+ onClose();
2552
+ onReportBug();
2553
+ },
2554
+ className: "w-full text-left px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors rounded-lg mx-1 flex items-center gap-2",
2555
+ style: { width: "calc(100% - 8px)" },
2556
+ children: [
2557
+ /* @__PURE__ */ jsx("span", { className: "w-3.5 shrink-0" }),
2558
+ "Report Bug"
2559
+ ]
2560
+ }
2561
+ )
2562
+ ] })
2563
+ ] });
2564
+ }
2565
+ function Layout({
2566
+ productName = "react-os-shell",
2567
+ productIcon = "/favicon.svg",
2568
+ wallpapers,
2569
+ navSections: navSections2 = navSections,
2570
+ navIcons: navIcons2 = navIcons,
2571
+ sectionIcons: sectionIcons2 = sectionIcons,
2572
+ categories = startMenuCategories,
2573
+ notifications,
2574
+ search
2575
+ } = {}) {
2576
+ const bugReport = useBugReport();
2577
+ const { user, logout} = useAuth();
2578
+ const { openPage, openEntity, openWindows } = useWindowManager();
2579
+ const [menuOpen, setMenuOpen] = useState(false);
2580
+ useEmailUnreadCount();
2581
+ const profile = user || {};
2582
+ useTheme();
2583
+ const { desktopBg} = useFavorites(wallpapers);
2584
+ const { prefs, save: savePrefs } = useShellPrefs();
2585
+ const taskbarPosition = prefs.taskbar_position || "bottom";
2586
+ const taskbarSize = prefs.taskbar_size || "medium";
2587
+ const desktopDblClick = prefs.desktop_dblclick || "deactivate";
2588
+ const isVerticalTaskbar = taskbarPosition === "left" || taskbarPosition === "right";
2589
+ const taskbarH = isVerticalTaskbar ? 0 : taskbarSize === "small" ? 40 : taskbarSize === "large" ? 72 : 56;
2590
+ const taskbarW = isVerticalTaskbar ? taskbarSize === "small" ? 180 : taskbarSize === "large" ? 260 : 220 : 0;
2591
+ const taskbarHClass = taskbarSize === "small" ? "h-10" : taskbarSize === "large" ? "h-[72px]" : "h-14";
2592
+ const taskbarWClass = taskbarSize === "small" ? "w-[180px]" : taskbarSize === "large" ? "w-[260px]" : "w-[220px]";
2593
+ const taskbarOpacity = (prefs.transparency_taskbar ?? 70) / 100;
2594
+ const menuOpacity = (prefs.transparency_start_menu ?? 95) / 100;
2595
+ const inactiveHeaderOpacity = (prefs.transparency_inactive_header ?? 70) / 100;
2596
+ const inactiveContentOpacity = (prefs.transparency_inactive_content ?? 80) / 100;
2597
+ const activeHeaderOpacity = (prefs.transparency_active_header ?? 80) / 100;
2598
+ const activeContentOpacity = (prefs.transparency_active_content ?? 90) / 100;
2599
+ useEffect(() => {
2600
+ const root = document.documentElement;
2601
+ root.style.setProperty("--inactive-header-opacity", String(inactiveHeaderOpacity));
2602
+ root.style.setProperty("--inactive-content-opacity", String(inactiveContentOpacity));
2603
+ root.style.setProperty("--active-header-opacity", String(activeHeaderOpacity));
2604
+ root.style.setProperty("--active-content-opacity", String(activeContentOpacity));
2605
+ root.style.setProperty("--menu-opacity", String(menuOpacity));
2606
+ root.style.setProperty("--taskbar-height", String(taskbarH));
2607
+ root.style.setProperty("--taskbar-width", String(taskbarW));
2608
+ root.style.setProperty("--taskbar-position", taskbarPosition);
2609
+ root.style.setProperty("--default-window-size", prefs.default_window_size || "large");
2610
+ root.style.setProperty("--window-position", prefs.window_position || "cascade");
2611
+ root.style.setProperty("--menu-density", prefs.menu_density || "normal");
2612
+ const menuSize = prefs.start_menu_size || "medium";
2613
+ const sizeVars = {
2614
+ small: { font: "12px", px: "0.75rem", py: "0.25rem", tabW: "150px", tabFont: "11px" },
2615
+ medium: { font: "14px", px: "1rem", py: "0.5rem", tabW: "200px", tabFont: "12px" },
2616
+ large: { font: "15px", px: "1.125rem", py: "0.625rem", tabW: "240px", tabFont: "13px" }
2617
+ };
2618
+ const sv = sizeVars[menuSize] || sizeVars.medium;
2619
+ root.style.setProperty("--menu-font-size", sv.font);
2620
+ root.style.setProperty("--menu-padding-x", sv.px);
2621
+ root.style.setProperty("--menu-padding-y", sv.py);
2622
+ root.style.setProperty("--window-tab-width", sv.tabW);
2623
+ root.style.setProperty("--window-tab-font-size", sv.tabFont);
2624
+ }, [inactiveHeaderOpacity, inactiveContentOpacity, activeHeaderOpacity, activeContentOpacity, taskbarH, taskbarPosition, prefs.default_window_size, prefs.window_position, prefs.menu_density, prefs.start_menu_size]);
2625
+ const [balloonDismissed, setBalloonDismissed] = useState(false);
2626
+ const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement);
2627
+ const [taskbarMenu, setTaskbarMenu] = useState(null);
2628
+ const [googleConnectOpen, setGoogleConnectOpen] = useState(false);
2629
+ const { isConnected: googleConnected } = useGoogleAuth();
2630
+ const [showLogout, setShowLogout] = useState(false);
2631
+ useEffect(() => {
2632
+ const handler = () => setGoogleConnectOpen(true);
2633
+ window.addEventListener("open-google-connect", handler);
2634
+ return () => window.removeEventListener("open-google-connect", handler);
2635
+ }, []);
2636
+ const savePref = useCallback((key, value) => {
2637
+ savePrefs({ [key]: value });
2638
+ }, [savePrefs]);
2639
+ const toggleFullscreen = useCallback(async () => {
2640
+ if (document.fullscreenElement) {
2641
+ if (navigator.keyboard?.unlock) navigator.keyboard.unlock();
2642
+ document.exitFullscreen();
2643
+ } else {
2644
+ await document.documentElement.requestFullscreen();
2645
+ if (navigator.keyboard?.lock) {
2646
+ try {
2647
+ await navigator.keyboard.lock(["Escape"]);
2648
+ } catch {
2649
+ }
2650
+ }
2651
+ }
2652
+ }, []);
2653
+ useEffect(() => {
2654
+ const handler = () => {
2655
+ setIsFullscreen(!!document.fullscreenElement);
2656
+ if (!document.fullscreenElement && navigator.keyboard?.unlock) {
2657
+ navigator.keyboard.unlock();
2658
+ }
2659
+ };
2660
+ document.addEventListener("fullscreenchange", handler);
2661
+ return () => document.removeEventListener("fullscreenchange", handler);
2662
+ }, []);
2663
+ const autoFullscreenDone = useRef(false);
2664
+ useEffect(() => {
2665
+ if (autoFullscreenDone.current) return;
2666
+ if (prefs.auto_fullscreen && !document.fullscreenElement) {
2667
+ autoFullscreenDone.current = true;
2668
+ const handler = async () => {
2669
+ if (!document.fullscreenElement) {
2670
+ try {
2671
+ await document.documentElement.requestFullscreen();
2672
+ if (navigator.keyboard?.lock) navigator.keyboard.lock(["Escape"]).catch(() => {
2673
+ });
2674
+ } catch {
2675
+ }
2676
+ }
2677
+ document.removeEventListener("click", handler);
2678
+ };
2679
+ document.addEventListener("click", handler, { once: true });
2680
+ }
2681
+ }, [prefs.auto_fullscreen]);
2682
+ const [showStartup, setShowStartup] = useState(false);
2683
+ const startupChecked = useRef(false);
2684
+ useEffect(() => {
2685
+ if (startupChecked.current) return;
2686
+ startupChecked.current = true;
2687
+ const key = "erp_startup_shown";
2688
+ if (!sessionStorage.getItem(key)) {
2689
+ sessionStorage.setItem(key, "1");
2690
+ setShowStartup(true);
2691
+ }
2692
+ }, []);
2693
+ const startupSoundDone = useRef(false);
2694
+ useEffect(() => {
2695
+ if (startupSoundDone.current || !showStartup) return;
2696
+ startupSoundDone.current = true;
2697
+ setTimeout(() => playStartup(), 500);
2698
+ }, [showStartup]);
2699
+ useEffect(() => {
2700
+ const handler = (e) => {
2701
+ if ((e.ctrlKey || e.metaKey) && e.key === ".") {
2702
+ e.preventDefault();
2703
+ setMenuOpen((prev) => !prev);
2704
+ }
2705
+ if (e.ctrlKey && e.key === "F11") {
2706
+ e.preventDefault();
2707
+ toggleFullscreen();
2708
+ }
2709
+ if (e.key === "Escape" && document.fullscreenElement) {
2710
+ const hasOpenModals = document.querySelectorAll("[data-modal-panel]").length > 0;
2711
+ if (!hasOpenModals) {
2712
+ e.preventDefault();
2713
+ toggleFullscreen();
2714
+ }
2715
+ }
2716
+ };
2717
+ window.addEventListener("keydown", handler);
2718
+ return () => {
2719
+ window.removeEventListener("keydown", handler);
2720
+ };
2721
+ }, [toggleFullscreen]);
2722
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-screen", children: [
2723
+ showStartup && /* @__PURE__ */ jsx(StartupAnimation, { onComplete: () => setShowStartup(false), ready: !!profile, productName }),
2724
+ showLogout && /* @__PURE__ */ jsx(LogoutAnimation, { onComplete: () => {
2725
+ sessionStorage.removeItem("erp_startup_shown");
2726
+ logout();
2727
+ } }),
2728
+ /* @__PURE__ */ jsx(
2729
+ StartMenu,
2730
+ {
2731
+ open: menuOpen,
2732
+ onClose: () => setMenuOpen(false),
2733
+ openPage: (path) => {
2734
+ openPage(path);
2735
+ setMenuOpen(false);
2736
+ },
2737
+ openWindows,
2738
+ profile,
2739
+ user,
2740
+ onLogout: () => setShowLogout(true),
2741
+ onNavigate: (path) => {
2742
+ openPage(path);
2743
+ setMenuOpen(false);
2744
+ },
2745
+ taskbarPosition,
2746
+ taskbarH,
2747
+ taskbarW,
2748
+ size: prefs.start_menu_size || "medium",
2749
+ navSections: navSections2,
2750
+ navIcons: navIcons2,
2751
+ sectionIcons: sectionIcons2,
2752
+ categories
2753
+ }
2754
+ ),
2755
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 min-h-0", children: /* @__PURE__ */ jsx(
2756
+ "main",
2757
+ {
2758
+ className: "flex-1 flex flex-col overflow-hidden cursor-default",
2759
+ onDoubleClick: () => {
2760
+ if (desktopDblClick === "deactivate") {
2761
+ setMenuOpen(false);
2762
+ window.dispatchEvent(new CustomEvent("deactivate-all-modals"));
2763
+ }
2764
+ },
2765
+ style: {
2766
+ backgroundColor: desktopBg?.startsWith("#") ? desktopBg : desktopBg === "none" ? (() => {
2767
+ const customBg = getComputedStyle(document.documentElement).getPropertyValue("--custom-bg-color")?.trim();
2768
+ if (customBg) return customBg;
2769
+ const t = document.documentElement.getAttribute("data-theme") || "light";
2770
+ const map = { light: "#f3f4f6", dark: "#1e1e2e", pink: "#fdf2f8", green: "#f0fdf4", grey: "#d1d5db", blue: "#eff6ff" };
2771
+ return map[t] || "#f3f4f6";
2772
+ })() : "#1a1a2e",
2773
+ backgroundImage: desktopBg && desktopBg !== "none" && !desktopBg.startsWith("#") ? `url(${desktopBg})` : "none",
2774
+ backgroundSize: "cover",
2775
+ backgroundPosition: "center",
2776
+ backgroundRepeat: "no-repeat"
2777
+ },
2778
+ children: /* @__PURE__ */ jsx(Desktop, { profile })
2779
+ }
2780
+ ) }),
2781
+ /* @__PURE__ */ jsxs(
2782
+ "div",
2783
+ {
2784
+ className: `flex backdrop-blur-sm border-gray-200 z-[250] fixed ${isVerticalTaskbar ? `flex-col items-center ${taskbarWClass} py-3 gap-2 top-0 bottom-0 ${taskbarPosition === "left" ? "left-0 border-r" : "right-0 border-l"}` : `items-center ${taskbarHClass} px-3 gap-2 left-0 right-0 ${taskbarPosition === "top" ? "top-0 border-b" : "bottom-0 border-t"}`}`,
2785
+ style: { backgroundColor: `rgb(var(--taskbar-bg-rgb, 243 244 246) / ${taskbarOpacity})` },
2786
+ onContextMenu: (e) => {
2787
+ e.preventDefault();
2788
+ setTaskbarMenu({ x: e.clientX, y: e.clientY });
2789
+ },
2790
+ children: [
2791
+ /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
2792
+ openWindows.length === 0 && !menuOpen && !balloonDismissed && /* @__PURE__ */ jsxs(
2793
+ "div",
2794
+ {
2795
+ className: `absolute left-1/2 -translate-x-1/2 whitespace-nowrap text-white text-[10px] font-medium pl-3 pr-2 py-1 rounded-full shadow-lg animate-bounce flex items-center gap-1 ${taskbarPosition === "top" ? "top-full mt-2" : "-top-8"}`,
2796
+ style: { backgroundColor: "var(--accent-600, #7c3aed)" },
2797
+ children: [
2798
+ "Click here to start",
2799
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
2800
+ e.stopPropagation();
2801
+ setBalloonDismissed(true);
2802
+ }, className: "text-white/60 hover:text-white ml-1.5", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }),
2803
+ /* @__PURE__ */ jsx(
2804
+ "div",
2805
+ {
2806
+ className: `absolute left-1/2 -translate-x-1/2 border-[6px] border-transparent ${taskbarPosition === "top" ? "bottom-full border-b-[var(--accent-color,#7c3aed)]" : "top-full border-t-[var(--accent-color,#7c3aed)]"}`,
2807
+ style: taskbarPosition === "top" ? { borderBottomColor: "var(--accent-600, #7c3aed)" } : { borderTopColor: "var(--accent-600, #7c3aed)" }
2808
+ }
2809
+ )
2810
+ ]
2811
+ }
2812
+ ),
2813
+ /* @__PURE__ */ jsxs(
2814
+ "button",
2815
+ {
2816
+ "data-menu-toggle": true,
2817
+ onClick: () => setMenuOpen((prev) => !prev),
2818
+ title: menuOpen ? "Close menu" : "Open menu",
2819
+ className: `group/erp relative flex items-center gap-1.5 rounded-lg px-4 py-2 text-xs font-medium border overflow-hidden transition-all ${isVerticalTaskbar ? "w-full" : "min-w-[140px]"} ${menuOpen ? "bg-gray-200/40 border-gray-300/40 text-gray-800" : "bg-gray-50/40 border-gray-200/40 text-gray-600 hover:text-gray-800"}`,
2820
+ style: { transition: "box-shadow 0.3s, border-color 0.3s" },
2821
+ onMouseMove: (e) => {
2822
+ const r = e.currentTarget.getBoundingClientRect();
2823
+ e.currentTarget.style.setProperty("--mx", `${e.clientX - r.left}px`);
2824
+ e.currentTarget.style.setProperty("--my", `${e.clientY - r.top}px`);
2825
+ },
2826
+ onMouseEnter: (e) => {
2827
+ e.currentTarget.style.boxShadow = "0 0 15px rgba(255,255,255,0.2), 0 0 30px rgba(255,255,255,0.1)";
2828
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.4)";
2829
+ },
2830
+ onMouseLeave: (e) => {
2831
+ e.currentTarget.style.boxShadow = "";
2832
+ e.currentTarget.style.borderColor = "";
2833
+ },
2834
+ children: [
2835
+ /* @__PURE__ */ jsx(
2836
+ "span",
2837
+ {
2838
+ className: "absolute inset-0 opacity-0 group-hover/erp:opacity-100 transition-opacity duration-200 pointer-events-none",
2839
+ style: { background: "radial-gradient(circle 60px at var(--mx, 50%) var(--my, 50%), rgba(255,255,255,0.25) 0%, transparent 100%)" }
2840
+ }
2841
+ ),
2842
+ productIcon && /* @__PURE__ */ jsx("img", { src: productIcon, alt: "", className: "relative z-10 h-3.5 w-3.5 shrink-0 opacity-60" }),
2843
+ /* @__PURE__ */ jsx("span", { className: "relative z-10 truncate", children: productName })
2844
+ ]
2845
+ }
2846
+ )
2847
+ ] }),
2848
+ /* @__PURE__ */ jsx("div", { className: isVerticalTaskbar ? "h-px w-6 bg-gray-300 my-1" : "w-px h-6 bg-gray-300 mx-1" }),
2849
+ /* @__PURE__ */ jsx("div", { id: "taskbar-windows", className: `flex-1 flex ${isVerticalTaskbar ? "flex-col items-center gap-1 min-h-0 overflow-y-auto w-full" : "items-center gap-1.5 min-w-0 overflow-x-auto"}` }),
2850
+ /* @__PURE__ */ jsx("div", { className: isVerticalTaskbar ? "h-px w-6 bg-gray-300 my-1" : "w-px h-6 bg-gray-300 mx-1" }),
2851
+ isVerticalTaskbar ? (
2852
+ /* Vertical: clock + bell + google evenly spaced in a row */
2853
+ /* @__PURE__ */ jsx("div", { className: "w-full px-2", children: /* @__PURE__ */ jsxs("div", { className: `flex items-center justify-center gap-2 ${taskbarPosition === "right" ? "flex-row-reverse" : ""}`, children: [
2854
+ /* @__PURE__ */ jsx(TaskbarClock, {}),
2855
+ /* @__PURE__ */ jsx(
2856
+ "button",
2857
+ {
2858
+ onClick: () => setGoogleConnectOpen(true),
2859
+ title: googleConnected ? "Google Connected" : "Connect Google",
2860
+ className: `shrink-0 rounded-md p-1.5 transition-colors ${googleConnected ? "hover:bg-green-50" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200"}`,
2861
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", children: [
2862
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2863
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2864
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2865
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: googleConnected ? "#16a34a" : "#9ca3af" })
2866
+ ] })
2867
+ }
2868
+ ),
2869
+ notifications && /* @__PURE__ */ jsx(NotificationBell, { ...notifications, popDirection: taskbarPosition === "right" ? "left" : "right" })
2870
+ ] }) })
2871
+ ) : (
2872
+ /* Horizontal: icons then clock */
2873
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2874
+ notifications && /* @__PURE__ */ jsx(NotificationBell, { ...notifications }),
2875
+ /* @__PURE__ */ jsx(
2876
+ "button",
2877
+ {
2878
+ onClick: () => setGoogleConnectOpen(true),
2879
+ title: googleConnected ? "Google Connected" : "Connect Google",
2880
+ className: `shrink-0 rounded-md p-2 transition-colors ${googleConnected ? "hover:bg-green-50" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200"}`,
2881
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", children: [
2882
+ /* @__PURE__ */ jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2883
+ /* @__PURE__ */ jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2884
+ /* @__PURE__ */ jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: googleConnected ? "#16a34a" : "#9ca3af" }),
2885
+ /* @__PURE__ */ jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: googleConnected ? "#16a34a" : "#9ca3af" })
2886
+ ] })
2887
+ }
2888
+ ),
2889
+ /* @__PURE__ */ jsx(TaskbarClock, {})
2890
+ ] })
2891
+ )
2892
+ ]
2893
+ }
2894
+ ),
2895
+ taskbarMenu && /* @__PURE__ */ jsx(
2896
+ TaskbarContextMenu,
2897
+ {
2898
+ x: taskbarMenu.x,
2899
+ y: taskbarMenu.y,
2900
+ position: taskbarPosition,
2901
+ size: taskbarSize,
2902
+ onChangePosition: (v) => {
2903
+ savePref("taskbar_position", v);
2904
+ setTaskbarMenu(null);
2905
+ },
2906
+ onChangeSize: (v) => {
2907
+ savePref("taskbar_size", v);
2908
+ setTaskbarMenu(null);
2909
+ },
2910
+ onClose: () => setTaskbarMenu(null),
2911
+ onReportBug: bugReport ? () => reportBug(bugReport.submit) : void 0
2912
+ }
2913
+ ),
2914
+ /* @__PURE__ */ jsx(GlobalSearch, { ...search }),
2915
+ /* @__PURE__ */ jsx(ShortcutHelp, {}),
2916
+ /* @__PURE__ */ jsx(GoogleConnectModal, { open: googleConnectOpen, onClose: () => setGoogleConnectOpen(false) })
2917
+ ] });
2918
+ }
2919
+ var THEMES = [
2920
+ { key: "system", label: "System", bar1: "bg-[#e5e7eb]", bar2: "bg-[#e5e7eb]" },
2921
+ { key: "light", label: "Light", bar1: "bg-[#e5e7eb]", bar2: "bg-[#e5e7eb]" },
2922
+ { key: "dark", label: "Dark", bar1: "bg-[#45475a]", bar2: "bg-[#45475a]" },
2923
+ { key: "pink", label: "Blossom", bar1: "bg-[#fbcfe8]", bar2: "bg-[#fbcfe8]" },
2924
+ { key: "green", label: "Nature", bar1: "bg-[#bbf7d0]", bar2: "bg-[#bbf7d0]" },
2925
+ { key: "grey", label: "Quicksilver", bar1: "bg-[#d1d5db]", bar2: "bg-[#d1d5db]" },
2926
+ { key: "blue", label: "Ocean", bar1: "bg-[#bfdbfe]", bar2: "bg-[#bfdbfe]" }
2927
+ ];
2928
+ var DEFAULT_WALLPAPERS = [];
2929
+ function resolveTheme(key) {
2930
+ if (key === "system") {
2931
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
2932
+ }
2933
+ return key;
2934
+ }
2935
+ function getVersion() {
2936
+ const now = /* @__PURE__ */ new Date();
2937
+ return `v${String(now.getDate()).padStart(2, "0")}${String(now.getMonth() + 1).padStart(2, "0")}.${String(now.getHours()).padStart(2, "0")}.${String(now.getMinutes()).padStart(2, "0")}.${now.getFullYear()}`;
2938
+ }
2939
+ function previewColor(resolved, light, dark, pink, green, grey, blue) {
2940
+ if (resolved === "dark") return dark;
2941
+ if (resolved === "pink") return pink;
2942
+ if (resolved === "green") return green;
2943
+ if (resolved === "grey") return grey ?? light;
2944
+ if (resolved === "blue") return blue ?? light;
2945
+ return light;
2946
+ }
2947
+ function Customization() {
2948
+ const host = useDesktopHost();
2949
+ const WALLPAPERS = host.wallpapers && host.wallpapers.length > 0 ? host.wallpapers : DEFAULT_WALLPAPERS;
2950
+ const { prefs, save } = useShellPrefs();
2951
+ const currentTheme = prefs.theme || "system";
2952
+ const resolved = resolveTheme(currentTheme);
2953
+ const rawBg = prefs.desktop_bg || (WALLPAPERS.length > 0 ? "random" : "none");
2954
+ const randomPickRef = useRef(WALLPAPERS.length > 0 ? WALLPAPERS[Math.floor(Math.random() * WALLPAPERS.length)].src : "none");
2955
+ const desktopBg = rawBg === "random" ? randomPickRef.current : rawBg;
2956
+ const customBg = prefs.desktop_bg_custom || "";
2957
+ const isColor = desktopBg?.startsWith("#");
2958
+ const presetPaths = /* @__PURE__ */ new Set([...WALLPAPERS.map((w) => w.src), "random"]);
2959
+ const isCustom = !isColor && desktopBg !== "none" && rawBg !== "random" && desktopBg && !presetPaths.has(desktopBg);
2960
+ const savePref = (key, value) => {
2961
+ save({ [key]: value });
2962
+ };
2963
+ const [localSliders, setLocalSliders] = useState({});
2964
+ const sliderTimers = useRef({});
2965
+ const saveSlider = useCallback((key, value) => {
2966
+ setLocalSliders((prev) => ({ ...prev, [key]: value }));
2967
+ const cssMap = {
2968
+ transparency_taskbar: "--taskbar-opacity",
2969
+ transparency_inactive_header: "--inactive-header-opacity",
2970
+ transparency_inactive_content: "--inactive-content-opacity",
2971
+ transparency_active_header: "--active-header-opacity",
2972
+ transparency_active_content: "--active-content-opacity"
2973
+ };
2974
+ if (cssMap[key]) {
2975
+ document.documentElement.style.setProperty(cssMap[key], String(value / 100));
2976
+ }
2977
+ if (sliderTimers.current[key]) clearTimeout(sliderTimers.current[key]);
2978
+ sliderTimers.current[key] = setTimeout(() => {
2979
+ savePref(key, value);
2980
+ setLocalSliders((prev) => {
2981
+ const next = { ...prev };
2982
+ delete next[key];
2983
+ return next;
2984
+ });
2985
+ }, 300);
2986
+ }, [savePref]);
2987
+ useEffect(() => () => {
2988
+ Object.values(sliderTimers.current).forEach(clearTimeout);
2989
+ }, []);
2990
+ const prevTaskbarOpacity = (prefs.transparency_taskbar ?? 70) / 100;
2991
+ const prevActiveHeaderOpacity = (prefs.transparency_active_header ?? 80) / 100;
2992
+ const prevActiveContentOpacity = (prefs.transparency_active_content ?? 90) / 100;
2993
+ const taskbarBg = previewColor(resolved, "bg-white/90", "bg-[#1e1e2e]/90", "bg-pink-50/90", "bg-green-50/90", "bg-gray-200/90", "bg-blue-50/90");
2994
+ const iconBg = previewColor(resolved, "bg-gray-200", "bg-[#313244]", "bg-pink-200", "bg-green-200", "bg-gray-400", "bg-blue-200");
2995
+ const winBg = previewColor(resolved, "bg-white border border-gray-200", "bg-[#1e1e2e] border border-[#45475a]", "bg-white border border-pink-200", "bg-white border border-green-200", "bg-gray-100 border border-gray-300", "bg-blue-50 border border-blue-200");
2996
+ const headerBg = previewColor(resolved, "bg-gray-100", "bg-[#313244]", "bg-pink-50", "bg-green-50", "bg-gray-300", "bg-blue-100");
2997
+ const accentBg = previewColor(resolved, "bg-blue-600", "bg-blue-500", "bg-pink-600", "bg-green-600", "bg-gray-700", "bg-blue-700");
2998
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
2999
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-200 overflow-hidden", style: { width: 480 }, children: /* @__PURE__ */ jsxs("div", { className: "relative", style: { height: 300 }, children: [
3000
+ isColor ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0", style: { backgroundColor: desktopBg } }) : /* @__PURE__ */ jsx("img", { src: desktopBg, alt: "", className: "absolute inset-0 w-full h-full object-cover" }),
3001
+ /* @__PURE__ */ jsxs("div", { className: `absolute left-0 right-0 bottom-0 h-4 ${taskbarBg} border-t border-gray-200/50 flex items-center px-2 gap-1`, style: { opacity: prevTaskbarOpacity }, children: [
3002
+ /* @__PURE__ */ jsx("div", { className: `h-2 w-8 rounded-sm ${accentBg}` }),
3003
+ /* @__PURE__ */ jsx("div", { className: `h-2 w-6 rounded-sm ${iconBg}` })
3004
+ ] }),
3005
+ /* @__PURE__ */ jsx("div", { className: "absolute left-4 top-4 flex gap-4", children: [
3006
+ { icon: "\u{1F4CB}", label: "Orders" },
3007
+ { icon: "\u{1F4E6}", label: "Products" },
3008
+ { icon: "\u{1F4CA}", label: "Reports" }
3009
+ ].map((d) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5 w-8", children: [
3010
+ /* @__PURE__ */ jsx("div", { className: "text-sm drop-shadow", children: d.icon }),
3011
+ /* @__PURE__ */ jsx("span", { className: "text-[5px] text-white font-medium drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)] leading-tight", children: d.label })
3012
+ ] }, d.label)) }),
3013
+ /* @__PURE__ */ jsxs("div", { className: `absolute rounded shadow-md ${winBg}`, style: { left: "35%", top: "20%", width: "55%", height: "55%" }, children: [
3014
+ /* @__PURE__ */ jsxs("div", { className: `h-3 rounded-t ${headerBg} flex items-center px-1.5 gap-0.5`, style: { opacity: prevActiveHeaderOpacity }, children: [
3015
+ /* @__PURE__ */ jsx("div", { className: "w-1 h-1 rounded-full bg-red-400" }),
3016
+ /* @__PURE__ */ jsx("div", { className: "w-1 h-1 rounded-full bg-yellow-400" }),
3017
+ /* @__PURE__ */ jsx("div", { className: "w-1 h-1 rounded-full bg-green-400" })
3018
+ ] }),
3019
+ /* @__PURE__ */ jsxs("div", { className: "p-1.5 space-y-1", style: { opacity: prevActiveContentOpacity }, children: [
3020
+ /* @__PURE__ */ jsx("div", { className: `h-1 rounded w-3/4 ${iconBg}` }),
3021
+ /* @__PURE__ */ jsx("div", { className: `h-1 rounded w-1/2 ${iconBg}` })
3022
+ ] })
3023
+ ] })
3024
+ ] }) }) }),
3025
+ /* @__PURE__ */ jsxs("div", { children: [
3026
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Theme" }),
3027
+ /* @__PURE__ */ jsx("div", { className: "flex gap-3 flex-wrap", children: [...THEMES, { key: "custom", label: "Custom", bar1: "bg-gray-200", bar2: "bg-gray-200" }].map((t) => {
3028
+ const isCustom2 = t.key === "custom";
3029
+ const r = isCustom2 ? "light" : resolveTheme(t.key);
3030
+ const customColor = prefs.accent_color || "#8b5cf6";
3031
+ const tAccent = isCustom2 ? "" : previewColor(r, "bg-[#2563eb]", "bg-[#3b82f6]", "bg-[#db2777]", "bg-[#16a34a]", "bg-[#374151]", "bg-[#1d4ed8]");
3032
+ const tBg = isCustom2 ? "bg-[#ffffff] border-[#d1d5db]" : previewColor(r, "bg-[#ffffff] border-[#d1d5db]", "bg-[#1e1e2e] border-[#45475a]", "bg-[#fdf2f8] border-[#f9a8d4]", "bg-[#f0fdf4] border-[#86efac]", "bg-[#e5e7eb] border-[#9ca3af]", "bg-[#eff6ff] border-[#93c5fd]");
3033
+ return /* @__PURE__ */ jsxs(
3034
+ "button",
3035
+ {
3036
+ onClick: () => {
3037
+ if (isCustom2) {
3038
+ savePref("theme", "light");
3039
+ if (!prefs.accent_color) savePref("accent_color", "#8b5cf6");
3040
+ } else {
3041
+ savePref("theme", t.key);
3042
+ savePref("accent_color", null);
3043
+ }
3044
+ },
3045
+ className: `flex flex-col items-center gap-1.5 rounded-lg border-2 p-3 transition-all ${(isCustom2 ? !!prefs.accent_color : currentTheme === t.key && !prefs.accent_color) ? "border-blue-500 shadow-md" : "border-gray-200 hover:border-gray-300"}`,
3046
+ children: [
3047
+ /* @__PURE__ */ jsx("div", { className: `w-20 h-14 rounded ${tBg} border overflow-hidden flex flex-col`, children: t.key === "system" ? /* @__PURE__ */ jsxs("div", { className: "flex-1 flex", children: [
3048
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col bg-[#ffffff]", children: [
3049
+ /* @__PURE__ */ jsx("div", { className: "h-2 bg-[#2563eb] w-full" }),
3050
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex gap-0.5 p-0.5", children: [
3051
+ /* @__PURE__ */ jsx("div", { className: "w-3 rounded-sm bg-[#f3f4f6]" }),
3052
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
3053
+ /* @__PURE__ */ jsx("div", { className: "h-1 rounded-sm w-3/4 bg-[#e5e7eb]" }),
3054
+ /* @__PURE__ */ jsx("div", { className: "h-1 rounded-sm w-1/2 bg-[#e5e7eb]" })
3055
+ ] })
3056
+ ] })
3057
+ ] }),
3058
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col bg-[#1e1e2e]", children: [
3059
+ /* @__PURE__ */ jsx("div", { className: "h-2 bg-[#3b82f6] w-full" }),
3060
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex gap-0.5 p-0.5", children: [
3061
+ /* @__PURE__ */ jsx("div", { className: "w-3 rounded-sm bg-[#313244]" }),
3062
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
3063
+ /* @__PURE__ */ jsx("div", { className: "h-1 rounded-sm w-3/4 bg-[#45475a]" }),
3064
+ /* @__PURE__ */ jsx("div", { className: "h-1 rounded-sm w-1/2 bg-[#45475a]" })
3065
+ ] })
3066
+ ] })
3067
+ ] })
3068
+ ] }) : isCustom2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
3069
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-full", style: { backgroundColor: customColor } }),
3070
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex gap-0.5 p-1", children: [
3071
+ /* @__PURE__ */ jsx("div", { className: "w-4 rounded-sm bg-[#f3f4f6]" }),
3072
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
3073
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 rounded-sm w-3/4 bg-[#e5e7eb]" }),
3074
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 rounded-sm w-1/2 bg-[#e5e7eb]" })
3075
+ ] })
3076
+ ] })
3077
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3078
+ /* @__PURE__ */ jsx("div", { className: `h-2 ${tAccent} w-full` }),
3079
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex gap-0.5 p-1", children: /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
3080
+ /* @__PURE__ */ jsx("div", { className: `h-1.5 rounded-sm w-3/4 ${t.bar1}` }),
3081
+ /* @__PURE__ */ jsx("div", { className: `h-1.5 rounded-sm w-1/2 ${t.bar2}` })
3082
+ ] }) })
3083
+ ] }) }),
3084
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${(isCustom2 ? !!prefs.accent_color : currentTheme === t.key && !prefs.accent_color) ? "text-blue-600" : "text-gray-600"}`, children: t.label })
3085
+ ]
3086
+ },
3087
+ t.key
3088
+ );
3089
+ }) }),
3090
+ prefs.accent_color && /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: [
3091
+ { key: "custom_bg_color", label: "Background Color", defaultVal: "#f3f4f6" },
3092
+ { key: "custom_title_color", label: "Title Color", defaultVal: "#f9fafb" },
3093
+ { key: "custom_window_color", label: "Windows Background", defaultVal: "#ffffff" },
3094
+ { key: "custom_button_color", label: "Button Color", defaultVal: "#2563eb" },
3095
+ { key: "accent_color", label: "Accent Color", defaultVal: "#8b5cf6" }
3096
+ ].map((item) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3097
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-40 shrink-0", children: item.label }),
3098
+ /* @__PURE__ */ jsx(
3099
+ "label",
3100
+ {
3101
+ className: "w-8 h-8 rounded-lg border-2 border-gray-300 overflow-hidden cursor-pointer flex items-center justify-center shrink-0",
3102
+ style: { backgroundColor: prefs[item.key] || item.defaultVal },
3103
+ children: /* @__PURE__ */ jsx(
3104
+ "input",
3105
+ {
3106
+ type: "color",
3107
+ value: prefs[item.key] || item.defaultVal,
3108
+ onChange: (e) => savePref(item.key, e.target.value),
3109
+ className: "opacity-0 absolute w-0 h-0"
3110
+ }
3111
+ )
3112
+ }
3113
+ ),
3114
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 font-mono", children: prefs[item.key] || item.defaultVal })
3115
+ ] }, item.key)) })
3116
+ ] }),
3117
+ /* @__PURE__ */ jsxs("div", { children: [
3118
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Desktop Wallpaper" }),
3119
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 flex-wrap items-center", children: [
3120
+ /* @__PURE__ */ jsx(
3121
+ "button",
3122
+ {
3123
+ onClick: () => savePref("desktop_bg", "none"),
3124
+ className: `w-28 h-20 rounded border-2 overflow-hidden flex items-center justify-center ${rawBg === "none" ? "border-blue-500" : "border-gray-300"}`,
3125
+ style: { backgroundColor: previewColor(resolved, "#f3f4f6", "#1e1e2e", "#fdf2f8", "#f0fdf4", "#d1d5db", "#eff6ff") },
3126
+ children: /* @__PURE__ */ jsx("span", { className: "text-[9px] text-gray-500", children: "None" })
3127
+ }
3128
+ ),
3129
+ /* @__PURE__ */ jsx(
3130
+ "button",
3131
+ {
3132
+ onClick: () => savePref("desktop_bg", "random"),
3133
+ className: `w-28 h-20 rounded border-2 overflow-hidden flex items-center justify-center ${rawBg === "random" ? "border-blue-500" : "border-gray-300"}`,
3134
+ children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 grid-rows-2 w-full h-full", children: WALLPAPERS.slice(0, 4).map((wp) => /* @__PURE__ */ jsx("img", { src: wp.src, alt: "", loading: "lazy", className: "w-full h-full object-cover" }, wp.src)) })
3135
+ }
3136
+ ),
3137
+ WALLPAPERS.map((wp) => /* @__PURE__ */ jsx(
3138
+ "button",
3139
+ {
3140
+ onClick: () => savePref("desktop_bg", wp.src),
3141
+ className: `w-28 h-20 rounded border-2 overflow-hidden ${rawBg === wp.src ? "border-blue-500" : "border-gray-300"}`,
3142
+ children: /* @__PURE__ */ jsx("img", { src: wp.src, alt: wp.label, loading: "lazy", className: "w-full h-full object-cover" })
3143
+ },
3144
+ wp.src
3145
+ )),
3146
+ (customBg || isCustom) && /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
3147
+ /* @__PURE__ */ jsx(
3148
+ "button",
3149
+ {
3150
+ onClick: () => savePref("desktop_bg", customBg || desktopBg),
3151
+ className: `w-28 h-20 rounded border-2 overflow-hidden ${isCustom ? "border-blue-500" : "border-gray-300"}`,
3152
+ children: /* @__PURE__ */ jsx("img", { src: customBg || desktopBg, alt: "Custom", loading: "lazy", className: "w-full h-full object-cover" })
3153
+ }
3154
+ ),
3155
+ /* @__PURE__ */ jsx(
3156
+ "button",
3157
+ {
3158
+ onClick: () => save({ desktop_bg: "random", desktop_bg_custom: "" }),
3159
+ className: "absolute -top-1.5 -right-1.5 h-4 w-4 rounded-full bg-red-500 text-white flex items-center justify-center text-[10px] leading-none opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-600 shadow",
3160
+ children: "\xD7"
3161
+ }
3162
+ )
3163
+ ] }),
3164
+ /* @__PURE__ */ jsxs("label", { className: "w-28 h-20 rounded border-2 border-gray-300 border-dashed overflow-hidden cursor-pointer flex items-center justify-center text-gray-400 hover:text-gray-600", children: [
3165
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }),
3166
+ /* @__PURE__ */ jsx("input", { type: "file", accept: "image/*", className: "hidden", onChange: (e) => {
3167
+ const file = e.target.files?.[0];
3168
+ if (!file) return;
3169
+ const reader = new FileReader();
3170
+ reader.onloadend = () => {
3171
+ if (reader.result) {
3172
+ save({ desktop_bg: reader.result, desktop_bg_custom: reader.result });
3173
+ }
3174
+ };
3175
+ reader.readAsDataURL(file);
3176
+ } })
3177
+ ] })
3178
+ ] })
3179
+ ] }),
3180
+ /* @__PURE__ */ jsxs("div", { children: [
3181
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Transparency" }),
3182
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: [
3183
+ { key: "transparency_taskbar", label: "Taskbar", defaultVal: 70 },
3184
+ { key: "transparency_start_menu", label: "Start Menu", defaultVal: 70 },
3185
+ { key: "transparency_inactive_header", label: "Inactive Windows Header / Footer", defaultVal: 70 },
3186
+ { key: "transparency_inactive_content", label: "Inactive Windows Content", defaultVal: 80 },
3187
+ { key: "transparency_active_header", label: "Active Windows Header / Footer", defaultVal: 80 },
3188
+ { key: "transparency_active_content", label: "Active Windows Content", defaultVal: 90 }
3189
+ ].map((item) => {
3190
+ const val = localSliders[item.key] ?? prefs[item.key] ?? item.defaultVal;
3191
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3192
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-64 shrink-0", children: item.label }),
3193
+ /* @__PURE__ */ jsx(
3194
+ "input",
3195
+ {
3196
+ type: "range",
3197
+ min: 20,
3198
+ max: 100,
3199
+ value: val,
3200
+ onChange: (e) => saveSlider(item.key, Number(e.target.value)),
3201
+ className: "flex-1 h-1.5 accent-blue-600 cursor-pointer"
3202
+ }
3203
+ ),
3204
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500 w-10 text-right font-mono", children: [
3205
+ val,
3206
+ "%"
3207
+ ] })
3208
+ ] }, item.key);
3209
+ }) })
3210
+ ] }),
3211
+ /* @__PURE__ */ jsxs("div", { children: [
3212
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Taskbar" }),
3213
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3214
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-24 shrink-0", children: "Position" }),
3215
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: ["bottom", "top", "left", "right"].map((pos) => /* @__PURE__ */ jsx(
3216
+ "button",
3217
+ {
3218
+ onClick: () => savePref("taskbar_position", pos),
3219
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.taskbar_position || "bottom") === pos ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3220
+ children: pos.charAt(0).toUpperCase() + pos.slice(1)
3221
+ },
3222
+ pos
3223
+ )) })
3224
+ ] }) })
3225
+ ] }),
3226
+ /* @__PURE__ */ jsxs("div", { children: [
3227
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Menu" }),
3228
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mb-3", children: "Controls font size, padding, taskbar height, and window button height across the Start Menu, context menus, dropdowns, and the notification popup." }),
3229
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3230
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3231
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-24 shrink-0", children: "Size" }),
3232
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
3233
+ { key: "small", label: "Small" },
3234
+ { key: "medium", label: "Medium" },
3235
+ { key: "large", label: "Large" }
3236
+ ].map((s) => /* @__PURE__ */ jsx(
3237
+ "button",
3238
+ {
3239
+ onClick: () => savePref("start_menu_size", s.key),
3240
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.start_menu_size || "medium") === s.key ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3241
+ children: s.label
3242
+ },
3243
+ s.key
3244
+ )) })
3245
+ ] }),
3246
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3247
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-24 shrink-0", children: "Density" }),
3248
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
3249
+ { key: "tight", label: "Tight" },
3250
+ { key: "normal", label: "Normal" }
3251
+ ].map((s) => /* @__PURE__ */ jsx(
3252
+ "button",
3253
+ {
3254
+ onClick: () => savePref("menu_density", s.key),
3255
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.menu_density || "normal") === s.key ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3256
+ children: s.label
3257
+ },
3258
+ s.key
3259
+ )) })
3260
+ ] })
3261
+ ] })
3262
+ ] }),
3263
+ /* @__PURE__ */ jsxs("div", { children: [
3264
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Behavior" }),
3265
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3266
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3267
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-40 shrink-0", children: "New window position" }),
3268
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
3269
+ { key: "center", label: "Center" },
3270
+ { key: "cascade", label: "Cascade" }
3271
+ ].map((s) => /* @__PURE__ */ jsx(
3272
+ "button",
3273
+ {
3274
+ onClick: () => savePref("window_position", s.key),
3275
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.window_position || "cascade") === s.key ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3276
+ children: s.label
3277
+ },
3278
+ s.key
3279
+ )) })
3280
+ ] }),
3281
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3282
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-40 shrink-0", children: "Double-click desktop" }),
3283
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
3284
+ { key: "deactivate", label: "Deactivate all" },
3285
+ { key: "nothing", label: "Do nothing" }
3286
+ ].map((s) => /* @__PURE__ */ jsx(
3287
+ "button",
3288
+ {
3289
+ onClick: () => savePref("desktop_dblclick", s.key),
3290
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.desktop_dblclick || "deactivate") === s.key ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3291
+ children: s.label
3292
+ },
3293
+ s.key
3294
+ )) })
3295
+ ] }),
3296
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3297
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 w-40 shrink-0", children: "Default window size" }),
3298
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
3299
+ { key: "small", label: "Small" },
3300
+ { key: "medium", label: "Medium" },
3301
+ { key: "large", label: "Large" },
3302
+ { key: "maximized", label: "Maximized" }
3303
+ ].map((s) => /* @__PURE__ */ jsx(
3304
+ "button",
3305
+ {
3306
+ onClick: () => savePref("default_window_size", s.key),
3307
+ className: `px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${(prefs.default_window_size || "large") === s.key ? "bg-blue-600 text-white border-blue-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50"}`,
3308
+ children: s.label
3309
+ },
3310
+ s.key
3311
+ )) })
3312
+ ] })
3313
+ ] })
3314
+ ] }),
3315
+ /* @__PURE__ */ jsxs("div", { children: [
3316
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Desktop" }),
3317
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3318
+ /* @__PURE__ */ jsx(
3319
+ "input",
3320
+ {
3321
+ type: "checkbox",
3322
+ checked: prefs.show_desktop_version ?? true,
3323
+ onChange: (e) => savePref("show_desktop_version", e.target.checked),
3324
+ className: "h-4 w-4 rounded border-gray-300 text-blue-600"
3325
+ }
3326
+ ),
3327
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: "Show version on desktop" }),
3328
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 ml-1 font-mono", children: getVersion() })
3329
+ ] }),
3330
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer mt-2", children: [
3331
+ /* @__PURE__ */ jsx(
3332
+ "input",
3333
+ {
3334
+ type: "checkbox",
3335
+ checked: prefs.auto_fullscreen ?? false,
3336
+ onChange: (e) => savePref("auto_fullscreen", e.target.checked),
3337
+ className: "h-4 w-4 rounded border-gray-300 text-blue-600"
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: "Enter full screen mode automatically" }),
3341
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 ml-1", children: "\u2014 on login" })
3342
+ ] }),
3343
+ /* @__PURE__ */ jsx(SoundSettings, {})
3344
+ ] }),
3345
+ /* @__PURE__ */ jsx(ModalActions, { children: /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Changes are saved automatically" }) })
3346
+ ] });
3347
+ }
3348
+ function SoundSettings() {
3349
+ const [enabled, setEnabled] = useState(soundsEnabled());
3350
+ const [config, setConfig] = useState(getSoundConfig());
3351
+ const update = (soundType, packKey) => {
3352
+ setSoundForType(soundType, packKey);
3353
+ setConfig(getSoundConfig());
3354
+ previewSound(packKey, soundType);
3355
+ };
3356
+ const applyAll = (packKey) => {
3357
+ setAllSounds(packKey);
3358
+ setConfig(getSoundConfig());
3359
+ previewSound(packKey, "success");
3360
+ };
3361
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2", children: [
3362
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3363
+ /* @__PURE__ */ jsx(
3364
+ "input",
3365
+ {
3366
+ type: "checkbox",
3367
+ checked: enabled,
3368
+ onChange: (e) => {
3369
+ localStorage.setItem("erp_sounds", String(e.target.checked));
3370
+ setEnabled(e.target.checked);
3371
+ },
3372
+ className: "h-4 w-4 rounded border-gray-300 text-blue-600"
3373
+ }
3374
+ ),
3375
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: "Sound effects" })
3376
+ ] }),
3377
+ enabled && /* @__PURE__ */ jsx("div", { className: "ml-6", children: /* @__PURE__ */ jsx("div", { className: "border border-gray-200 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-[11px]", children: [
3378
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "bg-gray-50", children: [
3379
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-1.5 text-left font-medium text-gray-500 w-24" }),
3380
+ SOUND_PACK_KEYS.map((key) => /* @__PURE__ */ jsx("th", { className: "px-1 py-1.5 text-center", children: /* @__PURE__ */ jsx("button", { onClick: () => applyAll(key), className: "font-medium text-gray-500 hover:text-blue-600 transition-colors", children: SOUND_PACKS[key].label }) }, key))
3381
+ ] }) }),
3382
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-100", children: SOUND_TYPES.map((soundType) => /* @__PURE__ */ jsxs("tr", { children: [
3383
+ /* @__PURE__ */ jsx("td", { className: "px-2 py-1.5 text-gray-700 font-medium", children: SOUND_TYPE_LABELS[soundType] }),
3384
+ SOUND_PACK_KEYS.map((packKey) => /* @__PURE__ */ jsx("td", { className: "px-1 py-1.5 text-center", children: /* @__PURE__ */ jsx(
3385
+ "button",
3386
+ {
3387
+ onClick: () => update(soundType, packKey),
3388
+ className: `w-4 h-4 rounded-full border-2 transition-colors ${config[soundType] === packKey ? "bg-blue-600 border-blue-600" : "border-gray-300 hover:border-blue-400"}`
3389
+ }
3390
+ ) }, packKey))
3391
+ ] }, soundType)) })
3392
+ ] }) }) })
3393
+ ] });
3394
+ }
3395
+ var EntityFetcherContext = createContext(null);
3396
+ function ShellEntityFetcherProvider({
3397
+ value,
3398
+ children
3399
+ }) {
3400
+ return /* @__PURE__ */ jsx(EntityFetcherContext.Provider, { value, children });
3401
+ }
3402
+ function useShellEntityFetcher() {
3403
+ const ctx = useContext(EntityFetcherContext);
3404
+ return ctx ?? ((endpoint) => {
3405
+ throw new Error(
3406
+ `react-os-shell: cannot fetch ${endpoint} \u2014 no <ShellEntityFetcherProvider> mounted. If your app registers entity windows, supply an entityFetcher.`
3407
+ );
3408
+ });
3409
+ }
3410
+ function useNewHotkey(callback) {
3411
+ useEffect(() => {
3412
+ const handler = (e) => {
3413
+ const isN = e.code === "KeyN" || e.key === "N" || e.key === "n";
3414
+ if (e.altKey && e.shiftKey && isN && !e.metaKey && !e.ctrlKey) {
3415
+ e.preventDefault();
3416
+ e.stopPropagation();
3417
+ callback();
3418
+ }
3419
+ };
3420
+ window.addEventListener("keydown", handler, true);
3421
+ return () => window.removeEventListener("keydown", handler, true);
3422
+ }, [callback]);
3423
+ }
3424
+ function useEditHotkey(callback) {
3425
+ const isActive = useModalActive();
3426
+ useEffect(() => {
3427
+ if (!callback || !isActive) return;
3428
+ const handler = (e) => {
3429
+ const isE = e.code === "KeyE" || e.key === "E" || e.key === "e";
3430
+ if (e.altKey && e.shiftKey && isE && !e.metaKey && !e.ctrlKey) {
3431
+ e.preventDefault();
3432
+ e.stopPropagation();
3433
+ callback();
3434
+ }
3435
+ };
3436
+ window.addEventListener("keydown", handler, true);
3437
+ return () => window.removeEventListener("keydown", handler, true);
3438
+ }, [callback, isActive]);
3439
+ }
3440
+
3441
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, BugReportConfigProvider, BugReportDetail, BugReportProvider, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Customization, Desktop, DesktopHostProvider, ENTER, GlobalSearch, Layout, MOD, NotificationBell, SHIFT, ShellAuthProvider, ShellEntityFetcherProvider, ShortcutHelp, StartMenu, StatusBadge, StatusBadgeProvider, createWindowRegistry, isMac, openBugReportDialog, reportBug, setShellAuthBridge, useBugReport, useClickOutside, useDesktopHost, useEditHotkey, useNewHotkey, useShellAuth, useShellEntityFetcher };
3442
+ //# sourceMappingURL=index.js.map
3443
+ //# sourceMappingURL=index.js.map