plusui-native-core 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/Core/CMakeLists.txt +190 -7
  2. package/Core/Features/App/app.cpp +129 -0
  3. package/Core/Features/App/app.ts +126 -0
  4. package/Core/Features/Browser/browser.cpp +181 -0
  5. package/Core/Features/Browser/browser.ts +182 -0
  6. package/Core/Features/Clipboard/clipboard.cpp +234 -0
  7. package/Core/Features/Clipboard/clipboard.ts +113 -0
  8. package/Core/Features/Display/display.cpp +209 -0
  9. package/Core/Features/Display/display.ts +104 -0
  10. package/Core/Features/Event/Events.ts +166 -0
  11. package/Core/Features/Event/events.cpp +200 -0
  12. package/Core/Features/Keyboard/keyboard.cpp +186 -0
  13. package/Core/Features/Keyboard/keyboard.ts +175 -0
  14. package/Core/Features/Menu/context-menu.css +293 -0
  15. package/Core/Features/Menu/menu.cpp +481 -0
  16. package/Core/Features/Menu/menu.ts +439 -0
  17. package/Core/Features/Tray/tray.cpp +310 -0
  18. package/Core/Features/Tray/tray.ts +68 -0
  19. package/Core/Features/WebGPU/webgpu.cpp +939 -0
  20. package/Core/Features/WebGPU/webgpu.ts +1013 -0
  21. package/Core/Features/WebView/webview.cpp +1052 -0
  22. package/Core/Features/WebView/webview.ts +510 -0
  23. package/Core/Features/Window/window.cpp +664 -0
  24. package/Core/Features/Window/window.ts +142 -0
  25. package/Core/Features/WindowManager/window_manager.cpp +341 -0
  26. package/Core/include/plusui/app.hpp +73 -0
  27. package/Core/include/plusui/browser.hpp +66 -0
  28. package/Core/include/plusui/clipboard.hpp +41 -0
  29. package/Core/include/plusui/events.hpp +58 -0
  30. package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
  31. package/Core/include/plusui/menu.hpp +153 -0
  32. package/Core/include/plusui/tray.hpp +93 -0
  33. package/Core/include/plusui/webgpu.hpp +434 -0
  34. package/Core/include/plusui/webview.hpp +142 -0
  35. package/Core/include/plusui/window.hpp +111 -0
  36. package/Core/include/plusui/window_manager.hpp +57 -0
  37. package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
  38. package/Core/vendor/stb_image.h +7988 -0
  39. package/Core/vendor/webview.h +618 -510
  40. package/Core/vendor/webview2.h +52079 -0
  41. package/README.md +19 -0
  42. package/package.json +12 -15
  43. package/Core/include/app.hpp +0 -121
  44. package/Core/include/menu.hpp +0 -79
  45. package/Core/include/tray.hpp +0 -81
  46. package/Core/include/window.hpp +0 -106
  47. package/Core/src/app.cpp +0 -311
  48. package/Core/src/display.cpp +0 -424
  49. package/Core/src/tray.cpp +0 -275
  50. package/Core/src/window.cpp +0 -528
  51. package/dist/index.d.ts +0 -205
  52. package/dist/index.js +0 -198
  53. package/src/index.ts +0 -574
  54. /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
@@ -0,0 +1,186 @@
1
+ #include <iostream>
2
+ #include <plusui/keyboard.hpp>
3
+ #include <unordered_map>
4
+
5
+ namespace plusui {
6
+
7
+ struct Keyboard::Impl {
8
+ std::unordered_map<int, bool> pressedKeys;
9
+ std::vector<std::function<void(const KeyEvent &)>> keyDownCallbacks;
10
+ std::vector<std::function<void(const KeyEvent &)>> keyUpCallbacks;
11
+ bool autoRepeat = true;
12
+
13
+ static const std::unordered_map<KeyCode, std::string> &keyNames() {
14
+ static std::unordered_map<KeyCode, std::string> names = {
15
+ {KeyCode::Space, "Space"},
16
+ {KeyCode::Escape, "Escape"},
17
+ {KeyCode::Enter, "Enter"},
18
+ {KeyCode::Tab, "Tab"},
19
+ {KeyCode::Backspace, "Backspace"},
20
+ {KeyCode::Delete, "Delete"},
21
+ {KeyCode::Right, "Right"},
22
+ {KeyCode::Left, "Left"},
23
+ {KeyCode::Down, "Down"},
24
+ {KeyCode::Up, "Up"},
25
+ {KeyCode::A, "A"},
26
+ {KeyCode::B, "B"},
27
+ {KeyCode::C, "C"},
28
+ {KeyCode::D, "D"},
29
+ {KeyCode::E, "E"},
30
+ {KeyCode::F, "F"},
31
+ {KeyCode::G, "G"},
32
+ {KeyCode::H, "H"},
33
+ {KeyCode::I, "I"},
34
+ {KeyCode::J, "J"},
35
+ {KeyCode::K, "K"},
36
+ {KeyCode::L, "L"},
37
+ {KeyCode::M, "M"},
38
+ {KeyCode::N, "N"},
39
+ {KeyCode::O, "O"},
40
+ {KeyCode::P, "P"},
41
+ {KeyCode::Q, "Q"},
42
+ {KeyCode::R, "R"},
43
+ {KeyCode::S, "S"},
44
+ {KeyCode::T, "T"},
45
+ {KeyCode::U, "U"},
46
+ {KeyCode::V, "V"},
47
+ {KeyCode::W, "W"},
48
+ {KeyCode::X, "X"},
49
+ {KeyCode::Y, "Y"},
50
+ {KeyCode::Z, "Z"},
51
+ {KeyCode::F1, "F1"},
52
+ {KeyCode::F2, "F2"},
53
+ {KeyCode::F3, "F3"},
54
+ {KeyCode::F4, "F4"},
55
+ {KeyCode::F5, "F5"},
56
+ {KeyCode::F6, "F6"},
57
+ {KeyCode::F7, "F7"},
58
+ {KeyCode::F8, "F8"},
59
+ {KeyCode::F9, "F9"},
60
+ {KeyCode::F10, "F10"},
61
+ {KeyCode::F11, "F11"},
62
+ {KeyCode::F12, "F12"},
63
+ };
64
+ return names;
65
+ }
66
+ };
67
+
68
+ Keyboard::Keyboard() : pImpl(std::make_unique<Impl>()) {}
69
+ Keyboard::~Keyboard() = default;
70
+
71
+ Keyboard &Keyboard::instance() {
72
+ static Keyboard instance;
73
+ return instance;
74
+ }
75
+
76
+ void Keyboard::onKeyDown(std::function<void(const KeyEvent &)> callback) {
77
+ pImpl->keyDownCallbacks.push_back(callback);
78
+ }
79
+
80
+ void Keyboard::onKeyUp(std::function<void(const KeyEvent &)> callback) {
81
+ pImpl->keyUpCallbacks.push_back(callback);
82
+ }
83
+
84
+ bool Keyboard::isKeyPressed(KeyCode key) const {
85
+ return pImpl->pressedKeys.count(static_cast<int>(key)) &&
86
+ pImpl->pressedKeys.at(static_cast<int>(key));
87
+ }
88
+
89
+ bool Keyboard::areModsActive(KeyMod mods) const { return false; }
90
+
91
+ std::string Keyboard::getKeyName(KeyCode key) const {
92
+ auto it = Impl::keyNames().find(key);
93
+ return it != Impl::keyNames().end() ? it->second : "Unknown";
94
+ }
95
+
96
+ KeyCode Keyboard::getKeyFromName(const std::string &name) const {
97
+ for (const auto &[key, keyName] : Impl::keyNames()) {
98
+ if (keyName == name)
99
+ return key;
100
+ }
101
+ return KeyCode::Unknown;
102
+ }
103
+
104
+ void Keyboard::setAutoRepeat(bool enabled) { pImpl->autoRepeat = enabled; }
105
+
106
+ bool Keyboard::getAutoRepeat() const { return pImpl->autoRepeat; }
107
+
108
+ struct ShortcutManager::Impl {
109
+ std::map<std::string, std::pair<Shortcut, std::function<void()>>> shortcuts;
110
+ std::function<void(const std::string &id)> triggerCallback;
111
+ };
112
+
113
+ ShortcutManager::ShortcutManager() : pImpl(std::make_unique<Impl>()) {}
114
+ ShortcutManager::~ShortcutManager() = default;
115
+
116
+ ShortcutManager &ShortcutManager::instance() {
117
+ static ShortcutManager instance;
118
+ return instance;
119
+ }
120
+
121
+ bool ShortcutManager::registerShortcut(const std::string &id,
122
+ const Shortcut &shortcut,
123
+ std::function<void()> callback) {
124
+ pImpl->shortcuts[id] = {shortcut, callback};
125
+ return true;
126
+ }
127
+
128
+ bool ShortcutManager::unregisterShortcut(const std::string &id) {
129
+ return pImpl->shortcuts.erase(id) > 0;
130
+ }
131
+
132
+ void ShortcutManager::clearShortcuts() { pImpl->shortcuts.clear(); }
133
+
134
+ bool ShortcutManager::isRegistered(const std::string &id) const {
135
+ return pImpl->shortcuts.count(id) > 0;
136
+ }
137
+
138
+ Shortcut ShortcutManager::getShortcut(const std::string &id) const {
139
+ auto it = pImpl->shortcuts.find(id);
140
+ return it != pImpl->shortcuts.end() ? it->second.first : Shortcut();
141
+ }
142
+
143
+ void ShortcutManager::onShortcutTriggered(
144
+ std::function<void(const std::string &id)> callback) {
145
+ pImpl->triggerCallback = callback;
146
+ }
147
+
148
+ std::string Shortcut::toString() const {
149
+ std::string s;
150
+ if (mods & KeyMod::Control)
151
+ s += "Ctrl+";
152
+ if (mods & KeyMod::Alt)
153
+ s += "Alt+";
154
+ if (mods & KeyMod::Shift)
155
+ s += "Shift+";
156
+ if (mods & KeyMod::Super)
157
+ s += "Super+";
158
+ return s + std::to_string(static_cast<int>(key));
159
+ }
160
+
161
+ Shortcut Shortcut::parse(const std::string &str) {
162
+ Shortcut shortcut;
163
+ KeyMod mods = KeyMod::None;
164
+ std::string key;
165
+
166
+ for (size_t i = 0; i < str.size(); ++i) {
167
+ if (str[i] == '+') {
168
+ if (key == "Ctrl" || key == "Control")
169
+ mods = mods | KeyMod::Control;
170
+ else if (key == "Alt")
171
+ mods = mods | KeyMod::Alt;
172
+ else if (key == "Shift")
173
+ mods = mods | KeyMod::Shift;
174
+ else if (key == "Super" || key == "Win")
175
+ mods = mods | KeyMod::Super;
176
+ key.clear();
177
+ } else {
178
+ key += str[i];
179
+ }
180
+ }
181
+
182
+ shortcut.mods = mods;
183
+ return shortcut;
184
+ }
185
+
186
+ } // namespace plusui
@@ -0,0 +1,175 @@
1
+ export enum KeyCode {
2
+ Unknown = 0,
3
+ Space = 32,
4
+ Escape = 256,
5
+ Enter = 257,
6
+ Tab = 258,
7
+ Backspace = 259,
8
+ Delete = 261,
9
+ Right = 262,
10
+ Left = 263,
11
+ Down = 264,
12
+ Up = 265,
13
+ F1 = 290,
14
+ F2 = 291,
15
+ F3 = 292,
16
+ F4 = 293,
17
+ F5 = 294,
18
+ F6 = 295,
19
+ F7 = 296,
20
+ F8 = 297,
21
+ F9 = 298,
22
+ F10 = 299,
23
+ F11 = 300,
24
+ F12 = 301,
25
+ LeftShift = 340,
26
+ LeftControl = 341,
27
+ LeftAlt = 342,
28
+ }
29
+
30
+ export enum KeyMod {
31
+ None = 0,
32
+ Shift = 1,
33
+ Control = 2,
34
+ Alt = 4,
35
+ Super = 8,
36
+ }
37
+
38
+ export interface KeyEvent {
39
+ key: KeyCode;
40
+ scancode: number;
41
+ mods: KeyMod;
42
+ pressed: boolean;
43
+ repeat: boolean;
44
+ keyName: string;
45
+ }
46
+
47
+ export interface Shortcut {
48
+ key: KeyCode;
49
+ mods: KeyMod;
50
+ }
51
+
52
+ export type KeyEventCallback = (event: KeyEvent) => void;
53
+ export type ShortcutCallback = () => void;
54
+
55
+ export class KeyboardAPI {
56
+ private shortcutHandlers: Map<string, ShortcutCallback> = new Map();
57
+
58
+ constructor(
59
+ private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
60
+ private eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
61
+ ) {
62
+ this.setupEventListeners();
63
+ }
64
+
65
+ private setupEventListeners(): void {
66
+ this.eventFn('keyboard:keydown', (data) => {
67
+ const event = data as KeyEvent;
68
+ });
69
+
70
+ this.eventFn('keyboard:keyup', (data) => {
71
+ const event = data as KeyEvent;
72
+ });
73
+
74
+ this.eventFn('keyboard:shortcut', (data) => {
75
+ const { id } = data as { id: string };
76
+ const handler = this.shortcutHandlers.get(id);
77
+ if (handler) handler();
78
+ });
79
+ }
80
+
81
+ async isKeyPressed(key: KeyCode): Promise<boolean> {
82
+ return await this.invokeFn<boolean>('keyboard.isKeyPressed', [key]);
83
+ }
84
+
85
+ async setAutoRepeat(enabled: boolean): Promise<void> {
86
+ await this.invokeFn('keyboard.setAutoRepeat', [enabled]);
87
+ }
88
+
89
+ async getAutoRepeat(): Promise<boolean> {
90
+ return await this.invokeFn<boolean>('keyboard.getAutoRepeat', []);
91
+ }
92
+
93
+ onKeyDown(callback: KeyEventCallback): () => void {
94
+ return this.eventFn('keyboard:keydown', callback as (...args: unknown[]) => void);
95
+ }
96
+
97
+ onKeyUp(callback: KeyEventCallback): () => void {
98
+ return this.eventFn('keyboard:keyup', callback as (...args: unknown[]) => void);
99
+ }
100
+
101
+ async registerShortcut(id: string, shortcut: Shortcut, callback: ShortcutCallback): Promise<boolean> {
102
+ this.shortcutHandlers.set(id, callback);
103
+ return await this.invokeFn<boolean>('keyboard.registerShortcut', [id, shortcut]);
104
+ }
105
+
106
+ async unregisterShortcut(id: string): Promise<boolean> {
107
+ this.shortcutHandlers.delete(id);
108
+ return await this.invokeFn<boolean>('keyboard.unregisterShortcut', [id]);
109
+ }
110
+
111
+ async clearShortcuts(): Promise<void> {
112
+ this.shortcutHandlers.clear();
113
+ await this.invokeFn('keyboard.clearShortcuts', []);
114
+ }
115
+
116
+ parseShortcut(str: string): Shortcut {
117
+ const parts = str.toLowerCase().split('+');
118
+ let mods = KeyMod.None;
119
+ let key = KeyCode.Unknown;
120
+
121
+ for (const part of parts) {
122
+ const trimmed = part.trim();
123
+ if (trimmed === 'ctrl' || trimmed === 'control') {
124
+ mods |= KeyMod.Control;
125
+ } else if (trimmed === 'alt') {
126
+ mods |= KeyMod.Alt;
127
+ } else if (trimmed === 'shift') {
128
+ mods |= KeyMod.Shift;
129
+ } else if (trimmed === 'super' || trimmed === 'win' || trimmed === 'cmd') {
130
+ mods |= KeyMod.Super;
131
+ } else {
132
+ key = this.keyNameToCode(trimmed);
133
+ }
134
+ }
135
+
136
+ return { key, mods };
137
+ }
138
+
139
+ private keyNameToCode(name: string): KeyCode {
140
+ const map: Record<string, KeyCode> = {
141
+ 'space': KeyCode.Space,
142
+ 'escape': KeyCode.Escape,
143
+ 'enter': KeyCode.Enter,
144
+ 'tab': KeyCode.Tab,
145
+ 'backspace': KeyCode.Backspace,
146
+ 'delete': KeyCode.Delete,
147
+ 'right': KeyCode.Right,
148
+ 'left': KeyCode.Left,
149
+ 'down': KeyCode.Down,
150
+ 'up': KeyCode.Up,
151
+ 'f1': KeyCode.F1,
152
+ 'f2': KeyCode.F2,
153
+ 'f3': KeyCode.F3,
154
+ 'f4': KeyCode.F4,
155
+ 'f5': KeyCode.F5,
156
+ 'f6': KeyCode.F6,
157
+ 'f7': KeyCode.F7,
158
+ 'f8': KeyCode.F8,
159
+ 'f9': KeyCode.F9,
160
+ 'f10': KeyCode.F10,
161
+ 'f11': KeyCode.F11,
162
+ 'f12': KeyCode.F12,
163
+ };
164
+ return map[name] || KeyCode.Unknown;
165
+ }
166
+ }
167
+
168
+ export function createKeyboardAPI(
169
+ invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
170
+ eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
171
+ ): KeyboardAPI {
172
+ return new KeyboardAPI(invokeFn, eventFn);
173
+ }
174
+
175
+ export default KeyboardAPI;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * PlusUI Context Menu Styles
3
+ *
4
+ * These styles are for HTML-based context menus rendered in the webview.
5
+ * Native OS menus (Windows, macOS, Linux) use system styles automatically.
6
+ *
7
+ * Use these when you want a custom-styled context menu that matches your app's theme.
8
+ */
9
+
10
+ /* ============================================================================
11
+ Base Context Menu Container
12
+ ============================================================================ */
13
+
14
+ .plusui-context-menu {
15
+ position: fixed;
16
+ z-index: 10000;
17
+ min-width: 180px;
18
+ max-width: 300px;
19
+ padding: 4px 0;
20
+ background: var(--menu-bg, #ffffff);
21
+ border: 1px solid var(--menu-border, #e0e0e0);
22
+ border-radius: 8px;
23
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15),
24
+ 0 1px 3px rgba(0, 0, 0, 0.1);
25
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
26
+ font-size: 13px;
27
+ user-select: none;
28
+ animation: plusui-menu-fade-in 0.15s ease-out;
29
+ }
30
+
31
+ @keyframes plusui-menu-fade-in {
32
+ from {
33
+ opacity: 0;
34
+ transform: scale(0.95);
35
+ }
36
+ to {
37
+ opacity: 1;
38
+ transform: scale(1);
39
+ }
40
+ }
41
+
42
+ /* ============================================================================
43
+ Menu Items
44
+ ============================================================================ */
45
+
46
+ .plusui-context-menu-item {
47
+ display: flex;
48
+ align-items: center;
49
+ padding: 8px 12px;
50
+ cursor: pointer;
51
+ color: var(--menu-text, #333333);
52
+ transition: background-color 0.1s ease;
53
+ }
54
+
55
+ .plusui-context-menu-item:hover {
56
+ background-color: var(--menu-hover, #f0f0f0);
57
+ }
58
+
59
+ .plusui-context-menu-item:active {
60
+ background-color: var(--menu-active, #e0e0e0);
61
+ }
62
+
63
+ .plusui-context-menu-item.disabled {
64
+ color: var(--menu-disabled, #999999);
65
+ cursor: not-allowed;
66
+ }
67
+
68
+ .plusui-context-menu-item.disabled:hover {
69
+ background-color: transparent;
70
+ }
71
+
72
+ /* ============================================================================
73
+ Menu Item Components
74
+ ============================================================================ */
75
+
76
+ .plusui-context-menu-icon {
77
+ width: 16px;
78
+ height: 16px;
79
+ margin-right: 10px;
80
+ flex-shrink: 0;
81
+ }
82
+
83
+ .plusui-context-menu-label {
84
+ flex: 1;
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ }
89
+
90
+ .plusui-context-menu-accelerator {
91
+ margin-left: 24px;
92
+ color: var(--menu-accelerator, #888888);
93
+ font-size: 12px;
94
+ flex-shrink: 0;
95
+ }
96
+
97
+ .plusui-context-menu-arrow {
98
+ margin-left: 8px;
99
+ color: var(--menu-arrow, #666666);
100
+ font-size: 10px;
101
+ }
102
+
103
+ /* ============================================================================
104
+ Separators
105
+ ============================================================================ */
106
+
107
+ .plusui-context-menu-separator {
108
+ height: 1px;
109
+ margin: 4px 12px;
110
+ background-color: var(--menu-separator, #e0e0e0);
111
+ }
112
+
113
+ /* ============================================================================
114
+ Checkbox & Radio Items
115
+ ============================================================================ */
116
+
117
+ .plusui-context-menu-item.checkbox::before,
118
+ .plusui-context-menu-item.radio::before {
119
+ content: '';
120
+ width: 16px;
121
+ height: 16px;
122
+ margin-right: 10px;
123
+ border: 2px solid var(--menu-checkbox-border, #666666);
124
+ border-radius: 3px;
125
+ flex-shrink: 0;
126
+ }
127
+
128
+ .plusui-context-menu-item.radio::before {
129
+ border-radius: 50%;
130
+ }
131
+
132
+ .plusui-context-menu-item.checkbox.checked::before {
133
+ background-color: var(--menu-checkbox-checked, #0066cc);
134
+ border-color: var(--menu-checkbox-checked, #0066cc);
135
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='white' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
136
+ background-size: 12px;
137
+ background-position: center;
138
+ background-repeat: no-repeat;
139
+ }
140
+
141
+ .plusui-context-menu-item.radio.checked::before {
142
+ background-color: var(--menu-radio-checked, #0066cc);
143
+ border-color: var(--menu-radio-checked, #0066cc);
144
+ box-shadow: inset 0 0 0 3px var(--menu-bg, #ffffff);
145
+ }
146
+
147
+ /* ============================================================================
148
+ Submenus
149
+ ============================================================================ */
150
+
151
+ .plusui-context-menu-submenu {
152
+ position: relative;
153
+ }
154
+
155
+ .plusui-context-menu-submenu > .plusui-context-menu {
156
+ position: absolute;
157
+ top: -4px;
158
+ left: 100%;
159
+ margin-left: 2px;
160
+ display: none;
161
+ }
162
+
163
+ .plusui-context-menu-submenu:hover > .plusui-context-menu {
164
+ display: block;
165
+ }
166
+
167
+ /* ============================================================================
168
+ Dark Theme
169
+ ============================================================================ */
170
+
171
+ .plusui-context-menu.dark,
172
+ [data-theme="dark"] .plusui-context-menu {
173
+ --menu-bg: #2d2d2d;
174
+ --menu-border: #404040;
175
+ --menu-text: #e0e0e0;
176
+ --menu-hover: #3d3d3d;
177
+ --menu-active: #4d4d4d;
178
+ --menu-disabled: #666666;
179
+ --menu-separator: #404040;
180
+ --menu-accelerator: #888888;
181
+ --menu-arrow: #888888;
182
+ --menu-checkbox-border: #888888;
183
+ --menu-checkbox-checked: #4da6ff;
184
+ --menu-radio-checked: #4da6ff;
185
+ }
186
+
187
+ /* ============================================================================
188
+ macOS Style
189
+ ============================================================================ */
190
+
191
+ .plusui-context-menu.macos {
192
+ border-radius: 6px;
193
+ padding: 3px 0;
194
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2),
195
+ 0 0 1px rgba(0, 0, 0, 0.1);
196
+ font-size: 13px;
197
+ -webkit-backdrop-filter: blur(20px);
198
+ backdrop-filter: blur(20px);
199
+ background: rgba(255, 255, 255, 0.85);
200
+ }
201
+
202
+ .plusui-context-menu.macos .plusui-context-menu-item {
203
+ padding: 4px 12px;
204
+ border-radius: 4px;
205
+ margin: 0 4px;
206
+ }
207
+
208
+ .plusui-context-menu.macos .plusui-context-menu-item:hover {
209
+ background-color: #0066cc;
210
+ color: white;
211
+ }
212
+
213
+ .plusui-context-menu.macos .plusui-context-menu-item:hover .plusui-context-menu-accelerator {
214
+ color: rgba(255, 255, 255, 0.7);
215
+ }
216
+
217
+ /* ============================================================================
218
+ Windows 11 Style
219
+ ============================================================================ */
220
+
221
+ .plusui-context-menu.windows11 {
222
+ border-radius: 8px;
223
+ padding: 4px;
224
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
225
+ border: 1px solid rgba(0, 0, 0, 0.08);
226
+ background: rgba(255, 255, 255, 0.95);
227
+ -webkit-backdrop-filter: blur(50px) saturate(150%);
228
+ backdrop-filter: blur(50px) saturate(150%);
229
+ }
230
+
231
+ .plusui-context-menu.windows11 .plusui-context-menu-item {
232
+ padding: 10px 12px;
233
+ border-radius: 4px;
234
+ }
235
+
236
+ .plusui-context-menu.windows11 .plusui-context-menu-item:hover {
237
+ background-color: rgba(0, 0, 0, 0.04);
238
+ }
239
+
240
+ .plusui-context-menu.windows11 .plusui-context-menu-separator {
241
+ margin: 4px 8px;
242
+ }
243
+
244
+ /* ============================================================================
245
+ Fluent Design (Acrylic)
246
+ ============================================================================ */
247
+
248
+ .plusui-context-menu.fluent {
249
+ background: rgba(255, 255, 255, 0.7);
250
+ -webkit-backdrop-filter: blur(40px) saturate(180%);
251
+ backdrop-filter: blur(40px) saturate(180%);
252
+ border: 1px solid rgba(255, 255, 255, 0.3);
253
+ border-radius: 8px;
254
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
255
+ }
256
+
257
+ /* ============================================================================
258
+ Compact Style (for toolbars, etc.)
259
+ ============================================================================ */
260
+
261
+ .plusui-context-menu.compact {
262
+ min-width: 120px;
263
+ font-size: 12px;
264
+ }
265
+
266
+ .plusui-context-menu.compact .plusui-context-menu-item {
267
+ padding: 6px 10px;
268
+ }
269
+
270
+ .plusui-context-menu.compact .plusui-context-menu-icon {
271
+ width: 14px;
272
+ height: 14px;
273
+ margin-right: 8px;
274
+ }
275
+
276
+ /* ============================================================================
277
+ Utility Classes
278
+ ============================================================================ */
279
+
280
+ /* Position menu above cursor instead of below */
281
+ .plusui-context-menu.above {
282
+ transform-origin: bottom left;
283
+ }
284
+
285
+ /* Right-aligned menu */
286
+ .plusui-context-menu.right-aligned {
287
+ transform-origin: top right;
288
+ }
289
+
290
+ /* No animation */
291
+ .plusui-context-menu.no-animation {
292
+ animation: none;
293
+ }