react-os-shell 0.2.43 → 0.2.44

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 (59) hide show
  1. package/dist/{Browser-5ZCLRIRU.js → Browser-LSJORULM.js} +3 -3
  2. package/dist/{Browser-5ZCLRIRU.js.map → Browser-LSJORULM.js.map} +1 -1
  3. package/dist/{Calculator-OHL2AZ52.js → Calculator-ZKEH53OV.js} +4 -4
  4. package/dist/{Calculator-OHL2AZ52.js.map → Calculator-ZKEH53OV.js.map} +1 -1
  5. package/dist/{Calendar-6AHL3UJY.js → Calendar-4UQDQ3NT.js} +3 -3
  6. package/dist/{Calendar-6AHL3UJY.js.map → Calendar-4UQDQ3NT.js.map} +1 -1
  7. package/dist/{CurrencyConverter-XZVZ7XOF.js → CurrencyConverter-TXBFDFG2.js} +56 -50
  8. package/dist/CurrencyConverter-TXBFDFG2.js.map +1 -0
  9. package/dist/{Documents-IPVZ47JX.js → Documents-CQVIIFZV.js} +3 -3
  10. package/dist/{Documents-IPVZ47JX.js.map → Documents-CQVIIFZV.js.map} +1 -1
  11. package/dist/{Email-Z7FDKAFD.js → Email-HRBZUWPY.js} +3 -3
  12. package/dist/{Email-Z7FDKAFD.js.map → Email-HRBZUWPY.js.map} +1 -1
  13. package/dist/Files-ITIKVHIE.js +8 -0
  14. package/dist/{Files-KEHRZ2UY.js.map → Files-ITIKVHIE.js.map} +1 -1
  15. package/dist/{Minesweeper-NGN4O6C4.js → Minesweeper-QGUPDVRS.js} +3 -3
  16. package/dist/{Minesweeper-NGN4O6C4.js.map → Minesweeper-QGUPDVRS.js.map} +1 -1
  17. package/dist/{Notepad-W3YYZ3GS.js → Notepad-74CQPZCV.js} +3 -3
  18. package/dist/{Notepad-W3YYZ3GS.js.map → Notepad-74CQPZCV.js.map} +1 -1
  19. package/dist/PomodoroTimer-PRP5CZ3S.js +627 -0
  20. package/dist/PomodoroTimer-PRP5CZ3S.js.map +1 -0
  21. package/dist/Preview-4MBQI66Q.js +7 -0
  22. package/dist/{Preview-4354N46U.js.map → Preview-4MBQI66Q.js.map} +1 -1
  23. package/dist/{Spreadsheet-FCFII6DW.js → Spreadsheet-MKXPPSKV.js} +3 -3
  24. package/dist/{Spreadsheet-FCFII6DW.js.map → Spreadsheet-MKXPPSKV.js.map} +1 -1
  25. package/dist/Weather-YXSCSPQT.js +424 -0
  26. package/dist/Weather-YXSCSPQT.js.map +1 -0
  27. package/dist/WorldClock-QO5PVJQQ.js +250 -0
  28. package/dist/WorldClock-QO5PVJQQ.js.map +1 -0
  29. package/dist/apps/index.d.ts +6 -10
  30. package/dist/apps/index.js +18 -18
  31. package/dist/apps/index.js.map +1 -1
  32. package/dist/chunk-7CCHEEYC.js +94 -0
  33. package/dist/chunk-7CCHEEYC.js.map +1 -0
  34. package/dist/{chunk-T2NQXP2J.js → chunk-7M3BBAHQ.js} +10 -4
  35. package/dist/chunk-7M3BBAHQ.js.map +1 -0
  36. package/dist/{chunk-IY7JJVHR.js → chunk-DUUANLLE.js} +3 -3
  37. package/dist/{chunk-IY7JJVHR.js.map → chunk-DUUANLLE.js.map} +1 -1
  38. package/dist/chunk-MK3HLUO4.js +380 -0
  39. package/dist/chunk-MK3HLUO4.js.map +1 -0
  40. package/dist/{chunk-2SRU4BYH.js → chunk-MTLVXT2C.js} +4 -4
  41. package/dist/{chunk-2SRU4BYH.js.map → chunk-MTLVXT2C.js.map} +1 -1
  42. package/dist/{chunk-TVOBLSSV.js → chunk-UK2AA3J6.js} +3 -3
  43. package/dist/{chunk-TVOBLSSV.js.map → chunk-UK2AA3J6.js.map} +1 -1
  44. package/dist/index.d.ts +16 -1
  45. package/dist/index.js +4089 -11
  46. package/dist/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/CurrencyConverter-XZVZ7XOF.js.map +0 -1
  49. package/dist/Files-KEHRZ2UY.js +0 -8
  50. package/dist/PomodoroTimer-T2J5NDJR.js +0 -196
  51. package/dist/PomodoroTimer-T2J5NDJR.js.map +0 -1
  52. package/dist/Preview-4354N46U.js +0 -7
  53. package/dist/Weather-XTADR7Z3.js +0 -266
  54. package/dist/Weather-XTADR7Z3.js.map +0 -1
  55. package/dist/WorldClock-OFK2EA2H.js +0 -126
  56. package/dist/WorldClock-OFK2EA2H.js.map +0 -1
  57. package/dist/chunk-7KZWBIDL.js +0 -4144
  58. package/dist/chunk-7KZWBIDL.js.map +0 -1
  59. package/dist/chunk-T2NQXP2J.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,27 +1,684 @@
1
- import { useBugReport, useDesktopHost } from './chunk-7KZWBIDL.js';
2
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, BugReportConfigProvider, BugReportProvider, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Desktop, DesktopHostProvider, ENTER, GlobalSearch, Layout, MOD, NotificationBell, SHIFT, ShellAuthProvider, ShortcutHelp, StartMenu, VERSION, isMac, openBugReportDialog, reportBug, setShellAuthBridge, useBugReport, useClickOutside, useDesktopHost, useShellAuth } from './chunk-7KZWBIDL.js';
3
- import './chunk-PDFQNHW7.js';
1
+ import { useEmailUnreadCount } from './chunk-PDFQNHW7.js';
4
2
  import { formatDate } from './chunk-NSU7OHPC.js';
5
3
  export { formatDate } from './chunk-NSU7OHPC.js';
6
- import './chunk-46LICZUM.js';
4
+ import { useGoogleAuth } from './chunk-46LICZUM.js';
5
+ import { subscribePomo, getPomoSnapshot } from './chunk-MK3HLUO4.js';
7
6
  import { useShellPrefs } from './chunk-36VM54SC.js';
8
7
  export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-36VM54SC.js';
9
- import { soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
10
- import { setPdfPreview } from './chunk-IY7JJVHR.js';
8
+ import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
9
+ import { setPdfPreview } from './chunk-DUUANLLE.js';
11
10
  import './chunk-KUIPWCTJ.js';
11
+ import { toast_default } from './chunk-WIJ45SYD.js';
12
12
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
13
- import { useWindowManager, Modal, ModalActions, useModalActive } from './chunk-T2NQXP2J.js';
14
- 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-T2NQXP2J.js';
13
+ import { useWindowManager, glassStyle, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, startMenuCategories, useIsMobile, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons, ModalActions, useModalActive, WINDOW_REGISTRY, isPageEntry, LoadingSpinner, ThumbCard, activateModal } from './chunk-7M3BBAHQ.js';
14
+ export { CancelButton, CopyButton, DocFavStar, GLASS_DIVIDER, GLASS_INPUT_BG, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, glassStyle, isEntityEntry, isPageEntry, setShellApiClient, setShellNavIcons, setShellWindowRegistry, setWindowDefaultPosition, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-7M3BBAHQ.js';
15
15
  import { confirm } from './chunk-PLGHQ7QW.js';
16
16
  export { ConfirmProvider, confirm, confirmDestructive, prompt } from './chunk-PLGHQ7QW.js';
17
- import { createContext, useState, useContext, useRef, useCallback, useEffect } from 'react';
18
- import { useQueryClient, useMutation } from '@tanstack/react-query';
17
+ import { createContext, lazy, useState, useRef, useEffect, useCallback, useLayoutEffect, useContext, Suspense, isValidElement, cloneElement, useSyncExternalStore, useMemo } from 'react';
19
18
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
19
+ import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react';
20
+ import { createPortal } from 'react-dom';
21
+ import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
22
+ import 'react-router-dom';
20
23
 
21
24
  // src/windowRegistry/createWindowRegistry.ts
22
25
  function createWindowRegistry(...partials) {
23
26
  return Object.assign({}, ...partials);
24
27
  }
28
+ 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";
29
+ function GlobalSearch({ providers, typeIcons, placeholder = "Search..." } = {}) {
30
+ const [open, setOpen] = useState(false);
31
+ const [query, setQuery] = useState("");
32
+ const [results, setResults] = useState([]);
33
+ const [loading, setLoading] = useState(false);
34
+ const [activeIdx, setActiveIdx] = useState(0);
35
+ const inputRef = useRef(null);
36
+ const { openEntity } = useWindowManager();
37
+ const debounceRef = useRef();
38
+ useEffect(() => {
39
+ const handler = (e) => {
40
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
41
+ e.preventDefault();
42
+ setOpen((prev) => !prev);
43
+ }
44
+ if (e.key === "Escape" && open) {
45
+ setOpen(false);
46
+ }
47
+ };
48
+ window.addEventListener("keydown", handler);
49
+ return () => window.removeEventListener("keydown", handler);
50
+ }, [open]);
51
+ useEffect(() => {
52
+ if (open) {
53
+ setTimeout(() => inputRef.current?.focus(), 50);
54
+ } else {
55
+ setQuery("");
56
+ setResults([]);
57
+ }
58
+ }, [open]);
59
+ useEffect(() => {
60
+ if (debounceRef.current) clearTimeout(debounceRef.current);
61
+ if (query.length < 2) {
62
+ setResults([]);
63
+ return;
64
+ }
65
+ if (!providers || providers.length === 0) {
66
+ setResults([]);
67
+ return;
68
+ }
69
+ debounceRef.current = setTimeout(async () => {
70
+ setLoading(true);
71
+ try {
72
+ const responses = await Promise.all(providers.map((p) => p(query).catch(() => [])));
73
+ setResults(responses.flat());
74
+ setActiveIdx(0);
75
+ } catch {
76
+ setResults([]);
77
+ }
78
+ setLoading(false);
79
+ }, 250);
80
+ return () => {
81
+ if (debounceRef.current) clearTimeout(debounceRef.current);
82
+ };
83
+ }, [query, providers]);
84
+ const handleSelect = useCallback((result) => {
85
+ setOpen(false);
86
+ openEntity(result.entity_type, result.entity_id, null, result.label);
87
+ }, [openEntity]);
88
+ const handleKeyDown = (e) => {
89
+ if (e.key === "ArrowDown") {
90
+ e.preventDefault();
91
+ setActiveIdx((i) => Math.min(i + 1, results.length - 1));
92
+ } else if (e.key === "ArrowUp") {
93
+ e.preventDefault();
94
+ setActiveIdx((i) => Math.max(i - 1, 0));
95
+ } else if (e.key === "Enter" && results[activeIdx]) {
96
+ handleSelect(results[activeIdx]);
97
+ }
98
+ };
99
+ if (!open) return null;
100
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[200] flex items-start justify-center pt-[15vh]", onClick: () => setOpen(false), children: [
101
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
102
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-xl rounded-2xl overflow-hidden", onClick: (e) => e.stopPropagation(), style: glassStyle(), children: [
103
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-gray-200", children: [
104
+ /* @__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 }) }),
105
+ /* @__PURE__ */ jsx(
106
+ "input",
107
+ {
108
+ ref: inputRef,
109
+ type: "text",
110
+ value: query,
111
+ onChange: (e) => setQuery(e.target.value),
112
+ onKeyDown: handleKeyDown,
113
+ placeholder,
114
+ className: "flex-1 text-sm bg-transparent border-0 outline-none placeholder-gray-400"
115
+ }
116
+ ),
117
+ /* @__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" })
118
+ ] }),
119
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[50vh] overflow-y-auto", children: [
120
+ loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Searching..." }),
121
+ !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." }),
122
+ !loading && results.length > 0 && /* @__PURE__ */ jsx("ul", { className: "py-2", children: results.map((r, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
123
+ "button",
124
+ {
125
+ 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"}`,
126
+ onClick: () => handleSelect(r),
127
+ onMouseEnter: () => setActiveIdx(i),
128
+ children: [
129
+ /* @__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 }) }),
130
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
131
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: r.label }),
132
+ r.sub && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 truncate", children: r.sub })
133
+ ] }),
134
+ /* @__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 })
135
+ ]
136
+ }
137
+ ) }, `${r.type}-${r.label}-${i}`)) }),
138
+ !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..." })
139
+ ] }),
140
+ /* @__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: [
141
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
142
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
143
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2191" }),
144
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2193" }),
145
+ " navigate"
146
+ ] }),
147
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
148
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Enter" }),
149
+ " open"
150
+ ] })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
153
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Esc" }),
154
+ " close"
155
+ ] })
156
+ ] })
157
+ ] })
158
+ ] });
159
+ }
160
+
161
+ // src/shell/Kbd.tsx
162
+ var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
163
+ var MOD = isMac ? "\u2318" : "Ctrl";
164
+ var ALT = isMac ? "\u2325" : "Alt";
165
+ var SHIFT = "\u21E7";
166
+ var ENTER = "\u23CE";
167
+ var CMD_ENTER = isMac ? "\u2318\u23CE" : "Ctrl\u23CE";
168
+ var CMD_S = isMac ? "\u2318S" : "Ctrl+S";
169
+ var CMD_K = isMac ? "\u2318K" : "Ctrl+K";
170
+ var CMD_DOT = isMac ? "\u2318." : "Ctrl+.";
171
+ var CMD_A = isMac ? "\u2318A" : "Ctrl+A";
172
+ var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
173
+ var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
174
+ var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
175
+ var sections = [
176
+ {
177
+ title: "Global",
178
+ shortcuts: [
179
+ { keys: CMD_K, description: "Search" },
180
+ { keys: CMD_DOT, description: "Toggle sidebar" },
181
+ { keys: "Ctrl F11", description: "Toggle fullscreen" },
182
+ { keys: "ESC", description: "Exit fullscreen (when no windows open)" },
183
+ { keys: "?", description: "Show keyboard shortcuts" }
184
+ ]
185
+ },
186
+ {
187
+ title: "Lists",
188
+ shortcuts: [
189
+ { keys: "J / \u2193", description: "Next row" },
190
+ { keys: "K / \u2191", description: "Previous row" },
191
+ { keys: "\u23CE", description: "Open selected row" },
192
+ { keys: "\u21E7J / \u21E7K", description: "Move and select" },
193
+ { keys: "\u21E7 Click", description: "Range select" },
194
+ { keys: "Space", description: "Toggle row checkbox" },
195
+ { keys: CMD_A, description: "Select / deselect all" },
196
+ { keys: ALT_SHIFT_N, description: "Create new item" },
197
+ { keys: ALT_SHIFT_E, description: "Edit selected item" }
198
+ ]
199
+ },
200
+ {
201
+ title: "Modals / Forms",
202
+ shortcuts: [
203
+ { keys: CMD_ENTER, description: "Submit" },
204
+ { keys: CMD_S, description: "Save" },
205
+ { keys: ALT_SHIFT_D, description: "Save as new (duplicate)" },
206
+ { keys: "ESC", description: "Close modal" }
207
+ ]
208
+ }
209
+ ];
210
+ function ShortcutHelp() {
211
+ const [open, setOpen] = useState(false);
212
+ useEffect(() => {
213
+ const toggle = () => setOpen((prev) => !prev);
214
+ document.addEventListener("toggle-shortcut-help", toggle);
215
+ return () => document.removeEventListener("toggle-shortcut-help", toggle);
216
+ }, []);
217
+ useEffect(() => {
218
+ const handler = (e) => {
219
+ if (e.key === "?" && !e.metaKey && !e.ctrlKey && !e.altKey) {
220
+ const tag = e.target?.tagName;
221
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
222
+ if (e.target?.isContentEditable) return;
223
+ e.preventDefault();
224
+ setOpen((prev) => !prev);
225
+ }
226
+ };
227
+ window.addEventListener("keydown", handler);
228
+ return () => window.removeEventListener("keydown", handler);
229
+ }, []);
230
+ return /* @__PURE__ */ jsxs(Dialog, { open, onClose: () => setOpen(false), className: "relative z-[9999]", children: [
231
+ /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
232
+ /* @__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: [
233
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 pt-5 pb-3 border-b border-gray-200", children: [
234
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-gray-900", children: "Keyboard Shortcuts" }),
235
+ /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "text-gray-400 hover:text-gray-500 text-sm", children: "ESC" })
236
+ ] }),
237
+ /* @__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: [
238
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2", children: section.title }),
239
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: section.shortcuts.map((s) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
240
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: s.description }),
241
+ /* @__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 })
242
+ ] }, s.keys + s.description)) })
243
+ ] }, section.title)) }),
244
+ /* @__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: [
245
+ "Press ",
246
+ /* @__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: "?" }),
247
+ " to toggle"
248
+ ] }) })
249
+ ] }) })
250
+ ] });
251
+ }
252
+ function timeAgo(dateStr) {
253
+ const diff = Date.now() - new Date(dateStr).getTime();
254
+ const mins = Math.floor(diff / 6e4);
255
+ if (mins < 1) return "just now";
256
+ if (mins < 60) return `${mins}m ago`;
257
+ const hrs = Math.floor(mins / 60);
258
+ if (hrs < 24) return `${hrs}h ago`;
259
+ const days = Math.floor(hrs / 24);
260
+ if (days < 7) return `${days}d ago`;
261
+ return formatDate(dateStr);
262
+ }
263
+ function NotificationBell({
264
+ useUnreadCount,
265
+ list,
266
+ markRead,
267
+ markAllRead,
268
+ onItemClick,
269
+ onViewAll,
270
+ popDirection
271
+ }) {
272
+ const [open, setOpen] = useState(false);
273
+ const dropdownRef = useRef(null);
274
+ const buttonRef = useRef(null);
275
+ const queryClient = useQueryClient();
276
+ const unreadCount = useUnreadCount();
277
+ useEffect(() => {
278
+ if ("Notification" in window && Notification.permission === "default") Notification.requestPermission();
279
+ }, []);
280
+ const handleClick = useCallback((notif) => {
281
+ if (!notif.is_read) {
282
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: Math.max(0, (old.count || 0) - 1) } : old);
283
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
284
+ if (!old?.results) return old;
285
+ return { ...old, results: old.results.map((n) => n.id === notif.id ? { ...n, is_read: true } : n) };
286
+ });
287
+ markRead(notif.id).catch(() => {
288
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
289
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
290
+ });
291
+ }
292
+ setOpen(false);
293
+ onItemClick(notif);
294
+ }, [queryClient, markRead, onItemClick]);
295
+ const [inlineNotif, setInlineNotif] = useState(null);
296
+ const inlineTimerRef = useRef(null);
297
+ const dismissInline = useCallback(() => {
298
+ if (inlineTimerRef.current) {
299
+ clearTimeout(inlineTimerRef.current);
300
+ inlineTimerRef.current = null;
301
+ }
302
+ setInlineNotif(null);
303
+ }, []);
304
+ const showInlineNotif = useCallback((notif, durationMs = 5e3) => {
305
+ setInlineNotif(notif);
306
+ playNotification();
307
+ if (inlineTimerRef.current) clearTimeout(inlineTimerRef.current);
308
+ inlineTimerRef.current = setTimeout(() => setInlineNotif(null), durationMs);
309
+ }, []);
310
+ useEffect(() => () => {
311
+ if (inlineTimerRef.current) clearTimeout(inlineTimerRef.current);
312
+ }, []);
313
+ const prevCountRef = useRef(null);
314
+ useEffect(() => {
315
+ if (prevCountRef.current === null) {
316
+ prevCountRef.current = unreadCount;
317
+ return;
318
+ }
319
+ if (unreadCount > prevCountRef.current) {
320
+ list({ page_size: 1 }).then((data) => {
321
+ const latest = data?.results?.[0];
322
+ if (!latest) return;
323
+ const title = latest.title || "New Notification";
324
+ const body = latest.message || latest.entity_label || "";
325
+ if (document.hidden) {
326
+ if ("Notification" in window && Notification.permission === "granted") {
327
+ const n = new Notification(title, { body, icon: "/favicon.svg", tag: `notif-${latest.id}` });
328
+ n.onclick = () => {
329
+ window.focus();
330
+ handleClick(latest);
331
+ n.close();
332
+ };
333
+ }
334
+ } else {
335
+ showInlineNotif(latest);
336
+ }
337
+ }).catch(() => {
338
+ });
339
+ }
340
+ prevCountRef.current = unreadCount;
341
+ }, [unreadCount, list, showInlineNotif]);
342
+ const { data: notifData } = useQuery({
343
+ queryKey: ["notifications-dropdown"],
344
+ queryFn: () => list({ page_size: 30 }),
345
+ staleTime: 3e4,
346
+ refetchInterval: 3e4
347
+ });
348
+ const notifications = notifData?.results ?? [];
349
+ const handleMarkAllRead = () => {
350
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: 0 } : old);
351
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
352
+ if (!old?.results) return old;
353
+ return { ...old, results: old.results.map((n) => ({ ...n, is_read: true })) };
354
+ });
355
+ markAllRead().then(() => {
356
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
357
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
358
+ });
359
+ };
360
+ const [dropdownPos, setDropdownPos] = useState({});
361
+ const calcPos = useCallback(() => {
362
+ const taskbarPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
363
+ const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 56;
364
+ const taskbarW = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-width")) || 0;
365
+ const verticalReserve = taskbarPos === "top" || taskbarPos === "bottom" ? taskbarH + 16 : 16;
366
+ const maxHeight = `calc(100vh - ${verticalReserve}px)`;
367
+ if (popDirection === "right") setDropdownPos({ left: taskbarW + 8, bottom: 8, maxHeight });
368
+ else if (popDirection === "left") setDropdownPos({ right: taskbarW + 8, bottom: 8, maxHeight });
369
+ else if (taskbarPos === "top") setDropdownPos({ right: 8, top: taskbarH + 8, maxHeight });
370
+ else setDropdownPos({ right: 8, bottom: taskbarH + 8, maxHeight });
371
+ }, [popDirection]);
372
+ useLayoutEffect(() => {
373
+ if (!open) return;
374
+ calcPos();
375
+ }, [open, calcPos]);
376
+ useEffect(() => {
377
+ if (!open) return;
378
+ window.addEventListener("resize", calcPos);
379
+ return () => window.removeEventListener("resize", calcPos);
380
+ }, [open, calcPos]);
381
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "relative", children: [
382
+ /* @__PURE__ */ jsxs(
383
+ "button",
384
+ {
385
+ ref: buttonRef,
386
+ "data-menu-toggle": true,
387
+ onMouseEnter: () => queryClient.prefetchQuery({ queryKey: ["notifications-dropdown"], queryFn: () => list({ page_size: 30 }) }),
388
+ onClick: () => setOpen((prev) => !prev),
389
+ title: "Notifications",
390
+ className: "relative shrink-0 rounded-md p-2 text-gray-900 hover:text-black hover:bg-white/20 transition-colors",
391
+ children: [
392
+ /* @__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" }) }),
393
+ 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 })
394
+ ]
395
+ }
396
+ ),
397
+ open && createPortal(
398
+ /* @__PURE__ */ jsxs(PopupMenu, { minWidth: 320, className: "w-80 flex flex-col overflow-hidden", style: { ...dropdownPos }, onClose: () => setOpen(false), children: [
399
+ /* @__PURE__ */ jsx(PopupMenuLabel, { children: /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between w-full", children: [
400
+ /* @__PURE__ */ jsx("span", { children: "Notifications" }),
401
+ 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" })
402
+ ] }) }),
403
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
404
+ /* @__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: [
405
+ /* @__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" }) }),
406
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 font-medium", children: "All caught up" }),
407
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-0.5", children: "No notifications yet" })
408
+ ] }) : notifications.map((notif) => /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => handleClick(notif), children: [
409
+ /* @__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" }) }),
410
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
411
+ /* @__PURE__ */ jsx("p", { className: `text-sm leading-tight ${!notif.is_read ? "font-medium" : ""}`, children: notif.title }),
412
+ notif.message && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-0.5 truncate", children: notif.message }),
413
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
414
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400", children: timeAgo(notif.created_at) }),
415
+ notif.actor_name && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-400", children: [
416
+ "by ",
417
+ notif.actor_name
418
+ ] })
419
+ ] })
420
+ ] })
421
+ ] }, notif.id)) }),
422
+ onViewAll && /* @__PURE__ */ jsxs(Fragment, { children: [
423
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
424
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
425
+ setOpen(false);
426
+ onViewAll();
427
+ }, children: /* @__PURE__ */ jsx("span", { className: "w-full text-center text-xs text-blue-600 font-medium", children: "View all notifications" }) })
428
+ ] })
429
+ ] }),
430
+ document.body
431
+ ),
432
+ inlineNotif && createPortal(
433
+ /* @__PURE__ */ jsxs(Fragment, { children: [
434
+ /* @__PURE__ */ jsx("style", { children: `@keyframes notif-in { from { opacity: 0; transform: translateX(30px) scale(0.95); } to { opacity: 1; transform: translateX(0) scale(1); } }` }),
435
+ /* @__PURE__ */ jsxs(
436
+ "div",
437
+ {
438
+ onClick: () => {
439
+ handleClick(inlineNotif);
440
+ dismissInline();
441
+ },
442
+ className: "fixed top-4 right-4 z-[9999] w-[320px] max-w-[calc(100vw-2rem)] cursor-pointer rounded-2xl bg-white/85 backdrop-blur-md border border-white/40 shadow-2xl flex items-start gap-3 px-4 py-3",
443
+ style: { animation: "notif-in 300ms cubic-bezier(0.4,0,0.2,1)" },
444
+ children: [
445
+ /* @__PURE__ */ jsx("div", { className: "h-9 w-9 shrink-0 rounded-lg bg-blue-500/15 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-blue-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, 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" }) }) }),
446
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
447
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide text-blue-600 mb-0.5", children: "Notification" }),
448
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-800 leading-snug truncate", children: inlineNotif.title }),
449
+ inlineNotif.message && /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500 leading-snug truncate mt-0.5", children: inlineNotif.message })
450
+ ] }),
451
+ /* @__PURE__ */ jsx(
452
+ "button",
453
+ {
454
+ onClick: (e) => {
455
+ e.stopPropagation();
456
+ dismissInline();
457
+ },
458
+ className: "shrink-0 p-1 -mr-1 -mt-1 text-gray-400 hover:text-gray-600 transition-colors",
459
+ "aria-label": "Dismiss notification",
460
+ children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
461
+ }
462
+ )
463
+ ]
464
+ }
465
+ )
466
+ ] }),
467
+ document.body
468
+ )
469
+ ] });
470
+ }
471
+ var BugReportContext = createContext(null);
472
+ function BugReportConfigProvider({ value, children }) {
473
+ return /* @__PURE__ */ jsx(BugReportContext.Provider, { value, children });
474
+ }
475
+ function useBugReport() {
476
+ return useContext(BugReportContext);
477
+ }
478
+ var globalOpen = () => Promise.resolve(null);
479
+ var openBugReportDialog = (screenshot) => globalOpen(screenshot);
480
+ function BugReportProvider({ children }) {
481
+ const [open, setOpen] = useState(false);
482
+ const [screenshot, setScreenshot] = useState(null);
483
+ const [previewUrl, setPreviewUrl] = useState(null);
484
+ const [description, setDescription] = useState("");
485
+ const [reportType, setReportType] = useState("bug");
486
+ const [annotating, setAnnotating] = useState(false);
487
+ const resolveRef = useRef();
488
+ const openFn = useCallback((s) => {
489
+ setScreenshot(s);
490
+ setDescription("");
491
+ setReportType("bug");
492
+ setAnnotating(false);
493
+ setOpen(true);
494
+ return new Promise((resolve) => {
495
+ resolveRef.current = resolve;
496
+ });
497
+ }, []);
498
+ useEffect(() => {
499
+ globalOpen = openFn;
500
+ }, [openFn]);
501
+ useEffect(() => {
502
+ if (!screenshot) {
503
+ setPreviewUrl(null);
504
+ return;
505
+ }
506
+ const url = URL.createObjectURL(screenshot);
507
+ setPreviewUrl(url);
508
+ return () => URL.revokeObjectURL(url);
509
+ }, [screenshot]);
510
+ const handleSubmit = () => {
511
+ setAnnotating(false);
512
+ setOpen(false);
513
+ resolveRef.current?.({ description: description.trim(), reportType, screenshot });
514
+ };
515
+ const handleCancel = () => {
516
+ setAnnotating(false);
517
+ setOpen(false);
518
+ resolveRef.current?.(null);
519
+ };
520
+ const isBug = reportType === "bug";
521
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
522
+ children,
523
+ /* @__PURE__ */ jsxs(
524
+ Dialog,
525
+ {
526
+ open,
527
+ onClose: annotating ? () => {
528
+ } : handleCancel,
529
+ className: "relative z-[9999]",
530
+ children: [
531
+ /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40" }),
532
+ /* @__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: [
533
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900", children: "Suggestion or Bug" }),
534
+ /* @__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." }),
535
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 inline-flex rounded-lg border border-gray-300 bg-gray-50 p-0.5", children: [
536
+ /* @__PURE__ */ jsx(
537
+ "button",
538
+ {
539
+ type: "button",
540
+ onClick: () => setReportType("bug"),
541
+ className: `px-3 py-1 text-sm font-medium rounded-md transition-colors ${isBug ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
542
+ children: "Bug"
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsx(
546
+ "button",
547
+ {
548
+ type: "button",
549
+ onClick: () => setReportType("suggestion"),
550
+ className: `px-3 py-1 text-sm font-medium rounded-md transition-colors ${!isBug ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
551
+ children: "Suggestion"
552
+ }
553
+ )
554
+ ] }),
555
+ previewUrl && /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
556
+ /* @__PURE__ */ jsxs("div", { className: "relative rounded-md border border-gray-200 overflow-hidden bg-gray-50 max-h-64", children: [
557
+ /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Screenshot preview", className: "w-full h-auto max-h-64 object-contain" }),
558
+ /* @__PURE__ */ jsxs(
559
+ "button",
560
+ {
561
+ type: "button",
562
+ onClick: () => setAnnotating(true),
563
+ className: "absolute top-2 right-2 inline-flex items-center gap-1 px-2.5 py-1 rounded-md bg-white/95 backdrop-blur border border-gray-200 shadow-sm text-xs font-medium text-gray-700 hover:bg-white",
564
+ title: "Mark up the screenshot \u2014 circle, arrow, mosaic, text\u2026",
565
+ children: [
566
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.7, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" }) }),
567
+ "Annotate"
568
+ ]
569
+ }
570
+ )
571
+ ] }),
572
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-gray-400", children: "Click Annotate to mark up the screenshot before sending." })
573
+ ] }),
574
+ !previewUrl && /* @__PURE__ */ jsx(UploadDropZone, { onSelect: (blob) => setScreenshot(blob) }),
575
+ /* @__PURE__ */ jsxs("label", { className: "mt-4 block text-sm font-medium text-gray-700", children: [
576
+ isBug ? "What went wrong?" : "What's your suggestion?",
577
+ /* @__PURE__ */ jsx("span", { className: "font-normal text-gray-400 ml-1", children: "(optional)" })
578
+ ] }),
579
+ /* @__PURE__ */ jsx(
580
+ "textarea",
581
+ {
582
+ autoFocus: true,
583
+ value: description,
584
+ onChange: (e) => setDescription(e.target.value),
585
+ onKeyDown: (e) => {
586
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleSubmit();
587
+ },
588
+ placeholder: isBug ? "Briefly describe the issue, what you were doing, what you expected to happen\u2026" : "Briefly describe what would make this better\u2026",
589
+ rows: 3,
590
+ 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"
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
594
+ /* @__PURE__ */ jsx(
595
+ "button",
596
+ {
597
+ type: "button",
598
+ onClick: handleCancel,
599
+ className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
600
+ children: "Cancel"
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsx(
604
+ "button",
605
+ {
606
+ type: "button",
607
+ onClick: handleSubmit,
608
+ className: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 text-sm font-medium rounded-lg",
609
+ children: "Send"
610
+ }
611
+ )
612
+ ] })
613
+ ] }) }),
614
+ annotating && previewUrl && /* @__PURE__ */ jsx(
615
+ BugReportAnnotator,
616
+ {
617
+ src: previewUrl,
618
+ onApply: (blob) => {
619
+ setScreenshot(blob);
620
+ setAnnotating(false);
621
+ },
622
+ onCancel: () => setAnnotating(false)
623
+ }
624
+ )
625
+ ]
626
+ }
627
+ )
628
+ ] });
629
+ }
630
+ var LazyImageAnnotator = lazy(() => import('./ImageAnnotator-CTTMAY5Z.js'));
631
+ function UploadDropZone({ onSelect }) {
632
+ const inputRef = useRef(null);
633
+ const [hover, setHover] = useState(false);
634
+ const accept = (file) => {
635
+ if (!file) return;
636
+ if (!file.type.startsWith("image/")) return;
637
+ onSelect(file);
638
+ };
639
+ return /* @__PURE__ */ jsxs(
640
+ "div",
641
+ {
642
+ onClick: () => inputRef.current?.click(),
643
+ onDragOver: (e) => {
644
+ e.preventDefault();
645
+ setHover(true);
646
+ },
647
+ onDragLeave: () => setHover(false),
648
+ onDrop: (e) => {
649
+ e.preventDefault();
650
+ setHover(false);
651
+ accept(e.dataTransfer.files?.[0]);
652
+ },
653
+ className: `mt-4 rounded-md border border-dashed px-4 py-6 text-center text-sm cursor-pointer transition-colors ${hover ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-500 hover:border-gray-400 hover:bg-gray-50"}`,
654
+ children: [
655
+ /* @__PURE__ */ jsx("p", { className: "text-gray-700 font-medium", children: "Screenshot capture failed" }),
656
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-gray-500", children: [
657
+ "Drop an image here, or ",
658
+ /* @__PURE__ */ jsx("span", { className: "text-blue-600 underline", children: "click to upload" }),
659
+ ". Your description will be sent either way."
660
+ ] }),
661
+ /* @__PURE__ */ jsx(
662
+ "input",
663
+ {
664
+ ref: inputRef,
665
+ type: "file",
666
+ accept: "image/*",
667
+ onChange: (e) => accept(e.target.files?.[0]),
668
+ className: "hidden"
669
+ }
670
+ )
671
+ ]
672
+ }
673
+ );
674
+ }
675
+ function BugReportAnnotator({
676
+ src,
677
+ onApply,
678
+ onCancel
679
+ }) {
680
+ return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[10000] bg-black/60 backdrop-blur-sm flex flex-col", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-white/80", children: "Loading editor\u2026" }), children: /* @__PURE__ */ jsx("div", { className: "flex-1 m-4 rounded-lg overflow-hidden bg-white shadow-2xl", children: /* @__PURE__ */ jsx(LazyImageAnnotator, { src, filename: "screenshot.png", onApply, onCancel }) }) }) });
681
+ }
25
682
  function StatePill({ resolved }) {
26
683
  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" });
27
684
  }
@@ -203,6 +860,3427 @@ function StatusBadge({ status }) {
203
860
  const label = status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
204
861
  return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${color}`, children: label });
205
862
  }
863
+
864
+ // src/version.ts
865
+ var VERSION = "0.2.44" ;
866
+ var APP_VERSION = VERSION;
867
+
868
+ // src/changelog.ts
869
+ var changelog = [];
870
+ var changelog_default = changelog;
871
+
872
+ // src/utils/reportBug.ts
873
+ async function captureViewport() {
874
+ try {
875
+ const stream = await navigator.mediaDevices.getDisplayMedia({
876
+ video: { displaySurface: "browser" },
877
+ audio: false,
878
+ // Chromium-only hints — ignored by other engines, no harm done.
879
+ preferCurrentTab: true,
880
+ selfBrowserSurface: "include",
881
+ surfaceSwitching: "exclude"
882
+ });
883
+ const video = document.createElement("video");
884
+ video.srcObject = stream;
885
+ video.muted = true;
886
+ await video.play();
887
+ const canvas = document.createElement("canvas");
888
+ canvas.width = video.videoWidth;
889
+ canvas.height = video.videoHeight;
890
+ canvas.getContext("2d").drawImage(video, 0, 0);
891
+ stream.getTracks().forEach((t) => t.stop());
892
+ return await new Promise((resolve) => {
893
+ canvas.toBlob((b) => resolve(b), "image/jpeg", 0.9);
894
+ });
895
+ } catch (err) {
896
+ console.error("Bug report screenshot failed:", err);
897
+ return null;
898
+ }
899
+ }
900
+ async function reportBug(submit) {
901
+ const screenshot = await captureViewport();
902
+ const submission = await openBugReportDialog(screenshot);
903
+ if (submission === null) return;
904
+ const finalScreenshot = submission.screenshot ?? screenshot;
905
+ try {
906
+ await submit({
907
+ description: submission.description || void 0,
908
+ screenshot: finalScreenshot ?? void 0,
909
+ url: window.location.href,
910
+ userAgent: navigator.userAgent,
911
+ viewport: `${window.innerWidth}x${window.innerHeight}`,
912
+ reportType: submission.reportType
913
+ });
914
+ toast_default.success(submission.reportType === "bug" ? "Bug sent to admins." : "Suggestion sent to admins.");
915
+ } catch (err) {
916
+ toast_default.error(err?.response?.data?.detail || "Failed to send.");
917
+ }
918
+ }
919
+ var ENTITY_ICON_COLORS = {
920
+ order: "text-blue-600",
921
+ purchase_order: "text-purple-600",
922
+ invoice: "text-green-600",
923
+ client: "text-indigo-600",
924
+ manufacturer: "text-orange-600",
925
+ shipment: "text-teal-600",
926
+ part_number: "text-gray-600",
927
+ project: "text-pink-600",
928
+ mould: "text-red-600",
929
+ design: "text-cyan-600",
930
+ brand: "text-amber-600",
931
+ price_sheet: "text-emerald-600",
932
+ folder: "text-yellow-600",
933
+ page: "text-blue-500"
934
+ };
935
+ var ENTITY_ICONS = {
936
+ order: "SO",
937
+ purchase_order: "PO",
938
+ invoice: "INV",
939
+ client: "CLI",
940
+ manufacturer: "MFR",
941
+ shipment: "DN",
942
+ part_number: "PN",
943
+ project: "PRJ",
944
+ mould: "MLD",
945
+ design: "DSN",
946
+ brand: "BRD",
947
+ price_sheet: "PS",
948
+ vendor_invoice: "VI",
949
+ vendor_payment: "VP",
950
+ warranty_claim: "WC",
951
+ qc_report: "QC",
952
+ vendor_shipment: "GRN",
953
+ bank_account: "BA",
954
+ wheel_finish: "WF",
955
+ weight_log: "WL",
956
+ production_progress: "PP",
957
+ vendor_price_sheet: "VPS",
958
+ proposal: "PR",
959
+ folder: "FLD"
960
+ };
961
+ var GRID = 90;
962
+ function snapToGrid(x, y) {
963
+ return { x: Math.round(x / GRID) * GRID, y: Math.round(y / GRID) * GRID };
964
+ }
965
+ var DesktopHostContext = createContext({});
966
+ function DesktopHostProvider({ value, children }) {
967
+ return /* @__PURE__ */ jsx(DesktopHostContext.Provider, { value, children });
968
+ }
969
+ function useDesktopHost() {
970
+ return useContext(DesktopHostContext);
971
+ }
972
+ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder }) {
973
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
974
+ const [rubber, setRubber] = useState(null);
975
+ const didDragRubber = useRef(false);
976
+ const bodyRef = useRef(null);
977
+ const [dragIdx, setDragIdx] = useState(null);
978
+ const [dropIdx, setDropIdx] = useState(null);
979
+ const toggleSelect = (i, e) => {
980
+ e.stopPropagation();
981
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
982
+ setSelected((prev) => {
983
+ const next = new Set(prev);
984
+ next.has(i) ? next.delete(i) : next.add(i);
985
+ return next;
986
+ });
987
+ } else {
988
+ setSelected(/* @__PURE__ */ new Set([i]));
989
+ }
990
+ };
991
+ const startRubber = (e) => {
992
+ if (e.button !== 0 || e.target !== bodyRef.current) return;
993
+ const r = bodyRef.current.getBoundingClientRect();
994
+ const x = e.clientX - r.left;
995
+ const y = e.clientY - r.top;
996
+ setRubber({ x1: x, y1: y, x2: x, y2: y });
997
+ didDragRubber.current = false;
998
+ setSelected(/* @__PURE__ */ new Set());
999
+ };
1000
+ useEffect(() => {
1001
+ if (!rubber) return;
1002
+ const move = (e) => {
1003
+ const r = bodyRef.current?.getBoundingClientRect();
1004
+ if (!r) return;
1005
+ const x = e.clientX - r.left;
1006
+ const y = e.clientY - r.top;
1007
+ const dx = x - rubber.x1, dy = y - rubber.y1;
1008
+ if (dx * dx + dy * dy > 16) didDragRubber.current = true;
1009
+ setRubber((prev) => prev ? { ...prev, x2: x, y2: y } : null);
1010
+ };
1011
+ const up = () => {
1012
+ const next = /* @__PURE__ */ new Set();
1013
+ const r = bodyRef.current;
1014
+ if (r && rubber) {
1015
+ const minX = Math.min(rubber.x1, rubber.x2);
1016
+ const maxX = Math.max(rubber.x1, rubber.x2);
1017
+ const minY = Math.min(rubber.y1, rubber.y2);
1018
+ const maxY = Math.max(rubber.y1, rubber.y2);
1019
+ const tiles = r.querySelectorAll("[data-folder-item]");
1020
+ const containerRect = r.getBoundingClientRect();
1021
+ tiles.forEach((t) => {
1022
+ const tr = t.getBoundingClientRect();
1023
+ const tx = tr.left - containerRect.left;
1024
+ const ty = tr.top - containerRect.top;
1025
+ if (tx + tr.width > minX && tx < maxX && ty + tr.height > minY && ty < maxY) {
1026
+ const i = parseInt(t.getAttribute("data-folder-item") || "-1", 10);
1027
+ if (i >= 0) next.add(i);
1028
+ }
1029
+ });
1030
+ }
1031
+ if (didDragRubber.current) setSelected(next);
1032
+ setRubber(null);
1033
+ };
1034
+ window.addEventListener("pointermove", move);
1035
+ window.addEventListener("pointerup", up);
1036
+ return () => {
1037
+ window.removeEventListener("pointermove", move);
1038
+ window.removeEventListener("pointerup", up);
1039
+ };
1040
+ }, [rubber]);
1041
+ const onItemDragStart = (i) => (e) => {
1042
+ e.dataTransfer.effectAllowed = "move";
1043
+ e.dataTransfer.setData("text/plain", String(i));
1044
+ setDragIdx(i);
1045
+ };
1046
+ const onItemDragOver = (i) => (e) => {
1047
+ e.preventDefault();
1048
+ if (dragIdx !== null && dragIdx !== i) setDropIdx(i);
1049
+ };
1050
+ const onItemDrop = (i) => (e) => {
1051
+ e.preventDefault();
1052
+ if (dragIdx === null || dragIdx === i) {
1053
+ setDragIdx(null);
1054
+ setDropIdx(null);
1055
+ return;
1056
+ }
1057
+ const next = [...items];
1058
+ const [moved] = next.splice(dragIdx, 1);
1059
+ next.splice(i, 0, moved);
1060
+ onReorder(next);
1061
+ setDragIdx(null);
1062
+ setDropIdx(null);
1063
+ };
1064
+ const moveSelectedOut = () => {
1065
+ if (selected.size === 0) return;
1066
+ onMoveOut(Array.from(selected).map((i) => items[i]).filter(Boolean));
1067
+ setSelected(/* @__PURE__ */ new Set());
1068
+ };
1069
+ const folderIcon = /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-500", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M2 6a2 2 0 012-2h5l2 2h9a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" }) });
1070
+ return /* @__PURE__ */ jsx(Modal, { open: true, onClose, title: folder.name, icon: folderIcon, size: "lg", children: /* @__PURE__ */ jsxs(
1071
+ "div",
1072
+ {
1073
+ ref: bodyRef,
1074
+ onPointerDown: startRubber,
1075
+ onClick: () => {
1076
+ if (didDragRubber.current) {
1077
+ didDragRubber.current = false;
1078
+ return;
1079
+ }
1080
+ setSelected(/* @__PURE__ */ new Set());
1081
+ },
1082
+ className: "relative h-full min-h-[300px] p-3 overflow-auto",
1083
+ style: {
1084
+ background: "linear-gradient(135deg, rgba(254, 243, 199, 0.55) 0%, rgba(253, 230, 138, 0.4) 50%, rgba(252, 211, 77, 0.3) 100%)"
1085
+ },
1086
+ children: [
1087
+ selected.size > 0 && /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 mb-2 flex items-center gap-2 px-2 py-1 rounded-md bg-white/80 backdrop-blur-sm shadow border border-gray-200 text-xs text-gray-700 w-fit", children: [
1088
+ /* @__PURE__ */ jsxs("span", { children: [
1089
+ selected.size,
1090
+ " selected"
1091
+ ] }),
1092
+ /* @__PURE__ */ jsx("button", { onClick: moveSelectedOut, className: "px-2 py-0.5 rounded text-blue-600 hover:bg-blue-50", children: "Move to desktop" }),
1093
+ /* @__PURE__ */ jsx("button", { onClick: () => setSelected(/* @__PURE__ */ new Set()), className: "px-2 py-0.5 rounded text-gray-500 hover:bg-gray-100", children: "Clear" })
1094
+ ] }),
1095
+ items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 text-center py-8 italic", children: "Folder is empty. Drag documents here." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3", children: items.map((item, i) => {
1096
+ const isSelected = selected.has(i);
1097
+ const isDropTarget = dropIdx === i && dragIdx !== i;
1098
+ return /* @__PURE__ */ jsxs(
1099
+ "div",
1100
+ {
1101
+ "data-folder-item": i,
1102
+ draggable: true,
1103
+ onDragStart: onItemDragStart(i),
1104
+ onDragOver: onItemDragOver(i),
1105
+ onDrop: onItemDrop(i),
1106
+ onDragEnd: () => {
1107
+ setDragIdx(null);
1108
+ setDropIdx(null);
1109
+ },
1110
+ onClick: (e) => toggleSelect(i, e),
1111
+ onDoubleClick: () => onOpen(item),
1112
+ className: `group relative flex flex-col items-center gap-1 w-20 p-2 rounded-lg cursor-default transition-colors ${isSelected ? "bg-blue-200/60 ring-2 ring-blue-400" : "hover:bg-white/60"} ${isDropTarget ? "ring-2 ring-blue-500 ring-dashed" : ""} ${dragIdx === i ? "opacity-40" : ""}`,
1113
+ children: [
1114
+ /* @__PURE__ */ jsx(
1115
+ "button",
1116
+ {
1117
+ onClick: (e) => {
1118
+ e.stopPropagation();
1119
+ onMoveOut([item]);
1120
+ },
1121
+ title: "Move to desktop",
1122
+ 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",
1123
+ 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" }) })
1124
+ }
1125
+ ),
1126
+ /* @__PURE__ */ jsx("div", { className: `w-12 h-12 rounded-lg bg-white 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() }),
1127
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium text-center leading-tight truncate w-full ${isSelected ? "text-blue-900" : "text-gray-700"}`, children: item.label })
1128
+ ]
1129
+ },
1130
+ `${item.entityType}-${item.entityId}-${i}`
1131
+ );
1132
+ }) }),
1133
+ rubber && /* @__PURE__ */ jsx(
1134
+ "div",
1135
+ {
1136
+ className: "absolute border border-blue-500 bg-blue-500/10 pointer-events-none",
1137
+ style: {
1138
+ left: Math.min(rubber.x1, rubber.x2),
1139
+ top: Math.min(rubber.y1, rubber.y2),
1140
+ width: Math.abs(rubber.x2 - rubber.x1),
1141
+ height: Math.abs(rubber.y2 - rubber.y1)
1142
+ }
1143
+ }
1144
+ )
1145
+ ]
1146
+ }
1147
+ ) });
1148
+ }
1149
+ function Desktop({ profile }) {
1150
+ useQueryClient();
1151
+ const { openEntity, openPage } = useWindowManager();
1152
+ const bugReport = useBugReport();
1153
+ const containerRef = useRef(null);
1154
+ const { prefs: shellPrefs, save: saveShellPrefs } = useShellPrefs();
1155
+ const prefs = { ...profile?.preferences || {}, ...shellPrefs };
1156
+ const favDocs = prefs.favorite_documents || [];
1157
+ const folders = prefs.desktop_folders || [];
1158
+ const snapEnabled = prefs.desktop_snap ?? false;
1159
+ const allNotes = prefs.notepad_notes || [];
1160
+ const stickyNotes = allNotes.filter((n) => n.sticky);
1161
+ const host = useDesktopHost();
1162
+ const openStickyRef = async (prefix, number) => {
1163
+ const refNum = `${prefix}#${number}`;
1164
+ if (!host.stickyResolver) {
1165
+ toast_default.error(`Unknown reference: ${refNum}`);
1166
+ return;
1167
+ }
1168
+ try {
1169
+ const result = await host.stickyResolver(prefix, number);
1170
+ if (result) openEntity(result.entityType, result.entityId, result.snapshot, result.label ?? refNum);
1171
+ else toast_default.error(`${refNum} not found`);
1172
+ } catch {
1173
+ toast_default.error(`Failed to open ${refNum}`);
1174
+ }
1175
+ };
1176
+ const toggleStickyCheckbox = (noteId, charIndex) => {
1177
+ const note = allNotes.find((n) => n.id === noteId);
1178
+ if (!note) return;
1179
+ const content = note.content;
1180
+ const match = content.slice(charIndex).match(/^\[([ xX]?)\]/);
1181
+ if (!match) return;
1182
+ const isChecked = match[1] === "x" || match[1] === "X";
1183
+ const replacement = isChecked ? "[ ]" : "[x]";
1184
+ const updated = content.slice(0, charIndex) + replacement + content.slice(charIndex + match[0].length);
1185
+ saveNotes(allNotes.map((n) => n.id === noteId ? { ...n, content: updated, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : n));
1186
+ };
1187
+ const renderStickyContent = (noteId, text) => {
1188
+ if (!text) return [];
1189
+ const lines = text.split("\n");
1190
+ let charOffset = 0;
1191
+ return lines.map((line, li) => {
1192
+ const lineStart = charOffset;
1193
+ charOffset += line.length + 1;
1194
+ const tokens = [];
1195
+ const refRegex = /([A-Z]{2,4})#(\d{4,6})/g;
1196
+ let m;
1197
+ while ((m = refRegex.exec(line)) !== null) {
1198
+ if (host.stickyResolver) {
1199
+ const prefix = m[1], num = m[2], startIdx = m.index, matchText = m[0];
1200
+ tokens.push({ idx: startIdx, len: matchText.length, render: () => /* @__PURE__ */ jsx(
1201
+ "button",
1202
+ {
1203
+ onClick: (e) => {
1204
+ e.stopPropagation();
1205
+ openStickyRef(prefix, num);
1206
+ },
1207
+ className: "text-blue-700 hover:underline font-medium cursor-pointer",
1208
+ children: matchText
1209
+ },
1210
+ `r-${li}-${startIdx}`
1211
+ ) });
1212
+ }
1213
+ }
1214
+ const cbRegex = /\[([ xX]?)\]/g;
1215
+ while ((m = cbRegex.exec(line)) !== null) {
1216
+ const isChecked = m[1] === "x" || m[1] === "X";
1217
+ const startIdx = m.index;
1218
+ const contentCharIdx = lineStart + startIdx;
1219
+ tokens.push({ idx: startIdx, len: m[0].length, render: () => /* @__PURE__ */ jsx(
1220
+ "button",
1221
+ {
1222
+ onClick: (e) => {
1223
+ e.stopPropagation();
1224
+ toggleStickyCheckbox(noteId, contentCharIdx);
1225
+ },
1226
+ 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`,
1227
+ 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" }) })
1228
+ },
1229
+ `c-${li}-${startIdx}`
1230
+ ) });
1231
+ }
1232
+ tokens.sort((a, b) => a.idx - b.idx);
1233
+ const parts = [];
1234
+ let lastIdx = 0;
1235
+ for (const t of tokens) {
1236
+ if (t.idx > lastIdx) parts.push(/* @__PURE__ */ jsx("span", { children: line.slice(lastIdx, t.idx) }, `t-${li}-${lastIdx}`));
1237
+ parts.push(t.render());
1238
+ lastIdx = t.idx + t.len;
1239
+ }
1240
+ if (lastIdx < line.length) parts.push(/* @__PURE__ */ jsx("span", { children: line.slice(lastIdx) }, `t-${li}-${lastIdx}`));
1241
+ if (parts.length === 0) parts.push(/* @__PURE__ */ jsx("span", { children: "\u200B" }, `e-${li}`));
1242
+ const lineHasChecked = /^\[x\]/i.test(line.trimStart());
1243
+ return /* @__PURE__ */ jsx("div", { className: lineHasChecked ? "line-through opacity-50" : "", children: parts }, li);
1244
+ });
1245
+ };
1246
+ const [dragging, setDragging] = useState(null);
1247
+ const [contextMenu, setContextMenu] = useState(null);
1248
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
1249
+ const [rubberBand, setRubberBand] = useState(null);
1250
+ const [openFolder, setOpenFolder] = useState(null);
1251
+ const [aboutOpen, setAboutOpen] = useState(false);
1252
+ const [whatsNewOpen, setWhatsNewOpen] = useState(false);
1253
+ const [renamingFolder, setRenamingFolder] = useState(null);
1254
+ const [renameValue, setRenameValue] = useState("");
1255
+ const [editingStickyId, setEditingStickyId] = useState(null);
1256
+ const [stickyDrag, setStickyDrag] = useState(null);
1257
+ const [stickyResize, setStickyResize] = useState(null);
1258
+ const [hoverFolderIdx, setHoverFolderIdx] = useState(null);
1259
+ const hoverFolderIdxRef = useRef(null);
1260
+ const saveDocs = useCallback((docs) => {
1261
+ if (host.saveShortcuts) host.saveShortcuts(docs);
1262
+ else saveShellPrefs({ favorite_documents: docs });
1263
+ }, [host, saveShellPrefs]);
1264
+ const saveFolders = useCallback((f) => {
1265
+ if (host.saveFolders) host.saveFolders(f);
1266
+ else saveShellPrefs({ desktop_folders: f });
1267
+ }, [host, saveShellPrefs]);
1268
+ useCallback((v) => {
1269
+ if (host.saveSnap) host.saveSnap(v);
1270
+ else saveShellPrefs({ desktop_snap: v });
1271
+ }, [host, saveShellPrefs]);
1272
+ const getDefaultPos = (idx) => {
1273
+ const col = Math.floor(idx / 8);
1274
+ const row = idx % 8;
1275
+ return { right: 20 + col * GRID, top: 20 + row * GRID };
1276
+ };
1277
+ const getItemPos = (item, idx) => {
1278
+ if (item.x != null && item.y != null) return { right: item.x, top: item.y };
1279
+ return getDefaultPos(idx);
1280
+ };
1281
+ const getFolderPos = (folder, idx) => {
1282
+ if (folder.x != null && folder.y != null) return { right: folder.x, top: folder.y };
1283
+ return getDefaultPos(favDocs.filter((d) => !d.folderId).length + idx);
1284
+ };
1285
+ const desktopItems = favDocs.filter((d) => !d.folderId);
1286
+ const folderItems = (folderId) => favDocs.filter((d) => d.folderId === folderId);
1287
+ const [localPositions, setLocalPositions] = useState({});
1288
+ const dragEntriesRef = useRef([]);
1289
+ const startDrag = (type, idx, e) => {
1290
+ if (e.button !== 0) return;
1291
+ const primaryKey = `${type}-${idx}`;
1292
+ const draggingMulti = selected.has(primaryKey) && selected.size > 1;
1293
+ const keys = draggingMulti ? Array.from(selected) : [primaryKey];
1294
+ const entries = [];
1295
+ for (const key of keys) {
1296
+ if (key.startsWith("item-")) {
1297
+ const i = parseInt(key.slice(5), 10);
1298
+ const itm = desktopItems[i];
1299
+ if (!itm) continue;
1300
+ const pos = getItemPos(itm, i);
1301
+ const el = document.querySelector(`[data-desktop-icon="${key}"]`);
1302
+ entries.push({ key, type: "item", idx: i, origX: pos.right, origY: pos.top, el });
1303
+ } else if (key.startsWith("folder-")) {
1304
+ const i = parseInt(key.slice(7), 10);
1305
+ const f = folders[i];
1306
+ if (!f) continue;
1307
+ const pos = getFolderPos(f, i);
1308
+ const el = document.querySelector(`[data-desktop-icon="${key}"]`);
1309
+ entries.push({ key, type: "folder", idx: i, origX: pos.right, origY: pos.top, el });
1310
+ }
1311
+ }
1312
+ dragEntriesRef.current = entries;
1313
+ const primaryEntry = entries.find((e2) => e2.key === primaryKey) ?? entries[0];
1314
+ if (!primaryEntry) return;
1315
+ setDragging({ type, idx, startX: e.clientX, startY: e.clientY, origX: primaryEntry.origX, origY: primaryEntry.origY });
1316
+ e.preventDefault();
1317
+ };
1318
+ useEffect(() => {
1319
+ if (!dragging) return;
1320
+ const entries = dragEntriesRef.current;
1321
+ const isSingleItemDrag = entries.length === 1 && entries[0].type === "item";
1322
+ const move = (e) => {
1323
+ const dx = e.clientX - dragging.startX;
1324
+ const dy = e.clientY - dragging.startY;
1325
+ for (const entry of entries) {
1326
+ if (!entry.el) continue;
1327
+ entry.el.style.right = `${entry.origX - dx}px`;
1328
+ entry.el.style.top = `${entry.origY + dy}px`;
1329
+ entry.el.style.left = "auto";
1330
+ entry.el.style.zIndex = "100";
1331
+ entry.el.style.opacity = "0.7";
1332
+ }
1333
+ if (isSingleItemDrag) {
1334
+ const elsBelow = document.elementsFromPoint(e.clientX, e.clientY);
1335
+ let nextHover = null;
1336
+ for (const el of elsBelow) {
1337
+ const fk = el.closest?.('[data-desktop-icon^="folder-"]');
1338
+ if (fk) {
1339
+ const key = fk.getAttribute("data-desktop-icon");
1340
+ if (key) nextHover = parseInt(key.slice(7), 10);
1341
+ break;
1342
+ }
1343
+ }
1344
+ hoverFolderIdxRef.current = nextHover;
1345
+ setHoverFolderIdx((prev) => prev === nextHover ? prev : nextHover);
1346
+ }
1347
+ };
1348
+ const up = (e) => {
1349
+ const dx = e.clientX - dragging.startX;
1350
+ const dy = e.clientY - dragging.startY;
1351
+ for (const entry of entries) {
1352
+ if (!entry.el) continue;
1353
+ entry.el.style.zIndex = "";
1354
+ entry.el.style.opacity = "";
1355
+ }
1356
+ const liveHoverIdx = hoverFolderIdxRef.current;
1357
+ const hoveredFolder = liveHoverIdx != null ? folders[liveHoverIdx] : null;
1358
+ hoverFolderIdxRef.current = null;
1359
+ setHoverFolderIdx(null);
1360
+ const computedPositions = entries.map((entry) => {
1361
+ let finalRight = entry.origX - dx;
1362
+ let finalTop = Math.max(0, entry.origY + dy);
1363
+ if (snapEnabled) {
1364
+ const s = snapToGrid(finalRight, finalTop);
1365
+ finalRight = s.x;
1366
+ finalTop = s.y;
1367
+ }
1368
+ finalRight = Math.max(0, finalRight);
1369
+ return { entry, finalRight, finalTop };
1370
+ });
1371
+ const itemMoves = computedPositions.filter((p) => p.entry.type === "item");
1372
+ if (itemMoves.length > 0) {
1373
+ const updated = [...favDocs];
1374
+ const positionsPatch = {};
1375
+ const singleItem = itemMoves.length === 1 && entries.length === 1 ? itemMoves[0] : null;
1376
+ const droppedOnFolder = singleItem ? hoveredFolder ?? folders.find((f, fi) => {
1377
+ const fp = getFolderPos(f, fi);
1378
+ return Math.abs(singleItem.finalRight - fp.right) < 40 && Math.abs(singleItem.finalTop - fp.top) < 40;
1379
+ }) : void 0;
1380
+ if (droppedOnFolder && singleItem?.entry.el) {
1381
+ const itemEl = singleItem.entry.el;
1382
+ const folderIdx = folders.indexOf(droppedOnFolder);
1383
+ const folderEl = document.querySelector(`[data-desktop-icon="folder-${folderIdx}"]`);
1384
+ if (folderEl) {
1385
+ const ir = itemEl.getBoundingClientRect();
1386
+ const fr = folderEl.getBoundingClientRect();
1387
+ const dx2 = fr.left + fr.width / 2 - (ir.left + ir.width / 2);
1388
+ const dy2 = fr.top + fr.height / 2 - (ir.top + ir.height / 2);
1389
+ itemEl.style.transition = "transform 220ms ease-out, opacity 220ms ease-out";
1390
+ itemEl.style.transform = `translate(${dx2}px, ${dy2}px) scale(0.2)`;
1391
+ itemEl.style.opacity = "0";
1392
+ }
1393
+ folderEl?.animate(
1394
+ [{ transform: "scale(1)" }, { transform: "scale(1.15)" }, { transform: "scale(1)" }],
1395
+ { duration: 280, easing: "ease-out" }
1396
+ );
1397
+ }
1398
+ for (const move2 of itemMoves) {
1399
+ const desktopIdx = favDocs.indexOf(desktopItems[move2.entry.idx]);
1400
+ if (desktopIdx === -1) continue;
1401
+ if (droppedOnFolder) {
1402
+ updated[desktopIdx] = { ...updated[desktopIdx], folderId: droppedOnFolder.id, x: void 0, y: void 0 };
1403
+ } else {
1404
+ updated[desktopIdx] = { ...updated[desktopIdx], x: move2.finalRight, y: move2.finalTop, folderId: void 0 };
1405
+ positionsPatch[`item-${desktopIdx}`] = { right: move2.finalRight, top: move2.finalTop };
1406
+ }
1407
+ }
1408
+ const commit = () => {
1409
+ saveDocs(updated);
1410
+ if (Object.keys(positionsPatch).length > 0) {
1411
+ setLocalPositions((prev) => ({ ...prev, ...positionsPatch }));
1412
+ }
1413
+ };
1414
+ if (droppedOnFolder) setTimeout(commit, 220);
1415
+ else commit();
1416
+ }
1417
+ const folderMoves = computedPositions.filter((p) => p.entry.type === "folder");
1418
+ if (folderMoves.length > 0) {
1419
+ const updated = [...folders];
1420
+ const positionsPatch = {};
1421
+ for (const move2 of folderMoves) {
1422
+ updated[move2.entry.idx] = { ...updated[move2.entry.idx], x: move2.finalRight, y: move2.finalTop };
1423
+ positionsPatch[`folder-${move2.entry.idx}`] = { right: move2.finalRight, top: move2.finalTop };
1424
+ }
1425
+ saveFolders(updated);
1426
+ setLocalPositions((prev) => ({ ...prev, ...positionsPatch }));
1427
+ }
1428
+ setDragging(null);
1429
+ dragEntriesRef.current = [];
1430
+ };
1431
+ window.addEventListener("pointermove", move);
1432
+ window.addEventListener("pointerup", up);
1433
+ return () => {
1434
+ window.removeEventListener("pointermove", move);
1435
+ window.removeEventListener("pointerup", up);
1436
+ };
1437
+ }, [dragging, snapEnabled, favDocs, folders, desktopItems]);
1438
+ const favDocsKey = JSON.stringify(favDocs.map((d) => `${d.entityId}:${d.x ?? ""}:${d.y ?? ""}:${d.folderId ?? ""}`));
1439
+ const foldersKey = JSON.stringify(folders.map((f) => `${f.id}:${f.x ?? ""}:${f.y ?? ""}`));
1440
+ useEffect(() => {
1441
+ setLocalPositions({});
1442
+ }, [favDocsKey, foldersKey]);
1443
+ const didRubberBandDragRef = useRef(false);
1444
+ const startRubberBand = (e) => {
1445
+ if (e.button !== 0 || e.target !== containerRef.current) return;
1446
+ const rect = containerRef.current.getBoundingClientRect();
1447
+ const x = e.clientX - rect.left;
1448
+ const y = e.clientY - rect.top;
1449
+ setRubberBand({ startX: x, startY: y, endX: x, endY: y });
1450
+ didRubberBandDragRef.current = false;
1451
+ setSelected(/* @__PURE__ */ new Set());
1452
+ };
1453
+ useEffect(() => {
1454
+ if (!rubberBand) return;
1455
+ const move = (e) => {
1456
+ const rect = containerRef.current?.getBoundingClientRect();
1457
+ if (!rect) return;
1458
+ const x = e.clientX - rect.left;
1459
+ const y = e.clientY - rect.top;
1460
+ const dx = x - rubberBand.startX;
1461
+ const dy = y - rubberBand.startY;
1462
+ if (dx * dx + dy * dy > 16) didRubberBandDragRef.current = true;
1463
+ setRubberBand((prev) => prev ? { ...prev, endX: x, endY: y } : null);
1464
+ };
1465
+ const up = () => {
1466
+ if (rubberBand) {
1467
+ const minX = Math.min(rubberBand.startX, rubberBand.endX);
1468
+ const maxX = Math.max(rubberBand.startX, rubberBand.endX);
1469
+ const minY = Math.min(rubberBand.startY, rubberBand.endY);
1470
+ const maxY = Math.max(rubberBand.startY, rubberBand.endY);
1471
+ const sel = /* @__PURE__ */ new Set();
1472
+ const cw = containerRef.current?.clientWidth || 800;
1473
+ desktopItems.forEach((item, i) => {
1474
+ const pos = getItemPos(item, i);
1475
+ const leftX = cw - pos.right - 80;
1476
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1477
+ sel.add(`item-${i}`);
1478
+ }
1479
+ });
1480
+ folders.forEach((f, i) => {
1481
+ const pos = getFolderPos(f, i);
1482
+ const leftX = cw - pos.right - 80;
1483
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1484
+ sel.add(`folder-${i}`);
1485
+ }
1486
+ });
1487
+ setSelected(sel);
1488
+ }
1489
+ setRubberBand(null);
1490
+ };
1491
+ window.addEventListener("pointermove", move);
1492
+ window.addEventListener("pointerup", up);
1493
+ return () => {
1494
+ window.removeEventListener("pointermove", move);
1495
+ window.removeEventListener("pointerup", up);
1496
+ };
1497
+ }, [rubberBand]);
1498
+ const handleDesktopContextMenu = (e) => {
1499
+ e.preventDefault();
1500
+ setContextMenu({ x: e.clientX, y: e.clientY });
1501
+ };
1502
+ const handleItemContextMenu = (e, itemIdx) => {
1503
+ e.preventDefault();
1504
+ e.stopPropagation();
1505
+ setContextMenu({ x: e.clientX, y: e.clientY, itemIdx });
1506
+ };
1507
+ const handleFolderContextMenu = (e, folderIdx) => {
1508
+ e.preventDefault();
1509
+ e.stopPropagation();
1510
+ setContextMenu({ x: e.clientX, y: e.clientY, folderIdx });
1511
+ };
1512
+ useEffect(() => {
1513
+ if (!contextMenu) return;
1514
+ const handler = () => setContextMenu(null);
1515
+ window.addEventListener("click", handler);
1516
+ return () => window.removeEventListener("click", handler);
1517
+ }, [contextMenu]);
1518
+ const createFolder = () => {
1519
+ const rect = containerRef.current?.getBoundingClientRect();
1520
+ const containerW = rect?.width ?? 0;
1521
+ const cursorLeft = contextMenu ? contextMenu.x - (rect?.left ?? 0) : containerW - 100;
1522
+ const cursorTop = contextMenu ? contextMenu.y - (rect?.top ?? 0) : 100;
1523
+ const x = Math.max(0, containerW - cursorLeft - 40);
1524
+ const y = Math.max(0, cursorTop - 20);
1525
+ const id = `folder-${Date.now()}`;
1526
+ saveFolders([...folders, { id, name: "New Folder", x, y }]);
1527
+ setContextMenu(null);
1528
+ setRenamingFolder(id);
1529
+ setRenameValue("New Folder");
1530
+ };
1531
+ const doSnapAll = () => {
1532
+ const rect = containerRef.current?.getBoundingClientRect();
1533
+ const h = rect?.height || 600;
1534
+ const colCount = Math.max(1, Math.floor(h / GRID));
1535
+ let slot = 0;
1536
+ const getSlotPos = () => {
1537
+ const col = Math.floor(slot / colCount);
1538
+ const row = slot % colCount;
1539
+ slot++;
1540
+ return { right: 20 + col * GRID, top: 10 + row * GRID };
1541
+ };
1542
+ const updatedF = folders.map((f) => {
1543
+ const pos = getSlotPos();
1544
+ return { ...f, x: pos.right, y: pos.top };
1545
+ });
1546
+ saveFolders(updatedF);
1547
+ const updated = favDocs.map((d) => {
1548
+ if (d.folderId) return d;
1549
+ const pos = getSlotPos();
1550
+ return { ...d, x: pos.right, y: pos.top };
1551
+ });
1552
+ saveDocs(updated);
1553
+ setContextMenu(null);
1554
+ };
1555
+ const removeItem = (idx) => {
1556
+ const desktopIdx = favDocs.indexOf(desktopItems[idx]);
1557
+ saveDocs(favDocs.filter((_, i) => i !== desktopIdx));
1558
+ setContextMenu(null);
1559
+ };
1560
+ const removeFolder = (idx) => {
1561
+ const folder = folders[idx];
1562
+ const updated = favDocs.map((d) => d.folderId === folder.id ? { ...d, folderId: void 0 } : d);
1563
+ saveDocs(updated);
1564
+ saveFolders(folders.filter((_, i) => i !== idx));
1565
+ setContextMenu(null);
1566
+ };
1567
+ const renameFolder = (id, name) => {
1568
+ saveFolders(folders.map((f) => f.id === id ? { ...f, name } : f));
1569
+ setRenamingFolder(null);
1570
+ };
1571
+ const saveNotes = useCallback((updated) => {
1572
+ if (host.saveNotes) host.saveNotes(updated);
1573
+ else saveShellPrefs({ notepad_notes: updated });
1574
+ }, [host, saveShellPrefs]);
1575
+ const createStickyNote = () => {
1576
+ const rect = containerRef.current?.getBoundingClientRect();
1577
+ const x = contextMenu ? contextMenu.x - (rect?.left || 0) : 100;
1578
+ const y = contextMenu ? contextMenu.y - (rect?.top || 0) : 100;
1579
+ const n = {
1580
+ id: `note-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
1581
+ title: "",
1582
+ content: "",
1583
+ color: "yellow",
1584
+ sticky: true,
1585
+ sticky_x: x,
1586
+ sticky_y: y,
1587
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1588
+ };
1589
+ saveNotes([n, ...allNotes]);
1590
+ setEditingStickyId(n.id);
1591
+ setContextMenu(null);
1592
+ };
1593
+ const updateStickyContent = (id, content) => {
1594
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, content, updated_at: (/* @__PURE__ */ new Date()).toISOString() } : n));
1595
+ };
1596
+ const removeStickyFromDesktop = (id) => {
1597
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, sticky: false } : n));
1598
+ };
1599
+ const deleteStickyNote = (id) => {
1600
+ saveNotes(allNotes.filter((n) => n.id !== id));
1601
+ };
1602
+ const cycleStickyColor = (id) => {
1603
+ const STICKY_COLORS = ["yellow", "blue", "green", "pink", "purple", "orange"];
1604
+ const note = allNotes.find((n) => n.id === id);
1605
+ if (!note) return;
1606
+ const idx = STICKY_COLORS.indexOf(note.color);
1607
+ const next = STICKY_COLORS[(idx + 1) % STICKY_COLORS.length];
1608
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, color: next } : n));
1609
+ };
1610
+ const toggleStickyOnTop = (id) => {
1611
+ saveNotes(allNotes.map((n) => n.id === id ? { ...n, sticky_on_top: !n.sticky_on_top } : n));
1612
+ };
1613
+ useEffect(() => {
1614
+ if (!stickyDrag) return;
1615
+ const move = (e) => {
1616
+ const el = document.querySelector(`[data-sticky-id="${stickyDrag.id}"]`);
1617
+ if (el) {
1618
+ el.style.left = `${stickyDrag.origX + e.clientX - stickyDrag.startX}px`;
1619
+ el.style.right = "auto";
1620
+ el.style.top = `${stickyDrag.origY + e.clientY - stickyDrag.startY}px`;
1621
+ }
1622
+ };
1623
+ const up = (e) => {
1624
+ const finalX = stickyDrag.origX + e.clientX - stickyDrag.startX;
1625
+ const finalY = Math.max(0, stickyDrag.origY + e.clientY - stickyDrag.startY);
1626
+ const el = document.querySelector(`[data-sticky-id="${stickyDrag.id}"]`);
1627
+ const noteW = el?.offsetWidth ?? 192;
1628
+ const centerX = finalX + noteW / 2;
1629
+ const anchor = centerX > window.innerWidth / 2 ? "right" : "left";
1630
+ const xVal = anchor === "right" ? window.innerWidth - finalX - noteW : finalX;
1631
+ saveNotes(allNotes.map((n) => n.id === stickyDrag.id ? { ...n, sticky_x: xVal, sticky_y: finalY, sticky_anchor: anchor } : n));
1632
+ if (el) {
1633
+ el.style.left = "";
1634
+ el.style.right = "";
1635
+ }
1636
+ setStickyDrag(null);
1637
+ };
1638
+ window.addEventListener("pointermove", move);
1639
+ window.addEventListener("pointerup", up);
1640
+ return () => {
1641
+ window.removeEventListener("pointermove", move);
1642
+ window.removeEventListener("pointerup", up);
1643
+ };
1644
+ }, [stickyDrag, allNotes]);
1645
+ useEffect(() => {
1646
+ if (!stickyResize) return;
1647
+ const move = (e) => {
1648
+ const el = document.querySelector(`[data-sticky-id="${stickyResize.id}"]`);
1649
+ if (el) {
1650
+ el.style.width = `${Math.max(140, stickyResize.origW + e.clientX - stickyResize.startX)}px`;
1651
+ el.style.height = `${Math.max(100, stickyResize.origH + e.clientY - stickyResize.startY)}px`;
1652
+ }
1653
+ };
1654
+ const up = (e) => {
1655
+ const finalW = Math.max(140, stickyResize.origW + e.clientX - stickyResize.startX);
1656
+ const finalH = Math.max(100, stickyResize.origH + e.clientY - stickyResize.startY);
1657
+ saveNotes(allNotes.map((n) => n.id === stickyResize.id ? { ...n, sticky_w: finalW, sticky_h: finalH } : n));
1658
+ setStickyResize(null);
1659
+ };
1660
+ window.addEventListener("pointermove", move);
1661
+ window.addEventListener("pointerup", up);
1662
+ return () => {
1663
+ window.removeEventListener("pointermove", move);
1664
+ window.removeEventListener("pointerup", up);
1665
+ };
1666
+ }, [stickyResize, allNotes]);
1667
+ const STICKY_BG = {
1668
+ yellow: "bg-yellow-100 border-yellow-300",
1669
+ blue: "bg-blue-100 border-blue-300",
1670
+ green: "bg-green-100 border-green-300",
1671
+ pink: "bg-pink-100 border-pink-300",
1672
+ purple: "bg-purple-100 border-purple-300",
1673
+ orange: "bg-orange-100 border-orange-300"
1674
+ };
1675
+ const renderIcon = (entityType, label, isSelected, entityId) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1676
+ 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: [
1677
+ /* @__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" }),
1678
+ /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1679
+ ] }) }) : 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: (() => {
1680
+ const icon = navIcons[label] || (entityId ? navIcons[entityId] : void 0);
1681
+ if (icon && isValidElement(icon)) {
1682
+ return cloneElement(icon, { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]" });
1683
+ }
1684
+ 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" }) });
1685
+ })() }) : /* @__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: [
1686
+ /* @__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: [
1687
+ /* @__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" }),
1688
+ /* @__PURE__ */ jsx("path", { d: "M26 0l10 10H30a4 4 0 01-4-4V0z", fill: "currentColor", fillOpacity: "0.2" }),
1689
+ /* @__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" })
1690
+ ] }),
1691
+ /* @__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() })
1692
+ ] }),
1693
+ /* @__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 })
1694
+ ] });
1695
+ const menuStyle = (x, y) => ({
1696
+ ...x + 180 > window.innerWidth ? { right: window.innerWidth - x } : { left: x },
1697
+ ...y + 250 > window.innerHeight ? { bottom: window.innerHeight - y } : { top: y }
1698
+ });
1699
+ return /* @__PURE__ */ jsxs(
1700
+ "div",
1701
+ {
1702
+ ref: containerRef,
1703
+ className: "flex-1 relative overflow-hidden",
1704
+ onPointerDown: startRubberBand,
1705
+ onContextMenu: handleDesktopContextMenu,
1706
+ onClick: () => {
1707
+ if (didRubberBandDragRef.current) {
1708
+ didRubberBandDragRef.current = false;
1709
+ return;
1710
+ }
1711
+ setSelected(/* @__PURE__ */ new Set());
1712
+ setContextMenu(null);
1713
+ },
1714
+ children: [
1715
+ (() => {
1716
+ const cs = typeof document !== "undefined" ? getComputedStyle(document.documentElement) : null;
1717
+ const tbH = parseInt(cs?.getPropertyValue("--taskbar-height") || "0") || 0;
1718
+ const tbW = parseInt(cs?.getPropertyValue("--taskbar-width") || "0") || 0;
1719
+ const tbPos = (cs?.getPropertyValue("--taskbar-position") || "bottom").trim();
1720
+ const defaultRight = 20 + (tbPos === "right" ? tbW : 0);
1721
+ const defaultBottom = 20 + (tbPos === "bottom" ? tbH : 0);
1722
+ const trashPos = prefs.desktop_trash_position;
1723
+ const right = trashPos?.right ?? defaultRight;
1724
+ const bottom = trashPos?.bottom ?? defaultBottom;
1725
+ const startTrashDrag = (e) => {
1726
+ if (e.button !== 0) return;
1727
+ e.preventDefault();
1728
+ e.stopPropagation();
1729
+ const el = e.currentTarget;
1730
+ const startX = e.clientX, startY = e.clientY;
1731
+ let moved = false;
1732
+ const move = (ev) => {
1733
+ const dx = ev.clientX - startX;
1734
+ const dy = ev.clientY - startY;
1735
+ if (!moved && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) moved = true;
1736
+ if (!moved) return;
1737
+ el.style.right = `${right - dx}px`;
1738
+ el.style.bottom = `${bottom - dy}px`;
1739
+ el.style.opacity = "0.7";
1740
+ el.style.zIndex = "100";
1741
+ };
1742
+ const up = (ev) => {
1743
+ window.removeEventListener("pointermove", move);
1744
+ window.removeEventListener("pointerup", up);
1745
+ el.style.opacity = "";
1746
+ el.style.zIndex = "";
1747
+ if (!moved) return;
1748
+ const newRight = Math.max(0, right - (ev.clientX - startX));
1749
+ const newBottom = Math.max(0, bottom - (ev.clientY - startY));
1750
+ saveShellPrefs({ desktop_trash_position: { right: newRight, bottom: newBottom } });
1751
+ };
1752
+ window.addEventListener("pointermove", move);
1753
+ window.addEventListener("pointerup", up);
1754
+ };
1755
+ return /* @__PURE__ */ jsx(
1756
+ "div",
1757
+ {
1758
+ "data-desktop-icon": "trash",
1759
+ style: { position: "absolute", right, bottom, zIndex: 1 },
1760
+ onPointerDown: startTrashDrag,
1761
+ onClick: (e) => e.stopPropagation(),
1762
+ onContextMenu: (e) => e.preventDefault(),
1763
+ onDoubleClick: (e) => {
1764
+ e.stopPropagation();
1765
+ window.__REACT_OS_SHELL_FILES_VIEW__ = "trash";
1766
+ window.dispatchEvent(new CustomEvent("react-os-shell:files-show-trash"));
1767
+ openPage("/files");
1768
+ },
1769
+ className: "cursor-default select-none",
1770
+ title: "Trash \u2014 double-click to open, drag to move",
1771
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1772
+ /* @__PURE__ */ jsx("div", { className: "w-12 h-12 flex items-center justify-center", children: /* @__PURE__ */ jsx("svg", { className: "h-12 w-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
1773
+ "path",
1774
+ {
1775
+ fill: "#c0c4cc",
1776
+ stroke: "#5f6677",
1777
+ strokeWidth: "0.6",
1778
+ strokeLinejoin: "round",
1779
+ fillRule: "evenodd",
1780
+ clipRule: "evenodd",
1781
+ d: "M16.5 4.478v.227a48.816 48.816 0 013.878.512.75.75 0 11-.256 1.478l-.209-.035-1.005 13.07a3 3 0 01-2.991 2.77H8.084a3 3 0 01-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 01-.256-1.478A48.567 48.567 0 017.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 014.368 0c1.603.051 2.816 1.387 2.816 2.951zm-6.136-1.452a51.196 51.196 0 013.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 00-6 0v-.113c0-.794.609-1.428 1.364-1.452zm-.355 5.945a.75.75 0 10-1.5.058l.347 9a.75.75 0 101.499-.058l-.346-9zm5.48.058a.75.75 0 10-1.498-.058l-.347 9a.75.75 0 001.5.058l.346-9z"
1782
+ }
1783
+ ) }) }),
1784
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-center leading-tight text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)]", children: "Trash" })
1785
+ ] })
1786
+ }
1787
+ );
1788
+ })(),
1789
+ desktopItems.map((doc, i) => {
1790
+ const docIdx = favDocs.indexOf(doc);
1791
+ const pos = localPositions[`item-${docIdx}`] || getItemPos(doc, i);
1792
+ const isSelected = selected.has(`item-${i}`);
1793
+ return /* @__PURE__ */ jsx(
1794
+ "div",
1795
+ {
1796
+ "data-desktop-icon": `item-${i}`,
1797
+ style: { position: "absolute", right: pos.right, top: pos.top, zIndex: 1 },
1798
+ onPointerDown: (e) => {
1799
+ e.stopPropagation();
1800
+ startDrag("item", i, e);
1801
+ },
1802
+ onClick: (e) => {
1803
+ e.stopPropagation();
1804
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
1805
+ setSelected((prev) => {
1806
+ const next = new Set(prev);
1807
+ next.has(`item-${i}`) ? next.delete(`item-${i}`) : next.add(`item-${i}`);
1808
+ return next;
1809
+ });
1810
+ } else if (!selected.has(`item-${i}`)) {
1811
+ setSelected(/* @__PURE__ */ new Set([`item-${i}`]));
1812
+ }
1813
+ },
1814
+ onContextMenu: (e) => handleItemContextMenu(e, i),
1815
+ onDoubleClick: (e) => {
1816
+ e.stopPropagation();
1817
+ doc.entityType === "page" ? openPage(doc.entityId) : openEntity(doc.entityType, doc.entityId, null, doc.label);
1818
+ },
1819
+ className: "cursor-default select-none",
1820
+ children: renderIcon(doc.entityType, doc.label, isSelected, doc.entityId)
1821
+ },
1822
+ `item-${doc.entityType}-${doc.entityId}-${i}`
1823
+ );
1824
+ }),
1825
+ folders.map((folder, i) => {
1826
+ const pos = localPositions[`folder-${i}`] || getFolderPos(folder, i);
1827
+ const isSelected = selected.has(`folder-${i}`);
1828
+ const isHovered = hoverFolderIdx === i;
1829
+ const itemCount = folderItems(folder.id).length;
1830
+ return /* @__PURE__ */ jsx(
1831
+ "div",
1832
+ {
1833
+ "data-desktop-icon": `folder-${i}`,
1834
+ style: {
1835
+ position: "absolute",
1836
+ right: pos.right,
1837
+ top: pos.top,
1838
+ zIndex: 1,
1839
+ transform: isHovered ? "scale(1.15)" : "scale(1)",
1840
+ transition: "transform 180ms ease-out"
1841
+ },
1842
+ onPointerDown: (e) => {
1843
+ e.stopPropagation();
1844
+ startDrag("folder", i, e);
1845
+ },
1846
+ onClick: (e) => {
1847
+ e.stopPropagation();
1848
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
1849
+ setSelected((prev) => {
1850
+ const next = new Set(prev);
1851
+ next.has(`folder-${i}`) ? next.delete(`folder-${i}`) : next.add(`folder-${i}`);
1852
+ return next;
1853
+ });
1854
+ } else if (!selected.has(`folder-${i}`)) {
1855
+ setSelected(/* @__PURE__ */ new Set([`folder-${i}`]));
1856
+ }
1857
+ },
1858
+ onContextMenu: (e) => handleFolderContextMenu(e, i),
1859
+ onDoubleClick: (e) => {
1860
+ e.stopPropagation();
1861
+ setOpenFolder(folder.id);
1862
+ },
1863
+ className: "cursor-default select-none",
1864
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1865
+ /* @__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" : ""} ${isHovered ? "rounded-lg ring-4 ring-amber-400 shadow-[0_0_20px_rgba(245,158,11,0.6)]" : ""}`, 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: [
1866
+ /* @__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" }),
1867
+ /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1868
+ ] }) }),
1869
+ renamingFolder === folder.id ? /* @__PURE__ */ jsx(
1870
+ "input",
1871
+ {
1872
+ autoFocus: true,
1873
+ value: renameValue,
1874
+ onChange: (e) => setRenameValue(e.target.value),
1875
+ onBlur: () => renameFolder(folder.id, renameValue),
1876
+ onKeyDown: (e) => {
1877
+ if (e.key === "Enter") renameFolder(folder.id, renameValue);
1878
+ if (e.key === "Escape") setRenamingFolder(null);
1879
+ },
1880
+ className: "text-[10px] w-full text-center bg-white/80 rounded px-1 outline-none",
1881
+ onClick: (e) => e.stopPropagation()
1882
+ }
1883
+ ) : /* @__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: [
1884
+ folder.name,
1885
+ itemCount > 0 ? ` (${itemCount})` : ""
1886
+ ] })
1887
+ ] })
1888
+ },
1889
+ `folder-${folder.id}`
1890
+ );
1891
+ }),
1892
+ stickyNotes.map((note) => {
1893
+ const xVal = note.sticky_x ?? 100;
1894
+ const y = note.sticky_y ?? 100;
1895
+ const w = note.sticky_w ?? 192;
1896
+ const h = note.sticky_h ?? 148;
1897
+ const anchor = note.sticky_anchor || "left";
1898
+ const bg = STICKY_BG[note.color] || STICKY_BG.yellow;
1899
+ const isEditing = editingStickyId === note.id;
1900
+ return /* @__PURE__ */ jsxs(
1901
+ "div",
1902
+ {
1903
+ "data-sticky-id": note.id,
1904
+ style: { position: "absolute", ...anchor === "right" ? { right: xVal } : { left: xVal }, top: y, width: w, height: h, zIndex: note.sticky_on_top ? 200 : isEditing ? 50 : 10 },
1905
+ onClick: (e) => e.stopPropagation(),
1906
+ className: `rounded-lg shadow-lg border ${bg} select-none flex flex-col`,
1907
+ children: [
1908
+ /* @__PURE__ */ jsxs(
1909
+ "div",
1910
+ {
1911
+ className: "flex items-center justify-between px-2 py-1 cursor-move border-b border-black/5",
1912
+ onPointerDown: (e) => {
1913
+ if (e.button !== 0) return;
1914
+ e.preventDefault();
1915
+ e.stopPropagation();
1916
+ const el = e.target.closest("[data-sticky-id]");
1917
+ const actualLeft = el ? el.getBoundingClientRect().left - (containerRef.current?.getBoundingClientRect().left ?? 0) : anchor === "right" ? window.innerWidth - xVal - w : xVal;
1918
+ setStickyDrag({ id: note.id, startX: e.clientX, startY: e.clientY, origX: actualLeft, origY: y });
1919
+ },
1920
+ children: [
1921
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
1922
+ "button",
1923
+ {
1924
+ onPointerDown: (e) => e.stopPropagation(),
1925
+ onClick: (e) => {
1926
+ e.stopPropagation();
1927
+ cycleStickyColor(note.id);
1928
+ },
1929
+ title: "Change color",
1930
+ className: "w-3 h-3 rounded-full bg-black/10 hover:bg-black/20 transition-colors"
1931
+ }
1932
+ ) }),
1933
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1934
+ /* @__PURE__ */ jsx(
1935
+ "button",
1936
+ {
1937
+ onPointerDown: (e) => e.stopPropagation(),
1938
+ onClick: (e) => {
1939
+ e.stopPropagation();
1940
+ toggleStickyOnTop(note.id);
1941
+ },
1942
+ title: note.sticky_on_top ? "Remove from top" : "Always on top",
1943
+ className: `p-0.5 rounded transition-colors ${note.sticky_on_top ? "text-blue-500" : "text-black/20 hover:text-black/50"}`,
1944
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: [
1945
+ /* @__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" }),
1946
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12.75 12l3 3 3-3" })
1947
+ ] })
1948
+ }
1949
+ ),
1950
+ /* @__PURE__ */ jsx(
1951
+ "button",
1952
+ {
1953
+ onPointerDown: (e) => e.stopPropagation(),
1954
+ onClick: (e) => {
1955
+ e.stopPropagation();
1956
+ removeStickyFromDesktop(note.id);
1957
+ },
1958
+ title: "Unpin from desktop",
1959
+ className: "p-0.5 rounded text-black/20 hover:text-black/50 transition-colors",
1960
+ 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" }) })
1961
+ }
1962
+ ),
1963
+ /* @__PURE__ */ jsx(
1964
+ "button",
1965
+ {
1966
+ onPointerDown: (e) => e.stopPropagation(),
1967
+ onClick: (e) => {
1968
+ e.stopPropagation();
1969
+ deleteStickyNote(note.id);
1970
+ },
1971
+ title: "Delete note",
1972
+ className: "p-0.5 rounded text-black/20 hover:text-red-500 transition-colors",
1973
+ 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" }) })
1974
+ }
1975
+ )
1976
+ ] })
1977
+ ]
1978
+ }
1979
+ ),
1980
+ isEditing ? /* @__PURE__ */ jsx(
1981
+ "textarea",
1982
+ {
1983
+ autoFocus: true,
1984
+ defaultValue: note.content,
1985
+ onBlur: (e) => {
1986
+ updateStickyContent(note.id, e.target.value);
1987
+ setEditingStickyId(null);
1988
+ },
1989
+ onKeyDown: (e) => {
1990
+ if (e.key === "Escape") {
1991
+ updateStickyContent(note.id, e.target.value);
1992
+ setEditingStickyId(null);
1993
+ }
1994
+ },
1995
+ className: "flex-1 w-full p-2 text-xs leading-relaxed bg-transparent outline-none resize-none placeholder:text-black/30",
1996
+ placeholder: "Write something..."
1997
+ }
1998
+ ) : /* @__PURE__ */ jsx("div", { className: "flex-1 p-2 cursor-text overflow-hidden", onDoubleClick: (e) => {
1999
+ e.stopPropagation();
2000
+ setEditingStickyId(note.id);
2001
+ }, 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..." }) }) }),
2002
+ /* @__PURE__ */ jsx(
2003
+ "div",
2004
+ {
2005
+ className: "absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize",
2006
+ onPointerDown: (e) => {
2007
+ if (e.button !== 0) return;
2008
+ e.preventDefault();
2009
+ e.stopPropagation();
2010
+ const el = e.target.closest("[data-sticky-id]");
2011
+ setStickyResize({ id: note.id, startX: e.clientX, startY: e.clientY, origW: el?.offsetWidth ?? w, origH: el?.offsetHeight ?? h });
2012
+ },
2013
+ 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: [
2014
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "1.5" }),
2015
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "10", r: "1.5" }),
2016
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "6", r: "1.5" })
2017
+ ] })
2018
+ }
2019
+ )
2020
+ ]
2021
+ },
2022
+ `sticky-${note.id}`
2023
+ );
2024
+ }),
2025
+ rubberBand && /* @__PURE__ */ jsx("div", { className: "absolute border border-blue-400 bg-blue-400/10 pointer-events-none", style: {
2026
+ left: Math.min(rubberBand.startX, rubberBand.endX),
2027
+ top: Math.min(rubberBand.startY, rubberBand.endY),
2028
+ width: Math.abs(rubberBand.endX - rubberBand.startX),
2029
+ height: Math.abs(rubberBand.endY - rubberBand.startY)
2030
+ } }),
2031
+ contextMenu && contextMenu.itemIdx == null && contextMenu.folderIdx == null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), children: [
2032
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: createStickyNote, children: [
2033
+ /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4 text-yellow-500", fill: "currentColor", viewBox: "0 0 24 24", children: [
2034
+ /* @__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" }),
2035
+ /* @__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" })
2036
+ ] }),
2037
+ "New Sticky Note"
2038
+ ] }),
2039
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: createFolder, children: [
2040
+ /* @__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" }) }),
2041
+ "Create Folder"
2042
+ ] }),
2043
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: doSnapAll, children: [
2044
+ /* @__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" }) }),
2045
+ "Snap to Grid"
2046
+ ] }),
2047
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
2048
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
2049
+ setContextMenu(null);
2050
+ openPage("/settings/customization");
2051
+ }, children: [
2052
+ /* @__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" }) }),
2053
+ "Customization"
2054
+ ] }),
2055
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
2056
+ setContextMenu(null);
2057
+ openPage("/settings/favorites");
2058
+ }, children: [
2059
+ /* @__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" }) }),
2060
+ "Favorites"
2061
+ ] }),
2062
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
2063
+ setContextMenu(null);
2064
+ setAboutOpen(true);
2065
+ }, children: [
2066
+ /* @__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" }) }),
2067
+ "About ",
2068
+ host.productName ?? "this app"
2069
+ ] }),
2070
+ bugReport && /* @__PURE__ */ jsxs(Fragment, { children: [
2071
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
2072
+ /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
2073
+ setContextMenu(null);
2074
+ reportBug(bugReport.submit);
2075
+ }, children: [
2076
+ /* @__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" }) }),
2077
+ "Suggestion or Bug"
2078
+ ] })
2079
+ ] })
2080
+ ] }),
2081
+ contextMenu && contextMenu.itemIdx != null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), minWidth: 160, children: [
2082
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
2083
+ const d = desktopItems[contextMenu.itemIdx];
2084
+ d.entityType === "page" ? openPage(d.entityId) : openEntity(d.entityType, d.entityId, null, d.label);
2085
+ setContextMenu(null);
2086
+ }, children: "Open" }),
2087
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
2088
+ /* @__PURE__ */ jsx(PopupMenuItem, { danger: true, onClick: () => removeItem(contextMenu.itemIdx), children: "Remove Shortcut" })
2089
+ ] }),
2090
+ contextMenu && contextMenu.folderIdx != null && /* @__PURE__ */ jsxs(PopupMenu, { style: menuStyle(contextMenu.x, contextMenu.y), minWidth: 160, children: [
2091
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
2092
+ setOpenFolder(folders[contextMenu.folderIdx].id);
2093
+ setContextMenu(null);
2094
+ }, children: "Open" }),
2095
+ /* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
2096
+ setRenamingFolder(folders[contextMenu.folderIdx].id);
2097
+ setRenameValue(folders[contextMenu.folderIdx].name);
2098
+ setContextMenu(null);
2099
+ }, children: "Rename" }),
2100
+ /* @__PURE__ */ jsx(PopupMenuDivider, {}),
2101
+ /* @__PURE__ */ jsx(PopupMenuItem, { danger: true, onClick: () => removeFolder(contextMenu.folderIdx), children: "Delete Folder" })
2102
+ ] }),
2103
+ openFolder && (() => {
2104
+ const folder = folders.find((f) => f.id === openFolder);
2105
+ if (!folder) return null;
2106
+ return /* @__PURE__ */ jsx(
2107
+ FolderWindow,
2108
+ {
2109
+ folder,
2110
+ items: folderItems(openFolder),
2111
+ onClose: () => setOpenFolder(null),
2112
+ onOpen: (item) => openEntity(item.entityType, item.entityId, null, item.label),
2113
+ onMoveOut: (toMove) => {
2114
+ const ids = new Set(toMove.map((t) => `${t.entityType}|${t.entityId}`));
2115
+ const updated = favDocs.map(
2116
+ (d) => d.folderId === openFolder && ids.has(`${d.entityType}|${d.entityId}`) ? { ...d, folderId: void 0 } : d
2117
+ );
2118
+ saveDocs(updated);
2119
+ },
2120
+ onReorder: (nextItems) => {
2121
+ const others = favDocs.filter((d) => d.folderId !== openFolder);
2122
+ saveDocs([...others, ...nextItems]);
2123
+ }
2124
+ }
2125
+ );
2126
+ })(),
2127
+ aboutOpen && (() => {
2128
+ const version = host.productVersion ?? APP_VERSION;
2129
+ 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: [
2130
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 pt-4 pb-3 w-full", children: [
2131
+ /* @__PURE__ */ jsx("img", { src: host.productIcon ?? "/favicon.svg", alt: "", className: "h-16 w-16" }),
2132
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2133
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-gray-900 tracking-wide", children: host.productName ?? "react-os-shell" }),
2134
+ host.productTagline && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: host.productTagline }),
2135
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-gray-400 mt-1 font-mono", children: version })
2136
+ ] })
2137
+ ] }),
2138
+ /* @__PURE__ */ jsxs("div", { className: "py-3 border-t border-gray-200 w-full px-4", children: [
2139
+ /* @__PURE__ */ jsx("h3", { className: "text-[10px] font-semibold text-gray-400 uppercase tracking-wide mb-2 text-center", children: "Open Source Licenses" }),
2140
+ /* @__PURE__ */ jsx("div", { className: "max-h-40 overflow-y-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-[11px]", children: [
2141
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-gray-500 border-b border-gray-100", children: [
2142
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Package" }),
2143
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "License" })
2144
+ ] }) }),
2145
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-50", children: [
2146
+ ["React", "MIT", "facebook/react"],
2147
+ ["React DOM", "MIT", "facebook/react"],
2148
+ ["React Router", "MIT", "remix-run/react-router"],
2149
+ ["TanStack Query", "MIT", "TanStack/query"],
2150
+ ["TanStack Table", "MIT", "TanStack/table"],
2151
+ ["React Hook Form", "MIT", "react-hook-form"],
2152
+ ["Axios", "MIT", "axios/axios"],
2153
+ ["Tailwind CSS", "MIT", "tailwindlabs/tailwindcss"],
2154
+ ["Headless UI", "MIT", "tailwindlabs/headlessui"],
2155
+ ["Heroicons", "MIT", "tailwindlabs/heroicons"],
2156
+ ["Recharts", "MIT", "recharts/recharts"],
2157
+ ["Zod", "MIT", "colinhacks/zod"],
2158
+ ["Vite", "MIT", "vitejs/vite"],
2159
+ ["TypeScript", "Apache-2.0", "microsoft/TypeScript"],
2160
+ ["jSpreadsheet CE", "MIT", "jspreadsheet/ce"],
2161
+ ["Django", "BSD-3", "django/django"],
2162
+ ["Django REST Framework", "BSD-3", "encode/django-rest-framework"],
2163
+ ["PostgreSQL", "PostgreSQL", "postgres/postgres"],
2164
+ ["Gunicorn", "MIT", "benoitc/gunicorn"]
2165
+ ].map(([name, license, repo]) => /* @__PURE__ */ jsxs("tr", { className: "text-gray-600", children: [
2166
+ /* @__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 }) }),
2167
+ /* @__PURE__ */ jsx("td", { className: "py-1 text-gray-400", children: license })
2168
+ ] }, name)) })
2169
+ ] }) })
2170
+ ] }),
2171
+ (host.productCopyright || host.productWebsite) && /* @__PURE__ */ jsxs("div", { className: "pt-3 pb-2 border-t border-gray-200 w-full text-center", children: [
2172
+ host.productCopyright && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-400", children: host.productCopyright }),
2173
+ 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?:\/\//, "") })
2174
+ ] })
2175
+ ] }) });
2176
+ })(),
2177
+ prefs.show_desktop_version === true && (host.productVersion ?? APP_VERSION) && /* @__PURE__ */ jsx(
2178
+ "button",
2179
+ {
2180
+ onClick: (e) => {
2181
+ e.stopPropagation();
2182
+ setWhatsNewOpen(true);
2183
+ },
2184
+ 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 ${prefs.taskbar_position === "top" ? "right-3" : prefs.taskbar_position === "left" ? "right-3" : prefs.taskbar_position === "right" ? "left-3" : "right-3 !bottom-16"}`,
2185
+ children: host.productVersion ?? APP_VERSION
2186
+ }
2187
+ ),
2188
+ whatsNewOpen && (() => {
2189
+ const entries = host.productChangelog ?? changelog_default;
2190
+ return /* @__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: entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 text-center py-6", children: "No changelog available." }) : entries.map((entry, i) => /* @__PURE__ */ jsxs("div", { children: [
2191
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-2", children: [
2192
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-gray-900 font-mono", children: entry.version }),
2193
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: formatDate(entry.date) })
2194
+ ] }),
2195
+ /* @__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: [
2196
+ /* @__PURE__ */ jsx("span", { className: "text-blue-500 mt-1 shrink-0", children: "\u2022" }),
2197
+ change
2198
+ ] }, j)) }),
2199
+ i < entries.length - 1 && /* @__PURE__ */ jsx("div", { className: "border-b border-gray-200 mt-4" })
2200
+ ] }, entry.version)) }) });
2201
+ })()
2202
+ ]
2203
+ }
2204
+ );
2205
+ }
2206
+ var DEFAULT = { hasAnyPerm: () => true };
2207
+ var ShellAuthContext = createContext(DEFAULT);
2208
+ function ShellAuthProvider({ value, children }) {
2209
+ return /* @__PURE__ */ jsx(ShellAuthContext.Provider, { value, children });
2210
+ }
2211
+ function useShellAuth() {
2212
+ return useContext(ShellAuthContext);
2213
+ }
2214
+
2215
+ // src/contexts/AuthContext.tsx
2216
+ var _authBridge = {};
2217
+ function setShellAuthBridge(bridge) {
2218
+ _authBridge = bridge;
2219
+ }
2220
+ function useAuth() {
2221
+ const { hasAnyPerm } = useShellAuth();
2222
+ return {
2223
+ user: _authBridge.user,
2224
+ logout: _authBridge.logout ?? (() => {
2225
+ }),
2226
+ hasAnyPerm
2227
+ };
2228
+ }
2229
+ function subscribeMediaQuery(cb) {
2230
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
2231
+ mq.addEventListener("change", cb);
2232
+ return () => mq.removeEventListener("change", cb);
2233
+ }
2234
+ function getSystemIsDark() {
2235
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
2236
+ }
2237
+ function hexToHsl(hex) {
2238
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
2239
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
2240
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
2241
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
2242
+ let h = 0, s = 0;
2243
+ const l = (max + min) / 2;
2244
+ if (max !== min) {
2245
+ const d = max - min;
2246
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2247
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
2248
+ else if (max === g) h = ((b - r) / d + 2) / 6;
2249
+ else h = ((r - g) / d + 4) / 6;
2250
+ }
2251
+ return [h * 360, s * 100, l * 100];
2252
+ }
2253
+ function hslToHex(h, s, l) {
2254
+ s /= 100;
2255
+ l /= 100;
2256
+ const a = s * Math.min(l, 1 - l);
2257
+ const f = (n) => {
2258
+ const k = (n + h / 30) % 12;
2259
+ return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
2260
+ };
2261
+ const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
2262
+ return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
2263
+ }
2264
+ function useTheme() {
2265
+ const { prefs } = useShellPrefs();
2266
+ const systemDark = useSyncExternalStore(subscribeMediaQuery, getSystemIsDark);
2267
+ const saved = prefs.theme || "system";
2268
+ const accentColor = prefs.accent_color || null;
2269
+ const customBgColor = prefs.custom_bg_color || null;
2270
+ const customTitleColor = prefs.custom_title_color || null;
2271
+ const customWindowColor = prefs.custom_window_color || null;
2272
+ const customButtonColor = prefs.custom_button_color || null;
2273
+ const resolved = saved === "system" ? systemDark ? "dark" : "light" : saved;
2274
+ useEffect(() => {
2275
+ document.documentElement.setAttribute("data-theme", resolved);
2276
+ return () => document.documentElement.removeAttribute("data-theme");
2277
+ }, [resolved]);
2278
+ useEffect(() => {
2279
+ const root = document.documentElement;
2280
+ if (accentColor && /^#[0-9a-fA-F]{6}$/.test(accentColor)) {
2281
+ const [h, s, l] = hexToHsl(accentColor);
2282
+ root.setAttribute("data-custom-accent", "true");
2283
+ root.style.setProperty("--accent-600", accentColor);
2284
+ root.style.setProperty("--accent-700", hslToHex(h, s, Math.max(l - 10, 5)));
2285
+ root.style.setProperty("--accent-500", hslToHex(h, s, Math.min(l + 10, 95)));
2286
+ root.style.setProperty("--accent-400", hslToHex(h, s, Math.min(l + 20, 95)));
2287
+ root.style.setProperty("--accent-300", hslToHex(h, Math.min(s + 10, 100), Math.min(l + 30, 92)));
2288
+ root.style.setProperty("--accent-200", hslToHex(h, Math.min(s + 15, 100), Math.min(l + 38, 94)));
2289
+ root.style.setProperty("--accent-100", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 42, 96)));
2290
+ root.style.setProperty("--accent-50", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 46, 98)));
2291
+ } else {
2292
+ root.removeAttribute("data-custom-accent");
2293
+ ["--accent-600", "--accent-700", "--accent-500", "--accent-400", "--accent-300", "--accent-200", "--accent-100", "--accent-50"].forEach((p) => root.style.removeProperty(p));
2294
+ }
2295
+ }, [accentColor]);
2296
+ useEffect(() => {
2297
+ const root = document.documentElement;
2298
+ if (accentColor) {
2299
+ const hexToRgb = (hex) => {
2300
+ const r = parseInt(hex.slice(1, 3), 16);
2301
+ const g = parseInt(hex.slice(3, 5), 16);
2302
+ const b = parseInt(hex.slice(5, 7), 16);
2303
+ return `${r} ${g} ${b}`;
2304
+ };
2305
+ if (customBgColor) root.style.setProperty("--custom-bg-color", customBgColor);
2306
+ if (customTitleColor) {
2307
+ root.style.setProperty("--window-header-rgb", hexToRgb(customTitleColor));
2308
+ root.style.setProperty("--window-footer-rgb", hexToRgb(customTitleColor));
2309
+ }
2310
+ if (customWindowColor) root.style.setProperty("--window-content-rgb", hexToRgb(customWindowColor));
2311
+ if (customButtonColor) {
2312
+ root.style.setProperty("--custom-button-color", customButtonColor);
2313
+ const [h, s, l] = hexToHsl(customButtonColor);
2314
+ root.style.setProperty("--custom-button-hover", hslToHex(h, s, Math.max(l - 10, 5)));
2315
+ }
2316
+ } else {
2317
+ ["--custom-bg-color", "--custom-button-color", "--custom-button-hover"].forEach((p) => root.style.removeProperty(p));
2318
+ }
2319
+ }, [accentColor, customBgColor, customTitleColor, customWindowColor, customButtonColor]);
2320
+ return { theme: saved, resolved };
2321
+ }
2322
+ function GoogleConnectModal({ open, onClose }) {
2323
+ const { isConnected, user, connect, disconnect, loading, error, hasClientId, setClientId } = useGoogleAuth();
2324
+ const [clientIdInput, setClientIdInput] = useState("");
2325
+ const clientIdRef = useRef(null);
2326
+ return /* @__PURE__ */ jsxs(Modal, { open, onClose, title: "Google Services", size: "md", children: [
2327
+ /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2328
+ /* @__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: [
2329
+ /* @__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" }) }) }),
2330
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: isConnected ? /* @__PURE__ */ jsxs(Fragment, { children: [
2331
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-900", children: user?.name || "Connected" }),
2332
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-green-700 truncate", children: user?.email })
2333
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2334
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: "Not connected" }),
2335
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: "Sign in to access Google services" })
2336
+ ] }) }),
2337
+ 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: [
2338
+ /* @__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" }) }),
2339
+ "Connected"
2340
+ ] })
2341
+ ] }) }),
2342
+ /* @__PURE__ */ jsxs("div", { children: [
2343
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-2", children: "Available Services" }),
2344
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [
2345
+ { icon: /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
2346
+ /* @__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" }),
2347
+ /* @__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" }),
2348
+ /* @__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" }),
2349
+ /* @__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" })
2350
+ ] }), label: "Gmail", desc: "Read, compose, and send emails" },
2351
+ { icon: /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
2352
+ /* @__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 }),
2353
+ /* @__PURE__ */ jsx("path", { d: "M2.25 9h19.5M6.75 3.75v3m4.5-3v3m4.5-3v3", fill: "none", stroke: "#4285F4", strokeWidth: 1.5, strokeLinecap: "round" })
2354
+ ] }), label: "Calendar", desc: "View and create calendar events" },
2355
+ { 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" }
2356
+ ].map((svc) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-gray-200 px-4 py-3", children: [
2357
+ /* @__PURE__ */ jsx("div", { className: "shrink-0", children: svc.icon }),
2358
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2359
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: svc.label }),
2360
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: svc.desc })
2361
+ ] }),
2362
+ 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" }) })
2363
+ ] }, svc.label)) })
2364
+ ] }),
2365
+ !hasClientId && /* @__PURE__ */ jsxs("div", { className: "space-y-2 bg-amber-50 border border-amber-200 rounded-lg p-4", children: [
2366
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-amber-800", children: "Setup required: Google OAuth Client ID" }),
2367
+ /* @__PURE__ */ jsx(
2368
+ "input",
2369
+ {
2370
+ ref: clientIdRef,
2371
+ value: clientIdInput,
2372
+ onChange: (e) => setClientIdInput(e.target.value),
2373
+ onInput: (e) => setClientIdInput(e.target.value),
2374
+ placeholder: "123456789.apps.googleusercontent.com",
2375
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500"
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ jsx(
2379
+ "button",
2380
+ {
2381
+ type: "button",
2382
+ onPointerDown: (e) => e.stopPropagation(),
2383
+ onClick: () => {
2384
+ const val = (clientIdRef.current?.value || clientIdInput).trim();
2385
+ if (val) {
2386
+ setClientId(val);
2387
+ setClientIdInput(val);
2388
+ }
2389
+ },
2390
+ className: "w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 cursor-pointer",
2391
+ children: "Save Client ID"
2392
+ }
2393
+ ),
2394
+ /* @__PURE__ */ jsxs("div", { className: "text-[10px] text-gray-500 space-y-0.5 mt-1", children: [
2395
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-600", children: "Setup instructions:" }),
2396
+ /* @__PURE__ */ jsxs("p", { children: [
2397
+ "1. Go to ",
2398
+ /* @__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" })
2399
+ ] }),
2400
+ /* @__PURE__ */ jsxs("p", { children: [
2401
+ "2. Click ",
2402
+ /* @__PURE__ */ jsx("b", { children: '"+ Create Credentials"' }),
2403
+ ", then select ",
2404
+ /* @__PURE__ */ jsx("b", { children: "OAuth client ID" })
2405
+ ] }),
2406
+ /* @__PURE__ */ jsxs("p", { children: [
2407
+ "3. Choose ",
2408
+ /* @__PURE__ */ jsx("b", { children: '"Web application"' }),
2409
+ ", give it any name, add the URL where this app runs (e.g. ",
2410
+ /* @__PURE__ */ jsx("b", { children: "http://localhost:5173" }),
2411
+ ") as an Authorized JavaScript origin, then click Create"
2412
+ ] }),
2413
+ /* @__PURE__ */ jsx("p", { children: "4. Copy your Client ID and paste it above" })
2414
+ ] })
2415
+ ] }),
2416
+ error && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: error })
2417
+ ] }),
2418
+ /* @__PURE__ */ jsx(ModalActions, { children: isConnected ? /* @__PURE__ */ jsx(
2419
+ "button",
2420
+ {
2421
+ onClick: disconnect,
2422
+ 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",
2423
+ children: "Disconnect"
2424
+ }
2425
+ ) : hasClientId ? /* @__PURE__ */ jsxs(
2426
+ "button",
2427
+ {
2428
+ onClick: connect,
2429
+ disabled: loading,
2430
+ 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",
2431
+ children: [
2432
+ /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", viewBox: "0 0 24 24", children: [
2433
+ /* @__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" }),
2434
+ /* @__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" }),
2435
+ /* @__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" }),
2436
+ /* @__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" })
2437
+ ] }),
2438
+ loading ? "Connecting..." : "Sign in with Google"
2439
+ ]
2440
+ }
2441
+ ) : null })
2442
+ ] });
2443
+ }
2444
+ function useClickOutside(ref, onClose) {
2445
+ useEffect(() => {
2446
+ const handler = (e) => {
2447
+ if (ref.current && !ref.current.contains(e.target)) onClose();
2448
+ };
2449
+ document.addEventListener("mousedown", handler);
2450
+ return () => document.removeEventListener("mousedown", handler);
2451
+ }, [ref, onClose]);
2452
+ }
2453
+ function StartupAnimation({ onComplete, ready = false, productName = "react-os-shell", subtitle }) {
2454
+ const [phase, setPhase] = useState("logo");
2455
+ const [minTimePassed, setMinTimePassed] = useState(false);
2456
+ const onCompleteRef = useRef(onComplete);
2457
+ onCompleteRef.current = onComplete;
2458
+ const fadingRef = useRef(false);
2459
+ useEffect(() => {
2460
+ const t1 = setTimeout(() => setPhase("text"), 600);
2461
+ const t2 = setTimeout(() => setMinTimePassed(true), 2e3);
2462
+ const t3 = setTimeout(() => {
2463
+ if (!fadingRef.current) {
2464
+ fadingRef.current = true;
2465
+ setPhase("fade");
2466
+ setTimeout(() => onCompleteRef.current(), 500);
2467
+ }
2468
+ }, 5e3);
2469
+ return () => {
2470
+ clearTimeout(t1);
2471
+ clearTimeout(t2);
2472
+ clearTimeout(t3);
2473
+ };
2474
+ }, []);
2475
+ useEffect(() => {
2476
+ if (!ready || !minTimePassed || fadingRef.current) return;
2477
+ fadingRef.current = true;
2478
+ setPhase("fade");
2479
+ const t = setTimeout(() => onCompleteRef.current(), 500);
2480
+ return () => clearTimeout(t);
2481
+ }, [ready, minTimePassed]);
2482
+ return /* @__PURE__ */ jsxs(
2483
+ "div",
2484
+ {
2485
+ 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"}`,
2486
+ style: { background: "linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)" },
2487
+ children: [
2488
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx(
2489
+ "div",
2490
+ {
2491
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full opacity-20",
2492
+ style: { background: "radial-gradient(circle, rgba(124,58,237,0.4) 0%, transparent 70%)", animation: "pulse-glow 2s ease-in-out infinite" }
2493
+ }
2494
+ ) }),
2495
+ /* @__PURE__ */ jsx("div", { className: `relative transition-all duration-700 ease-out ${phase === "logo" ? "scale-75 opacity-0" : "scale-100 opacity-100"}`, children: /* @__PURE__ */ jsx(
2496
+ "img",
2497
+ {
2498
+ src: "/favicon.svg",
2499
+ alt: "",
2500
+ className: "h-20 w-20 drop-shadow-[0_0_30px_rgba(124,58,237,0.5)]",
2501
+ style: { animation: "spin-in 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards" }
2502
+ }
2503
+ ) }),
2504
+ /* @__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: [
2505
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-[0.3em] text-white/90 uppercase", children: productName }),
2506
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex items-center justify-center gap-1.5", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
2507
+ "div",
2508
+ {
2509
+ className: "w-1.5 h-1.5 rounded-full bg-purple-400",
2510
+ style: { animation: `dot-bounce 1s ease-in-out ${i * 0.15}s infinite` }
2511
+ },
2512
+ i
2513
+ )) })
2514
+ ] }),
2515
+ 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 }) }),
2516
+ /* @__PURE__ */ jsx("style", { children: `
2517
+ @keyframes spin-in {
2518
+ 0% { transform: scale(0) rotate(-180deg); opacity: 0; }
2519
+ 100% { transform: scale(1) rotate(0deg); opacity: 1; }
2520
+ }
2521
+ @keyframes dot-bounce {
2522
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
2523
+ 40% { transform: scale(1); opacity: 1; }
2524
+ }
2525
+ @keyframes pulse-glow {
2526
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.15; }
2527
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.25; }
2528
+ }
2529
+ ` })
2530
+ ]
2531
+ }
2532
+ );
2533
+ }
2534
+ function LogoutAnimation({ onComplete, subtitle }) {
2535
+ const [phase, setPhase] = useState("show");
2536
+ useEffect(() => {
2537
+ playLogout();
2538
+ const t1 = setTimeout(() => setPhase("shrink"), 800);
2539
+ const t2 = setTimeout(() => setPhase("fade"), 1800);
2540
+ const t3 = setTimeout(onComplete, 2300);
2541
+ return () => {
2542
+ clearTimeout(t1);
2543
+ clearTimeout(t2);
2544
+ clearTimeout(t3);
2545
+ };
2546
+ }, [onComplete]);
2547
+ return /* @__PURE__ */ jsxs(
2548
+ "div",
2549
+ {
2550
+ className: `fixed inset-0 z-[9999] flex flex-col items-center justify-center transition-opacity duration-500 ${phase === "fade" ? "opacity-0" : "opacity-100"}`,
2551
+ style: { background: "linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)" },
2552
+ children: [
2553
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx(
2554
+ "div",
2555
+ {
2556
+ className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full",
2557
+ style: {
2558
+ background: "radial-gradient(circle, rgba(124,58,237,0.4) 0%, transparent 70%)",
2559
+ animation: "logout-glow 2s ease-in-out infinite",
2560
+ opacity: phase === "shrink" ? 0 : 0.2,
2561
+ transition: "opacity 1s"
2562
+ }
2563
+ }
2564
+ ) }),
2565
+ /* @__PURE__ */ jsx(
2566
+ "div",
2567
+ {
2568
+ className: `relative transition-all duration-1000 ease-in ${phase === "shrink" ? "scale-0 opacity-0" : "scale-100 opacity-100"}`,
2569
+ style: phase === "shrink" ? { transform: "scale(0) rotate(180deg)" } : void 0,
2570
+ children: /* @__PURE__ */ jsx("img", { src: "/favicon.svg", alt: "", className: "h-20 w-20 drop-shadow-[0_0_30px_rgba(124,58,237,0.5)]" })
2571
+ }
2572
+ ),
2573
+ /* @__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: [
2574
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-[0.3em] text-white/90 uppercase", children: "Goodbye" }),
2575
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-white/40", children: "See you next time" })
2576
+ ] }),
2577
+ 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 }) }),
2578
+ /* @__PURE__ */ jsx("style", { children: `
2579
+ @keyframes logout-glow {
2580
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.15; }
2581
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.25; }
2582
+ }
2583
+ ` })
2584
+ ]
2585
+ }
2586
+ );
2587
+ }
2588
+ function StartMenu({
2589
+ open,
2590
+ onClose,
2591
+ openPage,
2592
+ profile,
2593
+ user,
2594
+ onLogout,
2595
+ taskbarPosition,
2596
+ taskbarH,
2597
+ taskbarW = 0,
2598
+ size = "medium",
2599
+ navSections: navSections2 = navSections,
2600
+ navIcons: navIcons2 = navIcons,
2601
+ sectionIcons: sectionIcons2 = sectionIcons,
2602
+ categories = startMenuCategories
2603
+ }) {
2604
+ const erpLabels = new Set(categories.erp);
2605
+ const systemLabels = new Set(categories.system);
2606
+ const virtualSections = categories.virtual ?? [];
2607
+ const virtualByLabel = Object.fromEntries(
2608
+ virtualSections.map((v) => [v.label, v])
2609
+ );
2610
+ const { hasAnyPerm } = useAuth();
2611
+ const isMobile = useIsMobile();
2612
+ const [hoveredSection, setHoveredSection] = useState(null);
2613
+ const [hoveredY, setHoveredY] = useState(0);
2614
+ const [search, setSearch] = useState("");
2615
+ const [searchIdx, setSearchIdx] = useState(0);
2616
+ const menuRef = useRef(null);
2617
+ const hoverTimeout = useRef();
2618
+ useEffect(() => {
2619
+ if (!open) {
2620
+ setSearch("");
2621
+ setHoveredSection(null);
2622
+ setSearchIdx(0);
2623
+ }
2624
+ }, [open]);
2625
+ useEffect(() => {
2626
+ if (!open) return;
2627
+ const handler = (e) => {
2628
+ if (e.key === "Escape") onClose();
2629
+ };
2630
+ const click = (e) => {
2631
+ if (e.target.closest("[data-menu-toggle]")) return;
2632
+ if (menuRef.current && !menuRef.current.contains(e.target)) onClose();
2633
+ };
2634
+ window.addEventListener("keydown", handler);
2635
+ window.addEventListener("pointerdown", click);
2636
+ return () => {
2637
+ window.removeEventListener("keydown", handler);
2638
+ window.removeEventListener("pointerdown", click);
2639
+ };
2640
+ }, [open, onClose]);
2641
+ if (!open) return null;
2642
+ const handleClick = (path) => {
2643
+ openPage(path);
2644
+ onClose();
2645
+ };
2646
+ if (isMobile) {
2647
+ const allItems = [];
2648
+ for (const entry of navSections2) {
2649
+ if (isSection(entry)) {
2650
+ const sec = entry;
2651
+ if (sec.perms && !hasAnyPerm(sec.perms)) continue;
2652
+ for (const it of sec.items) {
2653
+ if (it.perms && !hasAnyPerm(it.perms)) continue;
2654
+ allItems.push({ item: it, sectionLabel: sec.label });
2655
+ }
2656
+ } else {
2657
+ const it = entry;
2658
+ if (it.perms && !hasAnyPerm(it.perms)) continue;
2659
+ allItems.push({ item: it });
2660
+ }
2661
+ }
2662
+ const filtered = search.length >= 1 ? allItems.filter(({ item }) => item.label.toLowerCase().includes(search.toLowerCase())) : allItems;
2663
+ return /* @__PURE__ */ jsxs(
2664
+ "div",
2665
+ {
2666
+ ref: menuRef,
2667
+ className: "fixed inset-0 z-[260] flex flex-col bg-white",
2668
+ style: { paddingBottom: "var(--mobile-bottom-nav, 56px)" },
2669
+ children: [
2670
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-gray-200", children: [
2671
+ /* @__PURE__ */ jsx("button", { onClick: onClose, className: "p-2 -ml-1 rounded-full active:bg-gray-200 text-gray-700", "aria-label": "Close menu", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-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" }) }) }),
2672
+ /* @__PURE__ */ jsxs("div", { className: `flex-1 flex items-center gap-2 ${GLASS_INPUT_BG} rounded-lg px-3 py-2`, children: [
2673
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 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" }) }),
2674
+ /* @__PURE__ */ jsx(
2675
+ "input",
2676
+ {
2677
+ value: search,
2678
+ onChange: (e) => setSearch(e.target.value),
2679
+ placeholder: "Search apps...",
2680
+ className: "flex-1 bg-transparent text-sm outline-none placeholder-gray-400",
2681
+ autoFocus: true
2682
+ }
2683
+ )
2684
+ ] })
2685
+ ] }),
2686
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 text-center py-12", children: "No matching apps" }) : filtered.map(({ item, sectionLabel }, i) => {
2687
+ const icon = navIcons2[item.to];
2688
+ return /* @__PURE__ */ jsxs(
2689
+ "button",
2690
+ {
2691
+ onClick: () => handleClick(item.to),
2692
+ className: "w-full flex items-center gap-3 px-4 py-3 active:bg-gray-100 border-b border-gray-100 text-left",
2693
+ children: [
2694
+ /* @__PURE__ */ jsx("span", { className: "h-8 w-8 rounded-lg bg-gray-100 flex items-center justify-center text-gray-600 shrink-0", children: icon && isValidElement(icon) ? cloneElement(icon, { className: "h-5 w-5" }) : null }),
2695
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2696
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900 truncate", children: item.label }),
2697
+ sectionLabel && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-gray-500 truncate", children: sectionLabel })
2698
+ ] })
2699
+ ]
2700
+ },
2701
+ `${item.to}-${i}`
2702
+ );
2703
+ }) })
2704
+ ]
2705
+ }
2706
+ );
2707
+ }
2708
+ const isVertical = taskbarPosition !== "bottom";
2709
+ const topItems = navSections2.filter((item) => !isSection(item));
2710
+ const erpSections = navSections2.filter((item) => isSection(item) && erpLabels.has(item.label));
2711
+ const systemSections = navSections2.filter((item) => isSection(item) && systemLabels.has(item.label));
2712
+ const getVisibleItems = (section) => section.items.filter((item) => !item.perms || hasAnyPerm(item.perms));
2713
+ const searchResults = search.length >= 2 ? navSections2.flatMap((item) => {
2714
+ if (isSection(item)) {
2715
+ return item.items.filter((i) => (!i.perms || hasAnyPerm(i.perms)) && i.label.toLowerCase().includes(search.toLowerCase())).map((i) => ({ ...i, section: item.label }));
2716
+ }
2717
+ return item.label.toLowerCase().includes(search.toLowerCase()) ? [{ ...item, section: "" }] : [];
2718
+ }) : [];
2719
+ 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 };
2720
+ const iconEl = (path) => {
2721
+ const icon = navIcons2[path];
2722
+ if (icon && isValidElement(icon)) return cloneElement(icon, { className: "h-4 w-4" });
2723
+ return null;
2724
+ };
2725
+ const secIcon = (label) => {
2726
+ const icon = sectionIcons2[label];
2727
+ if (icon && isValidElement(icon)) return cloneElement(icon, { className: "h-4 w-4" });
2728
+ return null;
2729
+ };
2730
+ const hoveredVirtual = hoveredSection ? virtualByLabel[hoveredSection] : void 0;
2731
+ const hoveredData = hoveredVirtual ? null : hoveredSection ? [...erpSections, ...systemSections].find((s) => s.label === hoveredSection) : null;
2732
+ const flyoutItems = hoveredVirtual ? hoveredVirtual.items : hoveredData ? getVisibleItems(hoveredData) : [];
2733
+ const menuDensity = typeof document !== "undefined" ? getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal" : "normal";
2734
+ const tight = menuDensity === "tight";
2735
+ 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];
2736
+ const menuGlass = glassStyle();
2737
+ const itemCls = `w-full flex items-center gap-2 rounded-lg ${sizeConfig.px} ${sizeConfig.py} ${sizeConfig.text}`;
2738
+ const flyoutH = flyoutItems.length * sizeConfig.itemH + 12;
2739
+ const menuWidth = sizeConfig.mw;
2740
+ let flyoutTop = hoveredY - flyoutH / 2;
2741
+ const minTop = taskbarPosition === "top" ? taskbarH + 4 : 4;
2742
+ const maxBottom = taskbarPosition === "bottom" ? window.innerHeight - taskbarH - 4 : window.innerHeight - 4;
2743
+ if (flyoutTop < minTop) flyoutTop = minTop;
2744
+ if (flyoutTop + flyoutH > maxBottom) flyoutTop = maxBottom - flyoutH;
2745
+ const handleSectionHover = (label, e) => {
2746
+ clearTimeout(hoverTimeout.current);
2747
+ const rect = e.currentTarget.getBoundingClientRect();
2748
+ setHoveredY(rect.top + rect.height / 2);
2749
+ setHoveredSection(label);
2750
+ };
2751
+ const renderSection = (section, isErp) => {
2752
+ if (section.perms && !hasAnyPerm(section.perms)) return null;
2753
+ const items = getVisibleItems(section);
2754
+ if (items.length === 0) return null;
2755
+ const isHovered = hoveredSection === section.label;
2756
+ return /* @__PURE__ */ jsx(
2757
+ "div",
2758
+ {
2759
+ onMouseEnter: (e) => handleSectionHover(section.label, e),
2760
+ onMouseLeave: () => {
2761
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2762
+ },
2763
+ 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: [
2764
+ secIcon(section.label),
2765
+ /* @__PURE__ */ jsx("span", { className: isErp ? "font-medium" : "", children: section.label }),
2766
+ /* @__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" }) })
2767
+ ] })
2768
+ },
2769
+ section.label
2770
+ );
2771
+ };
2772
+ const renderVirtualSection = (v) => {
2773
+ if (v.items.length === 0) return null;
2774
+ const isHovered = hoveredSection === v.label;
2775
+ return /* @__PURE__ */ jsx(
2776
+ "div",
2777
+ {
2778
+ onMouseEnter: (e) => handleSectionHover(v.label, e),
2779
+ onMouseLeave: () => {
2780
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2781
+ },
2782
+ 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: [
2783
+ v.icon,
2784
+ /* @__PURE__ */ jsx("span", { children: v.label }),
2785
+ /* @__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" }) })
2786
+ ] })
2787
+ },
2788
+ v.label
2789
+ );
2790
+ };
2791
+ return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "fixed z-[260]", style: posStyle, children: [
2792
+ /* @__PURE__ */ jsxs("div", { className: "flex", children: [
2793
+ /* @__PURE__ */ jsxs(
2794
+ "div",
2795
+ {
2796
+ className: `${sizeConfig.w} rounded-2xl flex ${isVertical ? "flex-col-reverse" : "flex-col"} overflow-hidden`,
2797
+ style: { animation: "menu-in 0.15s ease-out", ...menuGlass },
2798
+ children: [
2799
+ /* @__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: [
2800
+ /* @__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" }) }),
2801
+ /* @__PURE__ */ jsx(
2802
+ "input",
2803
+ {
2804
+ value: search,
2805
+ onChange: (e) => {
2806
+ setSearch(e.target.value);
2807
+ setHoveredSection(null);
2808
+ setSearchIdx(0);
2809
+ },
2810
+ onKeyDown: (e) => {
2811
+ if (search.length >= 2 && searchResults.length > 0) {
2812
+ if (e.key === "ArrowDown") {
2813
+ e.preventDefault();
2814
+ setSearchIdx((i) => Math.min(i + 1, searchResults.length - 1));
2815
+ } else if (e.key === "ArrowUp") {
2816
+ e.preventDefault();
2817
+ setSearchIdx((i) => Math.max(i - 1, 0));
2818
+ } else if (e.key === "Enter") {
2819
+ e.preventDefault();
2820
+ handleClick(searchResults[searchIdx].to);
2821
+ }
2822
+ }
2823
+ },
2824
+ placeholder: "Search...",
2825
+ className: "flex-1 bg-transparent text-xs outline-none placeholder-gray-400",
2826
+ autoFocus: true
2827
+ }
2828
+ )
2829
+ ] }) }),
2830
+ 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(
2831
+ "button",
2832
+ {
2833
+ onClick: () => handleClick(r.to),
2834
+ onMouseEnter: () => setSearchIdx(i),
2835
+ className: `${itemCls} transition-colors ${i === searchIdx ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`,
2836
+ children: [
2837
+ iconEl(r.to),
2838
+ /* @__PURE__ */ jsx("span", { children: r.label }),
2839
+ r.section && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 ml-auto", children: r.section })
2840
+ ]
2841
+ },
2842
+ i
2843
+ )) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-1 pb-1 flex flex-col", children: [
2844
+ isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
2845
+ erpSections.map((s) => renderSection(s, true)),
2846
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
2847
+ topItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
2848
+ /* @__PURE__ */ jsxs(
2849
+ "button",
2850
+ {
2851
+ onClick: () => handleClick(item.to),
2852
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2853
+ children: [
2854
+ iconEl(item.to),
2855
+ /* @__PURE__ */ jsx("span", { children: item.label })
2856
+ ]
2857
+ }
2858
+ ),
2859
+ item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
2860
+ ] }, item.to)),
2861
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleClick("/notifications"), className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`, children: [
2862
+ /* @__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" }) }),
2863
+ /* @__PURE__ */ jsx("span", { children: "Notifications" })
2864
+ ] }),
2865
+ systemSections.map((s) => renderSection(s, false)),
2866
+ virtualSections.map((v) => renderVirtualSection(v))
2867
+ ] }),
2868
+ !isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
2869
+ topItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
2870
+ /* @__PURE__ */ jsxs(
2871
+ "button",
2872
+ {
2873
+ onClick: () => handleClick(item.to),
2874
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2875
+ children: [
2876
+ iconEl(item.to),
2877
+ /* @__PURE__ */ jsx("span", { children: item.label })
2878
+ ]
2879
+ }
2880
+ ),
2881
+ item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
2882
+ ] }, item.to)),
2883
+ /* @__PURE__ */ jsxs("button", { onClick: () => handleClick("/notifications"), className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`, children: [
2884
+ /* @__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" }) }),
2885
+ /* @__PURE__ */ jsx("span", { children: "Notifications" })
2886
+ ] }),
2887
+ systemSections.map((s) => renderSection(s, false)),
2888
+ virtualSections.map((v) => renderVirtualSection(v)),
2889
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
2890
+ erpSections.map((s) => renderSection(s, true))
2891
+ ] })
2892
+ ] }),
2893
+ /* @__PURE__ */ jsx("div", { className: `${isVertical ? "border-b" : "border-t"} border-white/20 p-1`, children: /* @__PURE__ */ jsxs(
2894
+ "div",
2895
+ {
2896
+ onClick: () => handleClick("/profile"),
2897
+ 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",
2898
+ children: [
2899
+ 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() }),
2900
+ /* @__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 }),
2901
+ /* @__PURE__ */ jsx(
2902
+ "button",
2903
+ {
2904
+ onClick: (e) => {
2905
+ e.stopPropagation();
2906
+ onClose();
2907
+ onLogout();
2908
+ },
2909
+ title: "Sign Out",
2910
+ className: "shrink-0 p-1.5 rounded-md text-gray-500 hover:text-red-600 hover:bg-red-50 transition-colors",
2911
+ 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" }) })
2912
+ }
2913
+ )
2914
+ ]
2915
+ }
2916
+ ) })
2917
+ ]
2918
+ }
2919
+ ),
2920
+ hoveredSection && flyoutItems.length > 0 && search.length < 2 && /* @__PURE__ */ jsx(
2921
+ "div",
2922
+ {
2923
+ className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
2924
+ style: { left: menuRef.current ? menuRef.current.getBoundingClientRect().right + 4 : menuWidth + 12, top: flyoutTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
2925
+ onMouseEnter: () => clearTimeout(hoverTimeout.current),
2926
+ onMouseLeave: () => {
2927
+ hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
2928
+ },
2929
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1", children: flyoutItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
2930
+ /* @__PURE__ */ jsxs(
2931
+ "button",
2932
+ {
2933
+ onClick: () => handleClick(item.to),
2934
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
2935
+ children: [
2936
+ iconEl(item.to),
2937
+ /* @__PURE__ */ jsx("span", { children: item.label })
2938
+ ]
2939
+ }
2940
+ ),
2941
+ item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
2942
+ ] }, item.to)) })
2943
+ }
2944
+ )
2945
+ ] }),
2946
+ /* @__PURE__ */ jsx("style", { children: `
2947
+ @keyframes menu-in { from { opacity: 0; transform: scale(0.95) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
2948
+ @keyframes submenu-in { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: translateX(0); } }
2949
+ ` })
2950
+ ] });
2951
+ }
2952
+
2953
+ // src/shell/mobileShellStore.ts
2954
+ var _mode = "home";
2955
+ var listeners = /* @__PURE__ */ new Set();
2956
+ function getMobileMode() {
2957
+ return _mode;
2958
+ }
2959
+ function setMobileMode(mode) {
2960
+ if (_mode === mode) return;
2961
+ _mode = mode;
2962
+ listeners.forEach((fn) => fn());
2963
+ }
2964
+ function subscribeMobileMode(cb) {
2965
+ listeners.add(cb);
2966
+ return () => {
2967
+ listeners.delete(cb);
2968
+ };
2969
+ }
2970
+ var MOBILE_WIDGET_ORDER_KEY = "erp_mobile_widget_order";
2971
+ var MOBILE_HOME_ORDER_KEY = "erp_mobile_home_order";
2972
+ var LONG_PRESS_MS = 400;
2973
+ var ICON_GRADIENTS = [
2974
+ "from-blue-500 to-blue-700",
2975
+ "from-indigo-500 to-purple-600",
2976
+ "from-purple-500 to-pink-600",
2977
+ "from-pink-500 to-rose-600",
2978
+ "from-red-500 to-rose-600",
2979
+ "from-orange-500 to-red-600",
2980
+ "from-amber-500 to-orange-600",
2981
+ "from-yellow-500 to-amber-500",
2982
+ "from-lime-500 to-green-600",
2983
+ "from-green-500 to-emerald-600",
2984
+ "from-emerald-500 to-teal-600",
2985
+ "from-teal-500 to-cyan-600",
2986
+ "from-cyan-500 to-sky-600",
2987
+ "from-sky-500 to-blue-600",
2988
+ "from-violet-500 to-fuchsia-600"
2989
+ ];
2990
+ function hashGradient(seed) {
2991
+ let h = 0;
2992
+ for (let i = 0; i < seed.length; i++) h = (h << 5) - h + seed.charCodeAt(i);
2993
+ return ICON_GRADIENTS[Math.abs(h) % ICON_GRADIENTS.length];
2994
+ }
2995
+ function loadOrder(key) {
2996
+ try {
2997
+ const raw = localStorage.getItem(key);
2998
+ if (raw) {
2999
+ const parsed = JSON.parse(raw);
3000
+ if (Array.isArray(parsed)) return parsed.filter((s) => typeof s === "string");
3001
+ }
3002
+ } catch {
3003
+ }
3004
+ return [];
3005
+ }
3006
+ function saveOrder(key, order) {
3007
+ try {
3008
+ localStorage.setItem(key, JSON.stringify(order));
3009
+ } catch {
3010
+ }
3011
+ }
3012
+ var FALLBACK_APP_ICON = /* @__PURE__ */ jsx("svg", { className: "h-10 w-10", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6.75A2.25 2.25 0 016 4.5h12a2.25 2.25 0 012.25 2.25v10.5A2.25 2.25 0 0118 19.5H6a2.25 2.25 0 01-2.25-2.25V6.75z" }) });
3013
+ function sizeIcon(node, fallback, sizeClass = "h-10 w-10") {
3014
+ if (!node) return fallback;
3015
+ if (isValidElement(node)) {
3016
+ return cloneElement(node, {
3017
+ className: `${sizeClass} ${node.props?.className ?? ""}`.trim()
3018
+ });
3019
+ }
3020
+ return node;
3021
+ }
3022
+ function AppTile({ route, icon }) {
3023
+ return /* @__PURE__ */ jsx("span", { className: `relative aspect-square w-full rounded-2xl bg-gradient-to-br ${hashGradient(route)} flex items-center justify-center text-white shadow-sm border border-white/30`, children: sizeIcon(icon, FALLBACK_APP_ICON, "h-11 w-11") });
3024
+ }
3025
+ function FolderTile({ section, navIcons: navIcons2 }) {
3026
+ const previewItems = section.items.slice(0, 4);
3027
+ return /* @__PURE__ */ jsxs("span", { className: "relative aspect-square w-full rounded-2xl bg-white/30 backdrop-blur-sm border border-white/40 p-1.5 grid grid-cols-2 gap-1 shadow-sm", children: [
3028
+ previewItems.map((item) => /* @__PURE__ */ jsx(
3029
+ "span",
3030
+ {
3031
+ className: `rounded-md bg-gradient-to-br ${hashGradient(item.to)} flex items-center justify-center text-white`,
3032
+ children: sizeIcon(navIcons2[item.to], FALLBACK_APP_ICON, "h-3.5 w-3.5")
3033
+ },
3034
+ item.to
3035
+ )),
3036
+ Array.from({ length: Math.max(0, 4 - previewItems.length) }).map((_, i) => /* @__PURE__ */ jsx("span", { className: "rounded-md bg-white/20" }, `empty-${i}`))
3037
+ ] });
3038
+ }
3039
+ function MobileHome({
3040
+ navSections: navSections2,
3041
+ navIcons: navIcons2,
3042
+ sectionIcons: sectionIcons2,
3043
+ openWindows,
3044
+ onOpenApp,
3045
+ onActivateWindow
3046
+ }) {
3047
+ const [selectedFolder, setSelectedFolder] = useState(null);
3048
+ const homeIconsRaw = useMemo(() => {
3049
+ const list = [];
3050
+ for (const entry of navSections2) {
3051
+ if (isSection(entry)) {
3052
+ const sec = entry;
3053
+ list.push({ kind: "folder", id: `folder:${sec.label}`, label: sec.label, section: sec });
3054
+ } else {
3055
+ const it = entry;
3056
+ list.push({ kind: "app", id: `app:${it.to}`, label: it.label, route: it.to });
3057
+ }
3058
+ }
3059
+ return list;
3060
+ }, [navSections2]);
3061
+ const [homeOrder, setHomeOrder] = useState(() => loadOrder(MOBILE_HOME_ORDER_KEY));
3062
+ const homeIcons = useMemo(() => {
3063
+ const indexFor = (id) => {
3064
+ const i = homeOrder.indexOf(id);
3065
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
3066
+ };
3067
+ return homeIconsRaw.slice().sort((a, b) => {
3068
+ const ia = indexFor(a.id);
3069
+ const ib = indexFor(b.id);
3070
+ if (ia !== ib) return ia - ib;
3071
+ return homeIconsRaw.indexOf(a) - homeIconsRaw.indexOf(b);
3072
+ });
3073
+ }, [homeIconsRaw, homeOrder]);
3074
+ useEffect(() => {
3075
+ const visibleIds = homeIconsRaw.map((i) => i.id);
3076
+ const next = [
3077
+ ...homeOrder.filter((id) => visibleIds.includes(id)),
3078
+ ...visibleIds.filter((id) => !homeOrder.includes(id))
3079
+ ];
3080
+ if (next.length !== homeOrder.length || next.some((id, i) => id !== homeOrder[i])) {
3081
+ setHomeOrder(next);
3082
+ saveOrder(MOBILE_HOME_ORDER_KEY, next);
3083
+ }
3084
+ }, [homeIconsRaw, homeOrder]);
3085
+ const [dragId, setDragId] = useState(null);
3086
+ const [dragPos, setDragPos] = useState(null);
3087
+ const dragOffsetRef = useRef({ x: 0, y: 0 });
3088
+ const longPressTimerRef = useRef(null);
3089
+ const longPressFiredRef = useRef(false);
3090
+ const cancelLongPress = () => {
3091
+ if (longPressTimerRef.current) {
3092
+ clearTimeout(longPressTimerRef.current);
3093
+ longPressTimerRef.current = null;
3094
+ }
3095
+ };
3096
+ const beginLongPress = (id, e) => {
3097
+ if (dragId) return;
3098
+ const x = e.clientX;
3099
+ const y = e.clientY;
3100
+ const target = e.currentTarget;
3101
+ longPressFiredRef.current = false;
3102
+ cancelLongPress();
3103
+ longPressTimerRef.current = setTimeout(() => {
3104
+ if (!target.isConnected) return;
3105
+ const rect = target.getBoundingClientRect();
3106
+ dragOffsetRef.current = { x: x - rect.left, y: y - rect.top };
3107
+ longPressFiredRef.current = true;
3108
+ setDragId(id);
3109
+ setDragPos({ x, y });
3110
+ try {
3111
+ navigator.vibrate?.(15);
3112
+ } catch {
3113
+ }
3114
+ }, LONG_PRESS_MS);
3115
+ };
3116
+ useEffect(() => {
3117
+ if (!dragId) return;
3118
+ const onMove = (e) => {
3119
+ setDragPos({ x: e.clientX, y: e.clientY });
3120
+ const el = document.elementFromPoint(e.clientX, e.clientY);
3121
+ const targetEl = el?.closest("[data-home-icon-id]");
3122
+ if (!targetEl) return;
3123
+ const targetId = targetEl.dataset.homeIconId;
3124
+ if (targetId === dragId) return;
3125
+ setHomeOrder((prev) => {
3126
+ const fromIdx = prev.indexOf(dragId);
3127
+ const toIdx = prev.indexOf(targetId);
3128
+ if (fromIdx === -1 || toIdx === -1) return prev;
3129
+ const next = prev.slice();
3130
+ next.splice(fromIdx, 1);
3131
+ next.splice(toIdx, 0, dragId);
3132
+ return next;
3133
+ });
3134
+ };
3135
+ const onUp = () => {
3136
+ setDragId(null);
3137
+ setDragPos(null);
3138
+ setHomeOrder((prev) => {
3139
+ saveOrder(MOBILE_HOME_ORDER_KEY, prev);
3140
+ return prev;
3141
+ });
3142
+ };
3143
+ window.addEventListener("pointermove", onMove);
3144
+ window.addEventListener("pointerup", onUp);
3145
+ window.addEventListener("pointercancel", onUp);
3146
+ return () => {
3147
+ window.removeEventListener("pointermove", onMove);
3148
+ window.removeEventListener("pointerup", onUp);
3149
+ window.removeEventListener("pointercancel", onUp);
3150
+ };
3151
+ }, [dragId]);
3152
+ const handleIconClick = (icon) => {
3153
+ if (longPressFiredRef.current) {
3154
+ longPressFiredRef.current = false;
3155
+ return;
3156
+ }
3157
+ if (icon.kind === "app") onOpenApp(icon.route);
3158
+ else setSelectedFolder(icon.section);
3159
+ };
3160
+ const [widgetOrder, _setWidgetOrder] = useState(() => loadOrder(MOBILE_WIDGET_ORDER_KEY));
3161
+ const widgetWindows = useMemo(() => {
3162
+ const widgets = openWindows.filter((w) => {
3163
+ if (!w.route) return false;
3164
+ const entry = WINDOW_REGISTRY[w.route];
3165
+ return entry && isPageEntry(entry) && entry.widget;
3166
+ });
3167
+ const indexFor = (route) => {
3168
+ const i = widgetOrder.indexOf(route);
3169
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
3170
+ };
3171
+ return widgets.slice().sort((a, b) => indexFor(a.route) - indexFor(b.route));
3172
+ }, [openWindows, widgetOrder]);
3173
+ useEffect(() => {
3174
+ const visibleRoutes = widgetWindows.map((w) => w.route).filter(Boolean);
3175
+ const next = [
3176
+ ...widgetOrder.filter((r) => visibleRoutes.includes(r)),
3177
+ ...visibleRoutes.filter((r) => !widgetOrder.includes(r))
3178
+ ];
3179
+ if (next.length !== widgetOrder.length || next.some((r, i) => r !== widgetOrder[i])) {
3180
+ _setWidgetOrder(next);
3181
+ saveOrder(MOBILE_WIDGET_ORDER_KEY, next);
3182
+ }
3183
+ }, [widgetWindows, widgetOrder]);
3184
+ useMemo(() => {
3185
+ const map = /* @__PURE__ */ new Map();
3186
+ for (const w of openWindows) {
3187
+ if (!w.route) continue;
3188
+ map.set(w.route, (map.get(w.route) ?? 0) + 1);
3189
+ }
3190
+ return map;
3191
+ }, [openWindows]);
3192
+ const draggedIcon = dragId ? homeIcons.find((i) => i.id === dragId) : null;
3193
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3194
+ /* @__PURE__ */ jsx(
3195
+ "div",
3196
+ {
3197
+ className: "h-full overflow-y-auto px-4 pt-4 pb-4 select-none",
3198
+ style: {
3199
+ // Disable iOS long-press text-selection / "Copy" callout on icon labels.
3200
+ WebkitUserSelect: "none",
3201
+ WebkitTouchCallout: "none"
3202
+ },
3203
+ children: /* @__PURE__ */ jsxs("div", { children: [
3204
+ widgetWindows.length > 0 && /* @__PURE__ */ jsx("section", { className: "mb-4", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-4", children: widgetWindows.map((w) => {
3205
+ const entry = WINDOW_REGISTRY[w.route];
3206
+ if (!entry) return null;
3207
+ const Component = entry.component;
3208
+ return /* @__PURE__ */ jsx(
3209
+ "div",
3210
+ {
3211
+ className: "col-span-2 relative rounded-2xl bg-white/85 backdrop-blur border border-white/40 shadow-md overflow-hidden aspect-square",
3212
+ children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx(LoadingSpinner, {}) }), children: /* @__PURE__ */ jsx(Component, {}) })
3213
+ },
3214
+ w.id
3215
+ );
3216
+ }) }) }),
3217
+ homeIcons.length > 0 && /* @__PURE__ */ jsx("section", { children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-4", children: homeIcons.map((icon) => {
3218
+ const isFolder = icon.kind === "folder";
3219
+ const isBeingDragged = dragId === icon.id;
3220
+ return /* @__PURE__ */ jsxs(
3221
+ "button",
3222
+ {
3223
+ "data-home-icon-id": icon.id,
3224
+ onPointerDown: (e) => beginLongPress(icon.id, e),
3225
+ onPointerUp: cancelLongPress,
3226
+ onPointerCancel: cancelLongPress,
3227
+ onPointerLeave: cancelLongPress,
3228
+ onClick: () => handleIconClick(icon),
3229
+ style: { touchAction: "none", visibility: isBeingDragged ? "hidden" : "visible" },
3230
+ className: `flex flex-col items-center gap-1 py-1 rounded-lg active:bg-white/20 ${dragId && !isBeingDragged ? "transition-transform" : ""}`,
3231
+ children: [
3232
+ isFolder ? /* @__PURE__ */ jsx(FolderTile, { section: icon.section, navIcons: navIcons2 }) : /* @__PURE__ */ jsx(AppTile, { route: icon.route, icon: navIcons2[icon.route] }),
3233
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-white drop-shadow-sm truncate w-full text-center", children: icon.label })
3234
+ ]
3235
+ },
3236
+ icon.id
3237
+ );
3238
+ }) }) })
3239
+ ] })
3240
+ }
3241
+ ),
3242
+ draggedIcon && dragPos && /* @__PURE__ */ jsx(
3243
+ "div",
3244
+ {
3245
+ className: "fixed pointer-events-none z-[400] transition-none",
3246
+ style: {
3247
+ left: dragPos.x - dragOffsetRef.current.x,
3248
+ top: dragPos.y - dragOffsetRef.current.y,
3249
+ transform: "scale(1.12)"
3250
+ },
3251
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 py-1 w-20", children: [
3252
+ draggedIcon.kind === "folder" ? /* @__PURE__ */ jsx(FolderTile, { section: draggedIcon.section, navIcons: navIcons2 }) : /* @__PURE__ */ jsx(AppTile, { route: draggedIcon.route, icon: navIcons2[draggedIcon.route] }),
3253
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-white drop-shadow-md truncate w-full text-center", children: draggedIcon.label })
3254
+ ] })
3255
+ }
3256
+ ),
3257
+ selectedFolder && /* @__PURE__ */ jsx(
3258
+ FolderPopup,
3259
+ {
3260
+ folder: selectedFolder,
3261
+ navIcons: navIcons2,
3262
+ onClose: () => setSelectedFolder(null),
3263
+ onOpenApp: (path) => {
3264
+ setSelectedFolder(null);
3265
+ onOpenApp(path);
3266
+ }
3267
+ }
3268
+ )
3269
+ ] });
3270
+ }
3271
+ function FolderPopup({
3272
+ folder,
3273
+ navIcons: navIcons2,
3274
+ onClose,
3275
+ onOpenApp
3276
+ }) {
3277
+ const [closing, setClosing] = useState(false);
3278
+ const beginClose = () => {
3279
+ if (closing) return;
3280
+ setClosing(true);
3281
+ setTimeout(onClose, 200);
3282
+ };
3283
+ const closeThen = (after) => {
3284
+ if (closing) return;
3285
+ setClosing(true);
3286
+ setTimeout(after, 200);
3287
+ };
3288
+ return /* @__PURE__ */ jsxs(
3289
+ "div",
3290
+ {
3291
+ className: "fixed inset-0 z-[210] flex flex-col items-center justify-center px-6 bg-black/45 backdrop-blur-xl select-none",
3292
+ style: {
3293
+ paddingBottom: "calc(var(--mobile-bottom-nav, 70px) + 16px)",
3294
+ animation: closing ? "folder-fade-out 200ms ease-in forwards" : "folder-fade-in 220ms ease-out",
3295
+ WebkitUserSelect: "none",
3296
+ WebkitTouchCallout: "none"
3297
+ },
3298
+ onClick: beginClose,
3299
+ children: [
3300
+ /* @__PURE__ */ jsx("style", { children: `
3301
+ @keyframes folder-fade-in { from { opacity: 0; } to { opacity: 1; } }
3302
+ @keyframes folder-fade-out { from { opacity: 1; } to { opacity: 0; } }
3303
+ @keyframes folder-pop-in { from { opacity: 0; transform: scale(0.86) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
3304
+ @keyframes folder-pop-out { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.9) translateY(4px); } }
3305
+ ` }),
3306
+ /* @__PURE__ */ jsxs("div", { className: "w-full max-w-[304px]", children: [
3307
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold text-white drop-shadow-md mb-4 ml-4", children: folder.label }),
3308
+ /* @__PURE__ */ jsx(
3309
+ "div",
3310
+ {
3311
+ className: "max-h-[70vh] flex flex-col rounded-3xl bg-white/15 backdrop-blur-xl border border-white/25 shadow-2xl overflow-hidden",
3312
+ style: {
3313
+ animation: closing ? "folder-pop-out 180ms ease-in forwards" : "folder-pop-in 240ms cubic-bezier(0.34, 1.56, 0.64, 1)"
3314
+ },
3315
+ onClick: (e) => e.stopPropagation(),
3316
+ children: /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-4 py-5", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-4", children: folder.items.map((item) => {
3317
+ return /* @__PURE__ */ jsxs(
3318
+ "button",
3319
+ {
3320
+ onClick: () => closeThen(() => onOpenApp(item.to)),
3321
+ className: "flex flex-col items-center gap-1 py-1 rounded-lg active:bg-white/15",
3322
+ children: [
3323
+ /* @__PURE__ */ jsx(AppTile, { route: item.to, icon: navIcons2[item.to] }),
3324
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-white drop-shadow-sm truncate w-full text-center", children: item.label })
3325
+ ]
3326
+ },
3327
+ item.to
3328
+ );
3329
+ }) }) })
3330
+ }
3331
+ )
3332
+ ] })
3333
+ ]
3334
+ }
3335
+ );
3336
+ }
3337
+ function MobileSwitcher({ windows, onActivate, onClose, onCloseAll }) {
3338
+ const [cardSize, setCardSize] = useState(() => computeCardSize());
3339
+ useEffect(() => {
3340
+ const onResize = () => setCardSize(computeCardSize());
3341
+ window.addEventListener("resize", onResize);
3342
+ return () => window.removeEventListener("resize", onResize);
3343
+ }, []);
3344
+ if (windows.length === 0) {
3345
+ return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col items-center justify-center gap-3 text-white/80 px-6 text-center", children: [
3346
+ /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 text-white/40", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.4, children: [
3347
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "3.5", width: "7", height: "7", rx: "1.25" }),
3348
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "3.5", width: "7", height: "7", rx: "1.25" }),
3349
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "13.5", width: "7", height: "7", rx: "1.25" }),
3350
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "13.5", width: "7", height: "7", rx: "1.25" })
3351
+ ] }),
3352
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "No open apps." }),
3353
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-white/50", children: "Tap Home to launch one." })
3354
+ ] });
3355
+ }
3356
+ return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col", children: [
3357
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-3 pt-4 pb-4", children: [
3358
+ /* @__PURE__ */ jsxs("h1", { className: "text-white text-base font-semibold mb-3 px-1", children: [
3359
+ "Open apps \xB7 ",
3360
+ windows.length
3361
+ ] }),
3362
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: windows.map((w) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-stretch gap-1", children: [
3363
+ /* @__PURE__ */ jsx(
3364
+ ThumbCard,
3365
+ {
3366
+ id: w.id,
3367
+ label: w.label,
3368
+ maxW: cardSize.w,
3369
+ maxH: cardSize.h,
3370
+ titleAbove: true,
3371
+ onClick: () => onActivate(w.id),
3372
+ onClose: () => onClose(w.id)
3373
+ }
3374
+ ),
3375
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-white/80 truncate px-1", children: w.label })
3376
+ ] }, w.id)) })
3377
+ ] }),
3378
+ /* @__PURE__ */ jsx("div", { className: "shrink-0 px-3 py-3 flex justify-center", children: /* @__PURE__ */ jsx(
3379
+ "button",
3380
+ {
3381
+ onClick: onCloseAll,
3382
+ className: "px-5 py-2.5 rounded-full bg-white/15 backdrop-blur-md border border-white/25 text-white text-sm font-medium active:bg-white/25 shadow-lg",
3383
+ children: "Close All"
3384
+ }
3385
+ ) })
3386
+ ] });
3387
+ }
3388
+ function computeCardSize() {
3389
+ const w = typeof window !== "undefined" ? window.innerWidth : 360;
3390
+ const h = typeof window !== "undefined" ? window.innerHeight : 640;
3391
+ const cardW = Math.max(120, Math.floor((w - 36) / 2));
3392
+ const cardH = Math.min(cardW * 1.4, Math.floor(h * 0.4));
3393
+ return { w: cardW, h: cardH };
3394
+ }
3395
+ function timeAgo2(dateStr) {
3396
+ const diff = Date.now() - new Date(dateStr).getTime();
3397
+ const mins = Math.floor(diff / 6e4);
3398
+ if (mins < 1) return "just now";
3399
+ if (mins < 60) return `${mins}m ago`;
3400
+ const hrs = Math.floor(mins / 60);
3401
+ if (hrs < 24) return `${hrs}h ago`;
3402
+ const days = Math.floor(hrs / 24);
3403
+ if (days < 7) return `${days}d ago`;
3404
+ return formatDate(dateStr);
3405
+ }
3406
+ function MobileNotificationSheet({ config, onClose }) {
3407
+ const { list, markRead, markAllRead, onItemClick } = config;
3408
+ const queryClient = useQueryClient();
3409
+ const unreadCount = config.useUnreadCount();
3410
+ const { data: notifData } = useQuery({
3411
+ queryKey: ["notifications-dropdown"],
3412
+ queryFn: () => list({ page_size: 30 }),
3413
+ staleTime: 3e4
3414
+ });
3415
+ const notifications = notifData?.results ?? [];
3416
+ const handleClick = useCallback((notif) => {
3417
+ if (!notif.is_read) {
3418
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: Math.max(0, (old.count || 0) - 1) } : old);
3419
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
3420
+ if (!old?.results) return old;
3421
+ return { ...old, results: old.results.map((n) => n.id === notif.id ? { ...n, is_read: true } : n) };
3422
+ });
3423
+ markRead(notif.id).catch(() => {
3424
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
3425
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
3426
+ });
3427
+ }
3428
+ onClose();
3429
+ onItemClick(notif);
3430
+ }, [queryClient, markRead, onItemClick, onClose]);
3431
+ const handleMarkAllRead = () => {
3432
+ queryClient.setQueryData(["notification-unread-count"], (old) => old ? { ...old, count: 0 } : old);
3433
+ queryClient.setQueryData(["notifications-dropdown"], (old) => {
3434
+ if (!old?.results) return old;
3435
+ return { ...old, results: old.results.map((n) => ({ ...n, is_read: true })) };
3436
+ });
3437
+ markAllRead().then(() => {
3438
+ queryClient.invalidateQueries({ queryKey: ["notification-unread-count"] });
3439
+ queryClient.invalidateQueries({ queryKey: ["notifications-dropdown"] });
3440
+ });
3441
+ };
3442
+ return /* @__PURE__ */ jsxs(
3443
+ "div",
3444
+ {
3445
+ className: "fixed inset-0 z-[210] flex flex-col bg-white",
3446
+ style: { paddingBottom: "var(--mobile-bottom-nav, 70px)" },
3447
+ children: [
3448
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between px-3 py-3 border-b border-gray-200 shrink-0", children: [
3449
+ /* @__PURE__ */ jsx("button", { onClick: onClose, className: "p-2 -ml-1 rounded-full active:bg-gray-200 text-gray-700", "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-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" }) }) }),
3450
+ /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "Notifications" }),
3451
+ unreadCount > 0 ? /* @__PURE__ */ jsx("button", { onClick: handleMarkAllRead, className: "text-xs text-blue-600 font-medium px-2 py-1 active:bg-blue-50 rounded", children: "Mark all read" }) : /* @__PURE__ */ jsx("span", { className: "w-[88px]" })
3452
+ ] }),
3453
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: notifications.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 px-6 text-center", children: [
3454
+ /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-gray-300 mb-3", 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" }) }),
3455
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-700 font-medium", children: "All caught up" }),
3456
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-1", children: "No notifications yet" })
3457
+ ] }) : notifications.map((notif) => /* @__PURE__ */ jsxs(
3458
+ "button",
3459
+ {
3460
+ onClick: () => handleClick(notif),
3461
+ className: "w-full flex items-start gap-3 px-4 py-3 active:bg-gray-100 border-b border-gray-100 text-left",
3462
+ children: [
3463
+ /* @__PURE__ */ jsx("div", { className: "pt-1 shrink-0", children: !notif.is_read ? /* @__PURE__ */ jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-blue-500" }) : /* @__PURE__ */ jsx("div", { className: "h-2.5 w-2.5" }) }),
3464
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3465
+ /* @__PURE__ */ jsx("p", { className: `text-sm leading-snug ${!notif.is_read ? "font-medium text-gray-900" : "text-gray-700"}`, children: notif.title }),
3466
+ notif.message && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1 line-clamp-2", children: notif.message }),
3467
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1.5", children: [
3468
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-gray-400", children: timeAgo2(notif.created_at) }),
3469
+ notif.actor_name && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-gray-400", children: [
3470
+ "by ",
3471
+ notif.actor_name
3472
+ ] })
3473
+ ] })
3474
+ ] })
3475
+ ]
3476
+ },
3477
+ notif.id
3478
+ )) })
3479
+ ]
3480
+ }
3481
+ );
3482
+ }
3483
+ function MobileProfileSheet({ profile, user, onClose, onNavigate, onLogout }) {
3484
+ const initial = (profile?.first_name?.charAt(0) || user?.email?.charAt(0) || "?").toUpperCase();
3485
+ const displayName = profile?.first_name ? `${profile.first_name} ${profile.last_name || ""}`.trim() : user?.email ?? "Account";
3486
+ const groups = profile?.group_names ?? [];
3487
+ const handleNav = (path) => {
3488
+ onNavigate(path);
3489
+ onClose();
3490
+ };
3491
+ return /* @__PURE__ */ jsxs(
3492
+ "div",
3493
+ {
3494
+ className: "fixed inset-0 z-[210] flex flex-col bg-white",
3495
+ style: { paddingBottom: "var(--mobile-bottom-nav, 70px)" },
3496
+ children: [
3497
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between px-3 py-3 border-b border-gray-200 shrink-0", children: [
3498
+ /* @__PURE__ */ jsx("button", { onClick: onClose, className: "p-2 -ml-1 rounded-full active:bg-gray-200 text-gray-700", "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-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" }) }) }),
3499
+ /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "Profile" }),
3500
+ /* @__PURE__ */ jsx("span", { className: "w-10" })
3501
+ ] }),
3502
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
3503
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center px-6 py-8 border-b border-gray-100", children: [
3504
+ profile?.avatar_url ? /* @__PURE__ */ jsx("img", { src: profile.avatar_url, alt: "", className: "h-20 w-20 rounded-full object-cover border border-gray-200 shadow-sm" }) : /* @__PURE__ */ jsx("div", { className: "h-20 w-20 rounded-full bg-blue-100 flex items-center justify-center text-2xl font-bold text-blue-700 shadow-sm", children: initial }),
3505
+ /* @__PURE__ */ jsx("div", { className: "mt-3 text-base font-semibold text-gray-900 truncate max-w-full", children: displayName }),
3506
+ user?.email && profile?.first_name && /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 truncate max-w-full", children: user.email }),
3507
+ groups.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap justify-center gap-1.5", children: groups.map((g) => /* @__PURE__ */ jsx("span", { className: "text-[11px] px-2 py-0.5 rounded-full bg-gray-100 text-gray-700", children: g }, g)) })
3508
+ ] }),
3509
+ /* @__PURE__ */ jsxs("div", { className: "py-2", children: [
3510
+ /* @__PURE__ */ jsxs(
3511
+ "button",
3512
+ {
3513
+ onClick: () => handleNav("/customization"),
3514
+ className: "w-full flex items-center gap-3 px-4 py-4 active:bg-gray-100 border-b border-gray-100 text-left",
3515
+ children: [
3516
+ /* @__PURE__ */ jsx("span", { className: "h-9 w-9 rounded-lg bg-gray-100 flex items-center justify-center text-gray-700 shrink-0", children: /* @__PURE__ */ jsxs("svg", { className: "h-5 w-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
3517
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" }),
3518
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z" })
3519
+ ] }) }),
3520
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium text-gray-800", children: "Customization" }),
3521
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 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: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
3522
+ ]
3523
+ }
3524
+ ),
3525
+ /* @__PURE__ */ jsxs(
3526
+ "button",
3527
+ {
3528
+ onClick: () => {
3529
+ onLogout();
3530
+ onClose();
3531
+ },
3532
+ className: "w-full flex items-center gap-3 px-4 py-4 active:bg-red-50 text-left",
3533
+ children: [
3534
+ /* @__PURE__ */ jsx("span", { className: "h-9 w-9 rounded-lg bg-red-50 flex items-center justify-center text-red-600 shrink-0", 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.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" }) }) }),
3535
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium text-red-600", children: "Sign out" })
3536
+ ]
3537
+ }
3538
+ )
3539
+ ] })
3540
+ ] })
3541
+ ]
3542
+ }
3543
+ );
3544
+ }
3545
+ function MobileShell({
3546
+ navSections: navSections2,
3547
+ navIcons: navIcons2,
3548
+ sectionIcons: sectionIcons2,
3549
+ wallpaperStyle,
3550
+ notifications,
3551
+ profile,
3552
+ user,
3553
+ onNavigate,
3554
+ onLogout
3555
+ }) {
3556
+ const { openWindows, openPage, closeEntity } = useWindowManager();
3557
+ const mode = useSyncExternalStore(subscribeMobileMode, getMobileMode);
3558
+ const [sheet, setSheet] = useState(null);
3559
+ const unreadCount = notifications?.useUnreadCount() ?? 0;
3560
+ const switcherWindows = useMemo(() => {
3561
+ return openWindows.filter((w) => {
3562
+ if (!w.route) return true;
3563
+ const entry = WINDOW_REGISTRY[w.route];
3564
+ if (!entry || !isPageEntry(entry)) return true;
3565
+ return !entry.widget;
3566
+ });
3567
+ }, [openWindows]);
3568
+ const prevOpenCountRef = useRef(openWindows.length);
3569
+ useEffect(() => {
3570
+ if (openWindows.length === 0 && mode === "app") {
3571
+ setMobileMode("home");
3572
+ }
3573
+ prevOpenCountRef.current = openWindows.length;
3574
+ }, [openWindows.length, mode]);
3575
+ const activateWindowById = (windowId) => {
3576
+ const panel = document.querySelector(`[data-modal-panel][data-window-key="${windowId}"]`);
3577
+ const mid = panel?.getAttribute("data-modal-id");
3578
+ if (mid) activateModal(mid);
3579
+ };
3580
+ const handleOpenApp = (path) => {
3581
+ openPage(path);
3582
+ setMobileMode("app");
3583
+ };
3584
+ const handleActivateWindow = (id) => {
3585
+ activateWindowById(id);
3586
+ setMobileMode("app");
3587
+ };
3588
+ const closeSheet = () => setSheet(null);
3589
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3590
+ /* @__PURE__ */ jsx(
3591
+ "div",
3592
+ {
3593
+ className: "fixed inset-0 z-0 pointer-events-none",
3594
+ style: wallpaperStyle
3595
+ }
3596
+ ),
3597
+ mode === "home" && /* @__PURE__ */ jsx(
3598
+ "div",
3599
+ {
3600
+ className: "fixed inset-0 z-[200]",
3601
+ style: {
3602
+ ...wallpaperStyle,
3603
+ paddingBottom: "var(--mobile-bottom-nav, 70px)"
3604
+ },
3605
+ children: /* @__PURE__ */ jsx(
3606
+ MobileHome,
3607
+ {
3608
+ navSections: navSections2,
3609
+ navIcons: navIcons2,
3610
+ sectionIcons: sectionIcons2,
3611
+ openWindows,
3612
+ onOpenApp: handleOpenApp,
3613
+ onActivateWindow: handleActivateWindow
3614
+ }
3615
+ )
3616
+ }
3617
+ ),
3618
+ mode === "switcher" && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[200] bg-gray-900/95 backdrop-blur-sm", style: { paddingBottom: "var(--mobile-bottom-nav, 70px)" }, children: /* @__PURE__ */ jsx(
3619
+ MobileSwitcher,
3620
+ {
3621
+ windows: switcherWindows,
3622
+ onActivate: handleActivateWindow,
3623
+ onClose: (id) => closeEntity(id),
3624
+ onCloseAll: () => switcherWindows.forEach((w) => closeEntity(w.id))
3625
+ }
3626
+ ) }),
3627
+ sheet === "notifications" && notifications && /* @__PURE__ */ jsx(MobileNotificationSheet, { config: notifications, onClose: closeSheet }),
3628
+ sheet === "profile" && /* @__PURE__ */ jsx(
3629
+ MobileProfileSheet,
3630
+ {
3631
+ profile,
3632
+ user,
3633
+ onClose: closeSheet,
3634
+ onNavigate: (path) => {
3635
+ onNavigate?.(path);
3636
+ },
3637
+ onLogout: () => onLogout?.()
3638
+ }
3639
+ ),
3640
+ /* @__PURE__ */ jsx(
3641
+ MobileBottomNav,
3642
+ {
3643
+ mode,
3644
+ unreadCount,
3645
+ showNotifications: !!notifications,
3646
+ profileAvatar: profile?.avatar_url,
3647
+ profileInitial: (profile?.first_name?.charAt(0) || user?.email?.charAt(0) || "?").toUpperCase(),
3648
+ onHome: () => {
3649
+ closeSheet();
3650
+ setMobileMode("home");
3651
+ },
3652
+ onSwitcher: () => {
3653
+ closeSheet();
3654
+ setMobileMode("switcher");
3655
+ },
3656
+ onNotifications: () => setSheet(sheet === "notifications" ? null : "notifications"),
3657
+ onProfile: () => setSheet(sheet === "profile" ? null : "profile")
3658
+ }
3659
+ )
3660
+ ] });
3661
+ }
3662
+ function MobileBottomNav({
3663
+ mode,
3664
+ unreadCount,
3665
+ showNotifications,
3666
+ profileAvatar,
3667
+ profileInitial,
3668
+ onHome,
3669
+ onSwitcher,
3670
+ onNotifications,
3671
+ onProfile
3672
+ }) {
3673
+ const btnClass = (active) => `flex-1 flex flex-col items-center justify-center gap-0.5 py-2 transition-colors select-none ${active ? "text-blue-600" : "text-gray-700 active:text-gray-900"}`;
3674
+ return /* @__PURE__ */ jsxs(
3675
+ "nav",
3676
+ {
3677
+ className: "fixed bottom-0 inset-x-0 z-[300] flex items-stretch border-t border-white/40",
3678
+ style: {
3679
+ height: "var(--mobile-bottom-nav, 70px)",
3680
+ paddingBottom: "env(safe-area-inset-bottom)",
3681
+ WebkitBackdropFilter: "blur(28px) saturate(1.8)",
3682
+ backdropFilter: "blur(28px) saturate(1.8)",
3683
+ background: "linear-gradient(135deg, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0.45) 50%, rgba(255,255,255,0.55) 100%)",
3684
+ boxShadow: "inset 0 1px 0 rgba(255,255,255,0.6), 0 -2px 12px rgba(0,0,0,0.08)"
3685
+ },
3686
+ children: [
3687
+ /* @__PURE__ */ jsxs("button", { onClick: onHome, className: btnClass(mode === "home"), "aria-label": "Home", children: [
3688
+ /* @__PURE__ */ jsx("svg", { className: "h-8 w-8", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.7, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" }) }),
3689
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium", children: "Home" })
3690
+ ] }),
3691
+ /* @__PURE__ */ jsxs("button", { onClick: onSwitcher, className: btnClass(mode === "switcher"), "aria-label": "App switcher", children: [
3692
+ /* @__PURE__ */ jsxs("svg", { className: "h-8 w-8", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.7, children: [
3693
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "3.5", width: "7", height: "7", rx: "1.25" }),
3694
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "3.5", width: "7", height: "7", rx: "1.25" }),
3695
+ /* @__PURE__ */ jsx("rect", { x: "3.5", y: "13.5", width: "7", height: "7", rx: "1.25" }),
3696
+ /* @__PURE__ */ jsx("rect", { x: "13.5", y: "13.5", width: "7", height: "7", rx: "1.25" })
3697
+ ] }),
3698
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium", children: "Apps" })
3699
+ ] }),
3700
+ showNotifications && /* @__PURE__ */ jsxs("button", { onClick: onNotifications, className: btnClass(false), "aria-label": "Notifications", children: [
3701
+ /* @__PURE__ */ jsxs("span", { className: "relative", children: [
3702
+ /* @__PURE__ */ jsx("svg", { className: "h-8 w-8", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.7, 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" }) }),
3703
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -top-1.5 -right-2 min-w-[18px] h-[18px] px-1 rounded-full bg-red-500 text-white text-[10px] font-bold leading-[18px] text-center", children: unreadCount > 99 ? "99+" : unreadCount })
3704
+ ] }),
3705
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium", children: "Alerts" })
3706
+ ] }),
3707
+ /* @__PURE__ */ jsxs("button", { onClick: onProfile, className: btnClass(false), "aria-label": "Profile", children: [
3708
+ profileAvatar ? /* @__PURE__ */ jsx("img", { src: profileAvatar, alt: "", className: "h-8 w-8 rounded-full object-cover border border-gray-200" }) : /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center text-xs font-bold text-blue-700", children: profileInitial }),
3709
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium", children: "Profile" })
3710
+ ] })
3711
+ ]
3712
+ }
3713
+ );
3714
+ }
3715
+ function useFavorites(wallpapers) {
3716
+ const { prefs, save } = useShellPrefs();
3717
+ const favorites = prefs.favorite_pages || [];
3718
+ const toggle = useCallback((path) => {
3719
+ const next = favorites.includes(path) ? favorites.filter((p) => p !== path) : [...favorites, path];
3720
+ save({ favorite_pages: next });
3721
+ }, [favorites, save]);
3722
+ const wallpaperPool = wallpapers && wallpapers.length > 0 ? wallpapers : [];
3723
+ const randomPickRef = useRef(wallpaperPool.length > 0 ? wallpaperPool[Math.floor(Math.random() * wallpaperPool.length)] : "none");
3724
+ const rawBg = prefs.desktop_bg || (wallpaperPool.length > 0 ? "random" : "none");
3725
+ const desktopBg = rawBg === "random" ? randomPickRef.current : rawBg;
3726
+ const setDesktopBg = useCallback((bg) => {
3727
+ save({ desktop_bg: bg });
3728
+ }, [save]);
3729
+ return { favorites, toggle, isFavorite: (path) => favorites.includes(path), desktopBg, setDesktopBg };
3730
+ }
3731
+ function TaskbarPomodoro() {
3732
+ const snap = useSyncExternalStore(subscribePomo, getPomoSnapshot, getPomoSnapshot);
3733
+ const { openPage } = useWindowManager();
3734
+ const inSession = snap.running || snap.remaining < snap.total && snap.remaining > 0;
3735
+ if (!inSession) return null;
3736
+ const mm = String(Math.floor(snap.remaining / 60)).padStart(2, "0");
3737
+ const ss = String(snap.remaining % 60).padStart(2, "0");
3738
+ const dot = snap.mode === "focus" ? "bg-blue-500" : snap.mode === "short" ? "bg-emerald-500" : "bg-emerald-600";
3739
+ const label = snap.mode === "focus" ? "Focus" : snap.mode === "short" ? "Short break" : "Long break";
3740
+ return /* @__PURE__ */ jsxs(
3741
+ "button",
3742
+ {
3743
+ onClick: () => openPage("/pomodoro"),
3744
+ title: `${label} \xB7 ${mm}:${ss}${snap.running ? "" : " (paused)"}`,
3745
+ className: `shrink-0 flex items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-mono font-semibold tabular-nums hover:bg-gray-200/50 transition-colors ${snap.running ? "text-gray-800" : "text-gray-400"}`,
3746
+ children: [
3747
+ /* @__PURE__ */ jsx("span", { className: `block w-1.5 h-1.5 rounded-full ${dot} ${snap.running ? "animate-pulse" : "opacity-60"}` }),
3748
+ mm,
3749
+ ":",
3750
+ ss
3751
+ ]
3752
+ }
3753
+ );
3754
+ }
3755
+ function TaskbarClock() {
3756
+ const [now, setNow] = useState(/* @__PURE__ */ new Date());
3757
+ const [open, setOpen] = useState(false);
3758
+ const ref = useRef(null);
3759
+ const buttonRef = useRef(null);
3760
+ const { openPage } = useWindowManager();
3761
+ useEffect(() => {
3762
+ const t = setInterval(() => setNow(/* @__PURE__ */ new Date()), 1e4);
3763
+ return () => clearInterval(t);
3764
+ }, []);
3765
+ useClickOutside(ref, useCallback(() => {
3766
+ if (open) setOpen(false);
3767
+ }, [open]));
3768
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative shrink-0", children: [
3769
+ /* @__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: [
3770
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-gray-800", children: now.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) }),
3771
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-700", children: now.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" }) })
3772
+ ] }),
3773
+ open && (() => {
3774
+ const taskbarPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
3775
+ const rect = buttonRef.current?.getBoundingClientRect();
3776
+ const right = rect ? Math.max(8, window.innerWidth - rect.right) : 8;
3777
+ const posStyle = taskbarPos === "top" ? { right, top: (rect?.bottom ?? 0) + 4 } : { right, bottom: window.innerHeight - (rect?.top ?? 0) + 4 };
3778
+ return /* @__PURE__ */ jsx("div", { className: "fixed z-[300] rounded-lg border border-gray-200 bg-white shadow-xl", style: posStyle, children: /* @__PURE__ */ jsx(CalendarPopup, { now, onOpenCalendar: () => {
3779
+ openPage("/calendar");
3780
+ setOpen(false);
3781
+ } }) });
3782
+ })()
3783
+ ] });
3784
+ }
3785
+ function CalendarPopup({ now, onOpenCalendar }) {
3786
+ const [month, setMonth] = useState(() => new Date(now.getFullYear(), now.getMonth(), 1));
3787
+ const monthLabel = month.toLocaleDateString(void 0, { month: "long", year: "numeric" });
3788
+ const fullDate = now.toLocaleDateString(void 0, { weekday: "long", month: "long", day: "numeric" });
3789
+ const fullTime = now.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3790
+ const firstDay = month.getDay();
3791
+ const daysInMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate();
3792
+ const prevDays = new Date(month.getFullYear(), month.getMonth(), 0).getDate();
3793
+ const cells = [];
3794
+ for (let i = firstDay - 1; i >= 0; i--) {
3795
+ const day = prevDays - i;
3796
+ cells.push({ day, thisMonth: false, date: new Date(month.getFullYear(), month.getMonth() - 1, day) });
3797
+ }
3798
+ for (let day = 1; day <= daysInMonth; day++) {
3799
+ cells.push({ day, thisMonth: true, date: new Date(month.getFullYear(), month.getMonth(), day) });
3800
+ }
3801
+ let nextDay = 1;
3802
+ while (cells.length < 42) {
3803
+ cells.push({ day: nextDay, thisMonth: false, date: new Date(month.getFullYear(), month.getMonth() + 1, nextDay) });
3804
+ nextDay++;
3805
+ }
3806
+ const today = /* @__PURE__ */ new Date();
3807
+ const isToday = (d) => d.getFullYear() === today.getFullYear() && d.getMonth() === today.getMonth() && d.getDate() === today.getDate();
3808
+ const goPrev = () => setMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1));
3809
+ const goNext = () => setMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1));
3810
+ const goToday = () => setMonth(new Date(today.getFullYear(), today.getMonth(), 1));
3811
+ return /* @__PURE__ */ jsxs("div", { className: "w-[260px] p-3", children: [
3812
+ /* @__PURE__ */ jsxs("div", { className: "px-1 pb-2.5 border-b border-gray-100", children: [
3813
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold text-gray-500 uppercase tracking-wide", children: fullDate }),
3814
+ /* @__PURE__ */ jsx("div", { className: "text-2xl font-semibold text-gray-800 tabular-nums leading-tight mt-0.5", children: fullTime })
3815
+ ] }),
3816
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-2.5 mb-2", children: [
3817
+ /* @__PURE__ */ jsx("button", { onClick: goPrev, className: "p-1 rounded hover:bg-gray-100 text-gray-600", "aria-label": "Previous month", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 19l-7-7 7-7" }) }) }),
3818
+ /* @__PURE__ */ jsx("button", { onClick: goToday, className: "text-sm font-semibold text-gray-700 hover:text-blue-600 transition-colors", children: monthLabel }),
3819
+ /* @__PURE__ */ jsx("button", { onClick: goNext, className: "p-1 rounded hover:bg-gray-100 text-gray-600", "aria-label": "Next month", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) }) })
3820
+ ] }),
3821
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 mb-1 text-[10px] font-semibold text-gray-400 text-center uppercase tracking-wide", children: ["S", "M", "T", "W", "T", "F", "S"].map((d, i) => /* @__PURE__ */ jsx("div", { className: "py-1", children: d }, i)) }),
3822
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-px text-center", children: cells.map((cell, i) => {
3823
+ const todayCell = cell.thisMonth && isToday(cell.date);
3824
+ return /* @__PURE__ */ jsx(
3825
+ "div",
3826
+ {
3827
+ className: `text-[12px] py-1.5 rounded-md transition-colors ${todayCell ? "bg-blue-500 text-white font-semibold" : cell.thisMonth ? "text-gray-700 hover:bg-gray-100" : "text-gray-300"}`,
3828
+ children: cell.day
3829
+ },
3830
+ i
3831
+ );
3832
+ }) }),
3833
+ /* @__PURE__ */ jsx("div", { className: "mt-2 pt-2 border-t border-gray-100", children: /* @__PURE__ */ jsxs(
3834
+ "button",
3835
+ {
3836
+ onClick: onOpenCalendar,
3837
+ className: "w-full text-[11px] font-medium text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded py-1 transition-colors flex items-center justify-center gap-1.5",
3838
+ children: [
3839
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" }) }),
3840
+ "Open Calendar"
3841
+ ]
3842
+ }
3843
+ ) })
3844
+ ] });
3845
+ }
3846
+ function TaskbarContextMenu({ x, y, position, size, onChangePosition, onChangeSize, onClose, onReportBug }) {
3847
+ const ref = useRef(null);
3848
+ useClickOutside(ref, onClose);
3849
+ 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 };
3850
+ 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" });
3851
+ return /* @__PURE__ */ jsxs("div", { ref, className: "fixed z-[400] rounded-2xl py-1 min-w-[180px]", style: { ...posStyle, ...glassStyle() }, children: [
3852
+ /* @__PURE__ */ jsx("p", { className: "px-3 py-1 text-[10px] font-semibold text-gray-400 uppercase tracking-wide", children: "Position" }),
3853
+ ["bottom", "top", "left", "right"].map((pos) => /* @__PURE__ */ jsxs(
3854
+ "button",
3855
+ {
3856
+ onClick: () => onChangePosition(pos),
3857
+ 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",
3858
+ style: { width: "calc(100% - 8px)" },
3859
+ children: [
3860
+ check(position === pos),
3861
+ pos.charAt(0).toUpperCase() + pos.slice(1)
3862
+ ]
3863
+ },
3864
+ pos
3865
+ )),
3866
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1 mx-3" }),
3867
+ /* @__PURE__ */ jsx("p", { className: "px-3 py-1 text-[10px] font-semibold text-gray-400 uppercase tracking-wide", children: "Size" }),
3868
+ ["small", "medium", "large"].map((s) => /* @__PURE__ */ jsxs(
3869
+ "button",
3870
+ {
3871
+ onClick: () => onChangeSize(s),
3872
+ 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",
3873
+ style: { width: "calc(100% - 8px)" },
3874
+ children: [
3875
+ check(size === s),
3876
+ s.charAt(0).toUpperCase() + s.slice(1)
3877
+ ]
3878
+ },
3879
+ s
3880
+ )),
3881
+ onReportBug && /* @__PURE__ */ jsxs(Fragment, { children: [
3882
+ /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1 mx-3" }),
3883
+ /* @__PURE__ */ jsxs(
3884
+ "button",
3885
+ {
3886
+ onClick: () => {
3887
+ onClose();
3888
+ onReportBug();
3889
+ },
3890
+ 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",
3891
+ style: { width: "calc(100% - 8px)" },
3892
+ children: [
3893
+ /* @__PURE__ */ jsx("span", { className: "w-3.5 shrink-0" }),
3894
+ "Suggestion or Bug"
3895
+ ]
3896
+ }
3897
+ )
3898
+ ] })
3899
+ ] });
3900
+ }
3901
+ function Layout({
3902
+ productName = "react-os-shell",
3903
+ productIcon = "/favicon.svg",
3904
+ wallpapers,
3905
+ navSections: navSections2 = navSections,
3906
+ navIcons: navIcons2 = navIcons,
3907
+ sectionIcons: sectionIcons2 = sectionIcons,
3908
+ categories = startMenuCategories,
3909
+ notifications,
3910
+ search
3911
+ } = {}) {
3912
+ const bugReport = useBugReport();
3913
+ const { user, logout} = useAuth();
3914
+ const { openPage, openEntity, openWindows } = useWindowManager();
3915
+ const [menuOpen, setMenuOpen] = useState(false);
3916
+ useEmailUnreadCount();
3917
+ const isMobile = useIsMobile();
3918
+ const profile = user || {};
3919
+ useTheme();
3920
+ const { desktopBg} = useFavorites(wallpapers);
3921
+ const { prefs, save: savePrefs } = useShellPrefs();
3922
+ const taskbarPosition = prefs.taskbar_position || "bottom";
3923
+ const taskbarSize = prefs.taskbar_size || "medium";
3924
+ const desktopDblClick = prefs.desktop_dblclick || "deactivate";
3925
+ const isVerticalTaskbar = taskbarPosition === "left" || taskbarPosition === "right";
3926
+ const taskbarH = isVerticalTaskbar ? 0 : taskbarSize === "small" ? 40 : taskbarSize === "large" ? 72 : 56;
3927
+ const taskbarW = isVerticalTaskbar ? taskbarSize === "small" ? 180 : taskbarSize === "large" ? 260 : 220 : 0;
3928
+ const taskbarHClass = taskbarSize === "small" ? "h-10" : taskbarSize === "large" ? "h-[72px]" : "h-14";
3929
+ const taskbarWClass = taskbarSize === "small" ? "w-[180px]" : taskbarSize === "large" ? "w-[260px]" : "w-[220px]";
3930
+ const taskbarOpacity = (prefs.transparency_taskbar ?? 70) / 100;
3931
+ const menuOpacity = (prefs.transparency_start_menu ?? 95) / 100;
3932
+ const inactiveHeaderOpacity = (prefs.transparency_inactive_header ?? 70) / 100;
3933
+ const inactiveContentOpacity = (prefs.transparency_inactive_content ?? 80) / 100;
3934
+ const activeHeaderOpacity = (prefs.transparency_active_header ?? 80) / 100;
3935
+ const activeContentOpacity = (prefs.transparency_active_content ?? 90) / 100;
3936
+ useEffect(() => {
3937
+ const root = document.documentElement;
3938
+ root.style.setProperty("--inactive-header-opacity", String(inactiveHeaderOpacity));
3939
+ root.style.setProperty("--inactive-content-opacity", String(inactiveContentOpacity));
3940
+ root.style.setProperty("--active-header-opacity", String(activeHeaderOpacity));
3941
+ root.style.setProperty("--active-content-opacity", String(activeContentOpacity));
3942
+ root.style.setProperty("--menu-opacity", String(menuOpacity));
3943
+ root.style.setProperty("--taskbar-height", String(taskbarH));
3944
+ root.style.setProperty("--taskbar-width", String(taskbarW));
3945
+ root.style.setProperty("--taskbar-position", taskbarPosition);
3946
+ root.style.setProperty("--default-window-size", prefs.default_window_size || "large");
3947
+ root.style.setProperty("--window-position", prefs.window_position || "cascade");
3948
+ root.style.setProperty("--menu-density", prefs.menu_density || "normal");
3949
+ const menuSize = prefs.start_menu_size || "medium";
3950
+ const sizeVars = {
3951
+ small: { font: "12px", px: "0.75rem", py: "0.25rem", tabW: "150px", tabFont: "11px" },
3952
+ medium: { font: "14px", px: "1rem", py: "0.5rem", tabW: "200px", tabFont: "12px" },
3953
+ large: { font: "15px", px: "1.125rem", py: "0.625rem", tabW: "240px", tabFont: "13px" }
3954
+ };
3955
+ const sv = sizeVars[menuSize] || sizeVars.medium;
3956
+ root.style.setProperty("--menu-font-size", sv.font);
3957
+ root.style.setProperty("--menu-padding-x", sv.px);
3958
+ root.style.setProperty("--menu-padding-y", sv.py);
3959
+ root.style.setProperty("--window-tab-width", sv.tabW);
3960
+ root.style.setProperty("--window-tab-font-size", sv.tabFont);
3961
+ }, [inactiveHeaderOpacity, inactiveContentOpacity, activeHeaderOpacity, activeContentOpacity, taskbarH, taskbarPosition, prefs.default_window_size, prefs.window_position, prefs.menu_density, prefs.start_menu_size]);
3962
+ useEffect(() => {
3963
+ document.documentElement.style.setProperty("--mobile-bottom-nav", isMobile ? "100px" : "0px");
3964
+ }, [isMobile]);
3965
+ const [balloonDismissed, setBalloonDismissed] = useState(false);
3966
+ const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement);
3967
+ const [taskbarMenu, setTaskbarMenu] = useState(null);
3968
+ const [googleConnectOpen, setGoogleConnectOpen] = useState(false);
3969
+ const { isConnected: googleConnected } = useGoogleAuth();
3970
+ const [showLogout, setShowLogout] = useState(false);
3971
+ useEffect(() => {
3972
+ const handler = () => setGoogleConnectOpen(true);
3973
+ window.addEventListener("open-google-connect", handler);
3974
+ return () => window.removeEventListener("open-google-connect", handler);
3975
+ }, []);
3976
+ const savePref = useCallback((key, value) => {
3977
+ savePrefs({ [key]: value });
3978
+ }, [savePrefs]);
3979
+ const toggleFullscreen = useCallback(async () => {
3980
+ if (document.fullscreenElement) {
3981
+ if (navigator.keyboard?.unlock) navigator.keyboard.unlock();
3982
+ document.exitFullscreen();
3983
+ } else {
3984
+ await document.documentElement.requestFullscreen();
3985
+ if (navigator.keyboard?.lock) {
3986
+ try {
3987
+ await navigator.keyboard.lock(["Escape"]);
3988
+ } catch {
3989
+ }
3990
+ }
3991
+ }
3992
+ }, []);
3993
+ useEffect(() => {
3994
+ const handler = () => {
3995
+ setIsFullscreen(!!document.fullscreenElement);
3996
+ if (!document.fullscreenElement && navigator.keyboard?.unlock) {
3997
+ navigator.keyboard.unlock();
3998
+ }
3999
+ };
4000
+ document.addEventListener("fullscreenchange", handler);
4001
+ return () => document.removeEventListener("fullscreenchange", handler);
4002
+ }, []);
4003
+ const autoFullscreenDone = useRef(false);
4004
+ useEffect(() => {
4005
+ if (autoFullscreenDone.current) return;
4006
+ if (prefs.auto_fullscreen && !document.fullscreenElement) {
4007
+ autoFullscreenDone.current = true;
4008
+ const handler = async () => {
4009
+ if (!document.fullscreenElement) {
4010
+ try {
4011
+ await document.documentElement.requestFullscreen();
4012
+ if (navigator.keyboard?.lock) navigator.keyboard.lock(["Escape"]).catch(() => {
4013
+ });
4014
+ } catch {
4015
+ }
4016
+ }
4017
+ document.removeEventListener("click", handler);
4018
+ };
4019
+ document.addEventListener("click", handler, { once: true });
4020
+ }
4021
+ }, [prefs.auto_fullscreen]);
4022
+ const [showStartup, setShowStartup] = useState(false);
4023
+ const startupChecked = useRef(false);
4024
+ useEffect(() => {
4025
+ if (startupChecked.current) return;
4026
+ startupChecked.current = true;
4027
+ const key = "erp_startup_shown";
4028
+ if (!sessionStorage.getItem(key)) {
4029
+ sessionStorage.setItem(key, "1");
4030
+ setShowStartup(true);
4031
+ }
4032
+ }, []);
4033
+ const startupSoundDone = useRef(false);
4034
+ useEffect(() => {
4035
+ if (startupSoundDone.current || !showStartup) return;
4036
+ startupSoundDone.current = true;
4037
+ setTimeout(() => playStartup(), 500);
4038
+ }, [showStartup]);
4039
+ useEffect(() => {
4040
+ const handler = (e) => {
4041
+ if ((e.ctrlKey || e.metaKey) && e.key === ".") {
4042
+ e.preventDefault();
4043
+ setMenuOpen((prev) => !prev);
4044
+ }
4045
+ if (e.ctrlKey && e.key === "F11") {
4046
+ e.preventDefault();
4047
+ toggleFullscreen();
4048
+ }
4049
+ if (e.key === "Escape" && document.fullscreenElement) {
4050
+ const hasOpenModals = document.querySelectorAll("[data-modal-panel]").length > 0;
4051
+ if (!hasOpenModals) {
4052
+ e.preventDefault();
4053
+ toggleFullscreen();
4054
+ }
4055
+ }
4056
+ };
4057
+ window.addEventListener("keydown", handler);
4058
+ return () => {
4059
+ window.removeEventListener("keydown", handler);
4060
+ };
4061
+ }, [toggleFullscreen]);
4062
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-screen", children: [
4063
+ showStartup && /* @__PURE__ */ jsx(StartupAnimation, { onComplete: () => setShowStartup(false), ready: !!profile, productName }),
4064
+ showLogout && /* @__PURE__ */ jsx(LogoutAnimation, { onComplete: () => {
4065
+ sessionStorage.removeItem("erp_startup_shown");
4066
+ logout();
4067
+ } }),
4068
+ /* @__PURE__ */ jsx(
4069
+ StartMenu,
4070
+ {
4071
+ open: menuOpen,
4072
+ onClose: () => setMenuOpen(false),
4073
+ openPage: (path) => {
4074
+ openPage(path);
4075
+ setMenuOpen(false);
4076
+ },
4077
+ openWindows,
4078
+ profile,
4079
+ user,
4080
+ onLogout: () => setShowLogout(true),
4081
+ onNavigate: (path) => {
4082
+ openPage(path);
4083
+ setMenuOpen(false);
4084
+ },
4085
+ taskbarPosition,
4086
+ taskbarH,
4087
+ taskbarW,
4088
+ size: prefs.start_menu_size || "medium",
4089
+ navSections: navSections2,
4090
+ navIcons: navIcons2,
4091
+ sectionIcons: sectionIcons2,
4092
+ categories
4093
+ }
4094
+ ),
4095
+ (() => {
4096
+ const wallpaperStyle = {
4097
+ backgroundColor: desktopBg?.startsWith("#") ? desktopBg : desktopBg === "none" ? (() => {
4098
+ const customBg = getComputedStyle(document.documentElement).getPropertyValue("--custom-bg-color")?.trim();
4099
+ if (customBg) return customBg;
4100
+ const t = document.documentElement.getAttribute("data-theme") || "light";
4101
+ const map = { light: "#f3f4f6", dark: "#1e1e2e", pink: "#fdf2f8", green: "#f0fdf4", grey: "#d1d5db", blue: "#eff6ff" };
4102
+ return map[t] || "#f3f4f6";
4103
+ })() : "#1a1a2e",
4104
+ backgroundImage: desktopBg && desktopBg !== "none" && !desktopBg.startsWith("#") ? `url(${desktopBg})` : "none",
4105
+ backgroundSize: "cover",
4106
+ backgroundPosition: "center",
4107
+ backgroundRepeat: "no-repeat"
4108
+ };
4109
+ if (isMobile) {
4110
+ return /* @__PURE__ */ jsx(
4111
+ MobileShell,
4112
+ {
4113
+ productName,
4114
+ productIcon,
4115
+ navSections: navSections2,
4116
+ navIcons: navIcons2,
4117
+ sectionIcons: sectionIcons2,
4118
+ wallpaperStyle,
4119
+ notifications,
4120
+ profile,
4121
+ user,
4122
+ onNavigate: (path) => openPage(path),
4123
+ onLogout: () => setShowLogout(true)
4124
+ }
4125
+ );
4126
+ }
4127
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4128
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 min-h-0", children: /* @__PURE__ */ jsx(
4129
+ "main",
4130
+ {
4131
+ className: "flex-1 flex flex-col overflow-hidden cursor-default",
4132
+ onDoubleClick: () => {
4133
+ if (desktopDblClick === "deactivate") {
4134
+ setMenuOpen(false);
4135
+ window.dispatchEvent(new CustomEvent("deactivate-all-modals"));
4136
+ }
4137
+ },
4138
+ style: wallpaperStyle,
4139
+ children: /* @__PURE__ */ jsx(Desktop, { profile })
4140
+ }
4141
+ ) }),
4142
+ /* @__PURE__ */ jsxs(
4143
+ "div",
4144
+ {
4145
+ 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"}`}`,
4146
+ style: { backgroundColor: `rgb(var(--taskbar-bg-rgb, 243 244 246) / ${taskbarOpacity})` },
4147
+ onContextMenu: (e) => {
4148
+ e.preventDefault();
4149
+ setTaskbarMenu({ x: e.clientX, y: e.clientY });
4150
+ },
4151
+ children: [
4152
+ /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
4153
+ openWindows.length === 0 && !menuOpen && !balloonDismissed && /* @__PURE__ */ jsxs(
4154
+ "div",
4155
+ {
4156
+ 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"}`,
4157
+ style: { backgroundColor: "var(--accent-600, #7c3aed)" },
4158
+ children: [
4159
+ "Click here to start",
4160
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
4161
+ e.stopPropagation();
4162
+ setBalloonDismissed(true);
4163
+ }, 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" }) }) }),
4164
+ /* @__PURE__ */ jsx(
4165
+ "div",
4166
+ {
4167
+ 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)]"}`,
4168
+ style: taskbarPosition === "top" ? { borderBottomColor: "var(--accent-600, #7c3aed)" } : { borderTopColor: "var(--accent-600, #7c3aed)" }
4169
+ }
4170
+ )
4171
+ ]
4172
+ }
4173
+ ),
4174
+ /* @__PURE__ */ jsxs(
4175
+ "button",
4176
+ {
4177
+ "data-menu-toggle": true,
4178
+ onClick: () => setMenuOpen((prev) => !prev),
4179
+ title: menuOpen ? "Close menu" : "Open menu",
4180
+ 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"}`,
4181
+ style: { transition: "box-shadow 0.3s, border-color 0.3s" },
4182
+ onMouseMove: (e) => {
4183
+ const r = e.currentTarget.getBoundingClientRect();
4184
+ e.currentTarget.style.setProperty("--mx", `${e.clientX - r.left}px`);
4185
+ e.currentTarget.style.setProperty("--my", `${e.clientY - r.top}px`);
4186
+ },
4187
+ onMouseEnter: (e) => {
4188
+ e.currentTarget.style.boxShadow = "0 0 15px rgba(255,255,255,0.2), 0 0 30px rgba(255,255,255,0.1)";
4189
+ e.currentTarget.style.borderColor = "rgba(255,255,255,0.4)";
4190
+ },
4191
+ onMouseLeave: (e) => {
4192
+ e.currentTarget.style.boxShadow = "";
4193
+ e.currentTarget.style.borderColor = "";
4194
+ },
4195
+ children: [
4196
+ /* @__PURE__ */ jsx(
4197
+ "span",
4198
+ {
4199
+ className: "absolute inset-0 opacity-0 group-hover/erp:opacity-100 transition-opacity duration-200 pointer-events-none",
4200
+ style: { background: "radial-gradient(circle 60px at var(--mx, 50%) var(--my, 50%), rgba(255,255,255,0.25) 0%, transparent 100%)" }
4201
+ }
4202
+ ),
4203
+ productIcon && /* @__PURE__ */ jsx("img", { src: productIcon, alt: "", className: "relative z-10 h-3.5 w-3.5 shrink-0 opacity-60" }),
4204
+ /* @__PURE__ */ jsx("span", { className: "relative z-10 truncate", children: productName })
4205
+ ]
4206
+ }
4207
+ )
4208
+ ] }),
4209
+ /* @__PURE__ */ jsx("div", { className: isVerticalTaskbar ? "h-px w-6 bg-gray-300 my-1" : "w-px h-6 bg-gray-300 mx-1" }),
4210
+ /* @__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"}` }),
4211
+ /* @__PURE__ */ jsx("div", { className: isVerticalTaskbar ? "h-px w-6 bg-gray-300 my-1" : "w-px h-6 bg-gray-300 mx-1" }),
4212
+ isVerticalTaskbar ? (
4213
+ /* Vertical: clock + bell + google evenly spaced in a row */
4214
+ /* @__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: [
4215
+ /* @__PURE__ */ jsx(TaskbarClock, {}),
4216
+ /* @__PURE__ */ jsx(
4217
+ "button",
4218
+ {
4219
+ onClick: () => setGoogleConnectOpen(true),
4220
+ title: googleConnected ? "Google Connected" : "Connect Google",
4221
+ 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"}`,
4222
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", children: [
4223
+ /* @__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" }),
4224
+ /* @__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" }),
4225
+ /* @__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" }),
4226
+ /* @__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" })
4227
+ ] })
4228
+ }
4229
+ ),
4230
+ /* @__PURE__ */ jsx(TaskbarPomodoro, {}),
4231
+ notifications && /* @__PURE__ */ jsx(NotificationBell, { ...notifications, popDirection: taskbarPosition === "right" ? "left" : "right" })
4232
+ ] }) })
4233
+ ) : (
4234
+ /* Horizontal: icons then clock */
4235
+ /* @__PURE__ */ jsxs(Fragment, { children: [
4236
+ /* @__PURE__ */ jsx(TaskbarPomodoro, {}),
4237
+ notifications && /* @__PURE__ */ jsx(NotificationBell, { ...notifications }),
4238
+ /* @__PURE__ */ jsx(
4239
+ "button",
4240
+ {
4241
+ onClick: () => setGoogleConnectOpen(true),
4242
+ title: googleConnected ? "Google Connected" : "Connect Google",
4243
+ 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"}`,
4244
+ children: /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", children: [
4245
+ /* @__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" }),
4246
+ /* @__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" }),
4247
+ /* @__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" }),
4248
+ /* @__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" })
4249
+ ] })
4250
+ }
4251
+ ),
4252
+ /* @__PURE__ */ jsx(TaskbarClock, {})
4253
+ ] })
4254
+ )
4255
+ ]
4256
+ }
4257
+ )
4258
+ ] });
4259
+ })(),
4260
+ taskbarMenu && /* @__PURE__ */ jsx(
4261
+ TaskbarContextMenu,
4262
+ {
4263
+ x: taskbarMenu.x,
4264
+ y: taskbarMenu.y,
4265
+ position: taskbarPosition,
4266
+ size: taskbarSize,
4267
+ onChangePosition: (v) => {
4268
+ savePref("taskbar_position", v);
4269
+ setTaskbarMenu(null);
4270
+ },
4271
+ onChangeSize: (v) => {
4272
+ savePref("taskbar_size", v);
4273
+ setTaskbarMenu(null);
4274
+ },
4275
+ onClose: () => setTaskbarMenu(null),
4276
+ onReportBug: bugReport ? () => reportBug(bugReport.submit) : void 0
4277
+ }
4278
+ ),
4279
+ /* @__PURE__ */ jsx(GlobalSearch, { ...search }),
4280
+ /* @__PURE__ */ jsx(ShortcutHelp, {}),
4281
+ /* @__PURE__ */ jsx(GoogleConnectModal, { open: googleConnectOpen, onClose: () => setGoogleConnectOpen(false) })
4282
+ ] });
4283
+ }
206
4284
  var THEMES = [
207
4285
  { key: "system", label: "System", bar1: "bg-[#e5e7eb]", bar2: "bg-[#e5e7eb]" },
208
4286
  { key: "light", label: "Light", bar1: "bg-[#e5e7eb]", bar2: "bg-[#e5e7eb]" },
@@ -725,6 +4803,6 @@ function useEditHotkey(callback) {
725
4803
  }, [callback, isActive]);
726
4804
  }
727
4805
 
728
- export { BugReportDetail, Customization, ShellEntityFetcherProvider, StatusBadge, StatusBadgeProvider, createWindowRegistry, useEditHotkey, useNewHotkey, useShellEntityFetcher };
4806
+ 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, VERSION, createWindowRegistry, isMac, openBugReportDialog, reportBug, setShellAuthBridge, useBugReport, useClickOutside, useDesktopHost, useEditHotkey, useNewHotkey, useShellAuth, useShellEntityFetcher };
729
4807
  //# sourceMappingURL=index.js.map
730
4808
  //# sourceMappingURL=index.js.map