detached 22d75d9

feat(gui): add complete svelte 5 gui with tailwind css and vite build system

commit
22d75d9e604885a29b61e0aaa7c68392cffe836a
author
bamen · · 2025-09-15 05:21 UTC
parent
79f9593
signature
unsigned
45 files · +9603 -0
A gui/bindings/database/sql/index.ts +8 -0
@@ -0,0 +1,8 @@
1+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
2+// This file is automatically generated. DO NOT EDIT
3+
4+export {
5+ NullInt64,
6+ NullString,
7+ NullTime
8+} from "./models.js";
A gui/bindings/time/index.ts +10 -0
@@ -0,0 +1,10 @@
1+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
2+// This file is automatically generated. DO NOT EDIT
3+
4+export {
5+ Duration
6+} from "./models.js";
7+
8+export type {
9+ Time
10+} from "./models.js";
A gui/eslint.config.js +35 -0
@@ -0,0 +1,35 @@
1+import js from '@eslint/js';
2+import tseslint from 'typescript-eslint';
3+
4+export default tseslint.config(
5+ js.configs.recommended,
6+ ...tseslint.configs.recommended,
7+ {
8+ files: ['**/*.{js,mjs,cjs,ts}'],
9+ languageOptions: {
10+ ecmaVersion: 2024,
11+ sourceType: 'module',
12+ },
13+ rules: {
14+ // Modern TypeScript best practices
15+ '@typescript-eslint/no-unused-vars': ['error', {
16+ 'argsIgnorePattern': '^_',
17+ 'varsIgnorePattern': '^_'
18+ }],
19+ '@typescript-eslint/no-explicit-any': 'warn',
20+ '@typescript-eslint/no-empty-function': 'off',
21+ '@typescript-eslint/consistent-type-imports': 'error',
22+ '@typescript-eslint/no-import-type-side-effects': 'error',
23+
24+ // Modern JavaScript/TypeScript patterns
25+ 'prefer-const': 'error',
26+ 'no-var': 'error',
27+ 'object-shorthand': 'error',
28+ 'prefer-template': 'error',
29+ },
30+ },
31+ {
32+ files: ['**/*.js', '**/*.mjs'],
33+ ...tseslint.configs.disableTypeChecked,
34+ }
35+);⏎̸
A gui/package-lock.json +4576 -0
@@ -0,0 +1,4576 @@
1+{
2+ "name": "@dumber/gui",
3+ "version": "0.1.0",
4+ "lockfileVersion": 3,
5+ "requires": true,
6+ "packages": {
7+ "": {
8+ "name": "@dumber/gui",
9+ "version": "0.1.0",
10+ "devDependencies": {
11+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
12+ "@tailwindcss/cli": "^4.0.0",
13+ "@tailwindcss/postcss": "^4.1.13",
14+ "@types/node": "^20.0.0",
15+ "@typescript-eslint/eslint-plugin": "^7.0.0",
16+ "@typescript-eslint/parser": "^7.0.0",
17+ "autoprefixer": "^10.4.20",
18+ "eslint": "^8.57.0",
19+ "postcss": "^8.4.47",
20+ "svelte": "^5.0.0",
21+ "svelte-check": "^4.0.0",
22+ "tailwindcss": "^4.0.0",
23+ "typescript": "^5.3.0",
24+ "typescript-eslint": "^8.43.0",
25+ "vite": "^5.0.0"
26+ }
27+ },
28+ "node_modules/@alloc/quick-lru": {
29+ "version": "5.2.0",
30+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
31+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
32+ "dev": true,
33+ "license": "MIT",
34+ "engines": {
35+ "node": ">=10"
36+ },
37+ "funding": {
38+ "url": "https://github.com/sponsors/sindresorhus"
39+ }
40+ },
41+ "node_modules/@eslint-community/eslint-utils": {
42+ "version": "4.9.0",
43+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
44+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
45+ "dev": true,
46+ "license": "MIT",
47+ "dependencies": {
48+ "eslint-visitor-keys": "^3.4.3"
49+ },
50+ "engines": {
51+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
52+ },
53+ "funding": {
54+ "url": "https://opencollective.com/eslint"
55+ },
56+ "peerDependencies": {
57+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
58+ }
59+ },
60+ "node_modules/@eslint-community/regexpp": {
61+ "version": "4.12.1",
62+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
63+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
64+ "dev": true,
65+ "license": "MIT",
66+ "engines": {
67+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
68+ }
69+ },
70+ "node_modules/@eslint/eslintrc": {
71+ "version": "2.1.4",
72+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
73+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
74+ "dev": true,
75+ "license": "MIT",
76+ "dependencies": {
77+ "ajv": "^6.12.4",
78+ "debug": "^4.3.2",
79+ "espree": "^9.6.0",
80+ "globals": "^13.19.0",
81+ "ignore": "^5.2.0",
82+ "import-fresh": "^3.2.1",
83+ "js-yaml": "^4.1.0",
84+ "minimatch": "^3.1.2",
85+ "strip-json-comments": "^3.1.1"
86+ },
87+ "engines": {
88+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
89+ },
90+ "funding": {
91+ "url": "https://opencollective.com/eslint"
92+ }
93+ },
94+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
95+ "version": "1.1.12",
96+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
97+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
98+ "dev": true,
99+ "license": "MIT",
100+ "dependencies": {
101+ "balanced-match": "^1.0.0",
102+ "concat-map": "0.0.1"
103+ }
104+ },
105+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
106+ "version": "3.1.2",
107+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
108+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
109+ "dev": true,
110+ "license": "ISC",
111+ "dependencies": {
112+ "brace-expansion": "^1.1.7"
113+ },
114+ "engines": {
115+ "node": "*"
116+ }
117+ },
118+ "node_modules/@eslint/js": {
119+ "version": "8.57.1",
120+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
121+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
122+ "dev": true,
123+ "license": "MIT",
124+ "engines": {
125+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
126+ }
127+ },
128+ "node_modules/@humanwhocodes/config-array": {
129+ "version": "0.13.0",
130+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
131+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
132+ "deprecated": "Use @eslint/config-array instead",
133+ "dev": true,
134+ "license": "Apache-2.0",
135+ "dependencies": {
136+ "@humanwhocodes/object-schema": "^2.0.3",
137+ "debug": "^4.3.1",
138+ "minimatch": "^3.0.5"
139+ },
140+ "engines": {
141+ "node": ">=10.10.0"
142+ }
143+ },
144+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
145+ "version": "1.1.12",
146+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
147+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
148+ "dev": true,
149+ "license": "MIT",
150+ "dependencies": {
151+ "balanced-match": "^1.0.0",
152+ "concat-map": "0.0.1"
153+ }
154+ },
155+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
156+ "version": "3.1.2",
157+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
158+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
159+ "dev": true,
160+ "license": "ISC",
161+ "dependencies": {
162+ "brace-expansion": "^1.1.7"
163+ },
164+ "engines": {
165+ "node": "*"
166+ }
167+ },
168+ "node_modules/@humanwhocodes/module-importer": {
169+ "version": "1.0.1",
170+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
171+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
172+ "dev": true,
173+ "license": "Apache-2.0",
174+ "engines": {
175+ "node": ">=12.22"
176+ },
177+ "funding": {
178+ "type": "github",
179+ "url": "https://github.com/sponsors/nzakas"
180+ }
181+ },
182+ "node_modules/@humanwhocodes/object-schema": {
183+ "version": "2.0.3",
184+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
185+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
186+ "deprecated": "Use @eslint/object-schema instead",
187+ "dev": true,
188+ "license": "BSD-3-Clause"
189+ },
190+ "node_modules/@isaacs/fs-minipass": {
191+ "version": "4.0.1",
192+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
193+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
194+ "dev": true,
195+ "license": "ISC",
196+ "dependencies": {
197+ "minipass": "^7.0.4"
198+ },
199+ "engines": {
200+ "node": ">=18.0.0"
201+ }
202+ },
203+ "node_modules/@jridgewell/gen-mapping": {
204+ "version": "0.3.13",
205+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
206+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
207+ "dev": true,
208+ "license": "MIT",
209+ "dependencies": {
210+ "@jridgewell/sourcemap-codec": "^1.5.0",
211+ "@jridgewell/trace-mapping": "^0.3.24"
212+ }
213+ },
214+ "node_modules/@jridgewell/remapping": {
215+ "version": "2.3.5",
216+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
217+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
218+ "dev": true,
219+ "license": "MIT",
220+ "dependencies": {
221+ "@jridgewell/gen-mapping": "^0.3.5",
222+ "@jridgewell/trace-mapping": "^0.3.24"
223+ }
224+ },
225+ "node_modules/@jridgewell/resolve-uri": {
226+ "version": "3.1.2",
227+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
228+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
229+ "dev": true,
230+ "license": "MIT",
231+ "engines": {
232+ "node": ">=6.0.0"
233+ }
234+ },
235+ "node_modules/@jridgewell/sourcemap-codec": {
236+ "version": "1.5.5",
237+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
238+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
239+ "dev": true,
240+ "license": "MIT"
241+ },
242+ "node_modules/@jridgewell/trace-mapping": {
243+ "version": "0.3.30",
244+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
245+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
246+ "dev": true,
247+ "license": "MIT",
248+ "dependencies": {
249+ "@jridgewell/resolve-uri": "^3.1.0",
250+ "@jridgewell/sourcemap-codec": "^1.4.14"
251+ }
252+ },
253+ "node_modules/@nodelib/fs.scandir": {
254+ "version": "2.1.5",
255+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
256+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
257+ "dev": true,
258+ "license": "MIT",
259+ "dependencies": {
260+ "@nodelib/fs.stat": "2.0.5",
261+ "run-parallel": "^1.1.9"
262+ },
263+ "engines": {
264+ "node": ">= 8"
265+ }
266+ },
267+ "node_modules/@nodelib/fs.stat": {
268+ "version": "2.0.5",
269+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
270+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
271+ "dev": true,
272+ "license": "MIT",
273+ "engines": {
274+ "node": ">= 8"
275+ }
276+ },
277+ "node_modules/@nodelib/fs.walk": {
278+ "version": "1.2.8",
279+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
280+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
281+ "dev": true,
282+ "license": "MIT",
283+ "dependencies": {
284+ "@nodelib/fs.scandir": "2.1.5",
285+ "fastq": "^1.6.0"
286+ },
287+ "engines": {
288+ "node": ">= 8"
289+ }
290+ },
291+ "node_modules/@parcel/watcher": {
292+ "version": "2.5.1",
293+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
294+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
295+ "dev": true,
296+ "hasInstallScript": true,
297+ "license": "MIT",
298+ "dependencies": {
299+ "detect-libc": "^1.0.3",
300+ "is-glob": "^4.0.3",
301+ "micromatch": "^4.0.5",
302+ "node-addon-api": "^7.0.0"
303+ },
304+ "engines": {
305+ "node": ">= 10.0.0"
306+ },
307+ "funding": {
308+ "type": "opencollective",
309+ "url": "https://opencollective.com/parcel"
310+ },
311+ "optionalDependencies": {
312+ "@parcel/watcher-android-arm64": "2.5.1",
313+ "@parcel/watcher-darwin-arm64": "2.5.1",
314+ "@parcel/watcher-darwin-x64": "2.5.1",
315+ "@parcel/watcher-freebsd-x64": "2.5.1",
316+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
317+ "@parcel/watcher-linux-arm-musl": "2.5.1",
318+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
319+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
320+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
321+ "@parcel/watcher-linux-x64-musl": "2.5.1",
322+ "@parcel/watcher-win32-arm64": "2.5.1",
323+ "@parcel/watcher-win32-ia32": "2.5.1",
324+ "@parcel/watcher-win32-x64": "2.5.1"
325+ }
326+ },
327+ "node_modules/@parcel/watcher-android-arm64": {
328+ "version": "2.5.1",
329+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
330+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
331+ "cpu": [
332+ "arm64"
333+ ],
334+ "dev": true,
335+ "license": "MIT",
336+ "optional": true,
337+ "os": [
338+ "android"
339+ ],
340+ "engines": {
341+ "node": ">= 10.0.0"
342+ },
343+ "funding": {
344+ "type": "opencollective",
345+ "url": "https://opencollective.com/parcel"
346+ }
347+ },
348+ "node_modules/@parcel/watcher-darwin-arm64": {
349+ "version": "2.5.1",
350+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
351+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
352+ "cpu": [
353+ "arm64"
354+ ],
355+ "dev": true,
356+ "license": "MIT",
357+ "optional": true,
358+ "os": [
359+ "darwin"
360+ ],
361+ "engines": {
362+ "node": ">= 10.0.0"
363+ },
364+ "funding": {
365+ "type": "opencollective",
366+ "url": "https://opencollective.com/parcel"
367+ }
368+ },
369+ "node_modules/@parcel/watcher-darwin-x64": {
370+ "version": "2.5.1",
371+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
372+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
373+ "cpu": [
374+ "x64"
375+ ],
376+ "dev": true,
377+ "license": "MIT",
378+ "optional": true,
379+ "os": [
380+ "darwin"
381+ ],
382+ "engines": {
383+ "node": ">= 10.0.0"
384+ },
385+ "funding": {
386+ "type": "opencollective",
387+ "url": "https://opencollective.com/parcel"
388+ }
389+ },
390+ "node_modules/@parcel/watcher-freebsd-x64": {
391+ "version": "2.5.1",
392+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
393+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
394+ "cpu": [
395+ "x64"
396+ ],
397+ "dev": true,
398+ "license": "MIT",
399+ "optional": true,
400+ "os": [
401+ "freebsd"
402+ ],
403+ "engines": {
404+ "node": ">= 10.0.0"
405+ },
406+ "funding": {
407+ "type": "opencollective",
408+ "url": "https://opencollective.com/parcel"
409+ }
410+ },
411+ "node_modules/@parcel/watcher-linux-arm-glibc": {
412+ "version": "2.5.1",
413+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
414+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
415+ "cpu": [
416+ "arm"
417+ ],
418+ "dev": true,
419+ "license": "MIT",
420+ "optional": true,
421+ "os": [
422+ "linux"
423+ ],
424+ "engines": {
425+ "node": ">= 10.0.0"
426+ },
427+ "funding": {
428+ "type": "opencollective",
429+ "url": "https://opencollective.com/parcel"
430+ }
431+ },
432+ "node_modules/@parcel/watcher-linux-arm-musl": {
433+ "version": "2.5.1",
434+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
435+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
436+ "cpu": [
437+ "arm"
438+ ],
439+ "dev": true,
440+ "license": "MIT",
441+ "optional": true,
442+ "os": [
443+ "linux"
444+ ],
445+ "engines": {
446+ "node": ">= 10.0.0"
447+ },
448+ "funding": {
449+ "type": "opencollective",
450+ "url": "https://opencollective.com/parcel"
451+ }
452+ },
453+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
454+ "version": "2.5.1",
455+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
456+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
457+ "cpu": [
458+ "arm64"
459+ ],
460+ "dev": true,
461+ "license": "MIT",
462+ "optional": true,
463+ "os": [
464+ "linux"
465+ ],
466+ "engines": {
467+ "node": ">= 10.0.0"
468+ },
469+ "funding": {
470+ "type": "opencollective",
471+ "url": "https://opencollective.com/parcel"
472+ }
473+ },
474+ "node_modules/@parcel/watcher-linux-arm64-musl": {
475+ "version": "2.5.1",
476+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
477+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
478+ "cpu": [
479+ "arm64"
480+ ],
481+ "dev": true,
482+ "license": "MIT",
483+ "optional": true,
484+ "os": [
485+ "linux"
486+ ],
487+ "engines": {
488+ "node": ">= 10.0.0"
489+ },
490+ "funding": {
491+ "type": "opencollective",
492+ "url": "https://opencollective.com/parcel"
493+ }
494+ },
495+ "node_modules/@parcel/watcher-linux-x64-glibc": {
496+ "version": "2.5.1",
497+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
498+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
499+ "cpu": [
500+ "x64"
501+ ],
502+ "dev": true,
503+ "license": "MIT",
504+ "optional": true,
505+ "os": [
506+ "linux"
507+ ],
508+ "engines": {
509+ "node": ">= 10.0.0"
510+ },
511+ "funding": {
512+ "type": "opencollective",
513+ "url": "https://opencollective.com/parcel"
514+ }
515+ },
516+ "node_modules/@parcel/watcher-linux-x64-musl": {
517+ "version": "2.5.1",
518+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
519+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
520+ "cpu": [
521+ "x64"
522+ ],
523+ "dev": true,
524+ "license": "MIT",
525+ "optional": true,
526+ "os": [
527+ "linux"
528+ ],
529+ "engines": {
530+ "node": ">= 10.0.0"
531+ },
532+ "funding": {
533+ "type": "opencollective",
534+ "url": "https://opencollective.com/parcel"
535+ }
536+ },
537+ "node_modules/@parcel/watcher-win32-arm64": {
538+ "version": "2.5.1",
539+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
540+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
541+ "cpu": [
542+ "arm64"
543+ ],
544+ "dev": true,
545+ "license": "MIT",
546+ "optional": true,
547+ "os": [
548+ "win32"
549+ ],
550+ "engines": {
551+ "node": ">= 10.0.0"
552+ },
553+ "funding": {
554+ "type": "opencollective",
555+ "url": "https://opencollective.com/parcel"
556+ }
557+ },
558+ "node_modules/@parcel/watcher-win32-ia32": {
559+ "version": "2.5.1",
560+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
561+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
562+ "cpu": [
563+ "ia32"
564+ ],
565+ "dev": true,
566+ "license": "MIT",
567+ "optional": true,
568+ "os": [
569+ "win32"
570+ ],
571+ "engines": {
572+ "node": ">= 10.0.0"
573+ },
574+ "funding": {
575+ "type": "opencollective",
576+ "url": "https://opencollective.com/parcel"
577+ }
578+ },
579+ "node_modules/@parcel/watcher-win32-x64": {
580+ "version": "2.5.1",
581+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
582+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
583+ "cpu": [
584+ "x64"
585+ ],
586+ "dev": true,
587+ "license": "MIT",
588+ "optional": true,
589+ "os": [
590+ "win32"
591+ ],
592+ "engines": {
593+ "node": ">= 10.0.0"
594+ },
595+ "funding": {
596+ "type": "opencollective",
597+ "url": "https://opencollective.com/parcel"
598+ }
599+ },
600+ "node_modules/@rollup/rollup-android-arm-eabi": {
601+ "version": "4.50.1",
602+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz",
603+ "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==",
604+ "cpu": [
605+ "arm"
606+ ],
607+ "dev": true,
608+ "license": "MIT",
609+ "optional": true,
610+ "os": [
611+ "android"
612+ ]
613+ },
614+ "node_modules/@rollup/rollup-android-arm64": {
615+ "version": "4.50.1",
616+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz",
617+ "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==",
618+ "cpu": [
619+ "arm64"
620+ ],
621+ "dev": true,
622+ "license": "MIT",
623+ "optional": true,
624+ "os": [
625+ "android"
626+ ]
627+ },
628+ "node_modules/@rollup/rollup-darwin-arm64": {
629+ "version": "4.50.1",
630+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz",
631+ "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==",
632+ "cpu": [
633+ "arm64"
634+ ],
635+ "dev": true,
636+ "license": "MIT",
637+ "optional": true,
638+ "os": [
639+ "darwin"
640+ ]
641+ },
642+ "node_modules/@rollup/rollup-darwin-x64": {
643+ "version": "4.50.1",
644+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz",
645+ "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==",
646+ "cpu": [
647+ "x64"
648+ ],
649+ "dev": true,
650+ "license": "MIT",
651+ "optional": true,
652+ "os": [
653+ "darwin"
654+ ]
655+ },
656+ "node_modules/@rollup/rollup-freebsd-arm64": {
657+ "version": "4.50.1",
658+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz",
659+ "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==",
660+ "cpu": [
661+ "arm64"
662+ ],
663+ "dev": true,
664+ "license": "MIT",
665+ "optional": true,
666+ "os": [
667+ "freebsd"
668+ ]
669+ },
670+ "node_modules/@rollup/rollup-freebsd-x64": {
671+ "version": "4.50.1",
672+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz",
673+ "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==",
674+ "cpu": [
675+ "x64"
676+ ],
677+ "dev": true,
678+ "license": "MIT",
679+ "optional": true,
680+ "os": [
681+ "freebsd"
682+ ]
683+ },
684+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
685+ "version": "4.50.1",
686+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz",
687+ "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==",
688+ "cpu": [
689+ "arm"
690+ ],
691+ "dev": true,
692+ "license": "MIT",
693+ "optional": true,
694+ "os": [
695+ "linux"
696+ ]
697+ },
698+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
699+ "version": "4.50.1",
700+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz",
701+ "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==",
702+ "cpu": [
703+ "arm"
704+ ],
705+ "dev": true,
706+ "license": "MIT",
707+ "optional": true,
708+ "os": [
709+ "linux"
710+ ]
711+ },
712+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
713+ "version": "4.50.1",
714+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz",
715+ "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==",
716+ "cpu": [
717+ "arm64"
718+ ],
719+ "dev": true,
720+ "license": "MIT",
721+ "optional": true,
722+ "os": [
723+ "linux"
724+ ]
725+ },
726+ "node_modules/@rollup/rollup-linux-arm64-musl": {
727+ "version": "4.50.1",
728+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz",
729+ "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==",
730+ "cpu": [
731+ "arm64"
732+ ],
733+ "dev": true,
734+ "license": "MIT",
735+ "optional": true,
736+ "os": [
737+ "linux"
738+ ]
739+ },
740+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
741+ "version": "4.50.1",
742+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz",
743+ "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==",
744+ "cpu": [
745+ "loong64"
746+ ],
747+ "dev": true,
748+ "license": "MIT",
749+ "optional": true,
750+ "os": [
751+ "linux"
752+ ]
753+ },
754+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
755+ "version": "4.50.1",
756+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz",
757+ "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==",
758+ "cpu": [
759+ "ppc64"
760+ ],
761+ "dev": true,
762+ "license": "MIT",
763+ "optional": true,
764+ "os": [
765+ "linux"
766+ ]
767+ },
768+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
769+ "version": "4.50.1",
770+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz",
771+ "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==",
772+ "cpu": [
773+ "riscv64"
774+ ],
775+ "dev": true,
776+ "license": "MIT",
777+ "optional": true,
778+ "os": [
779+ "linux"
780+ ]
781+ },
782+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
783+ "version": "4.50.1",
784+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz",
785+ "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==",
786+ "cpu": [
787+ "riscv64"
788+ ],
789+ "dev": true,
790+ "license": "MIT",
791+ "optional": true,
792+ "os": [
793+ "linux"
794+ ]
795+ },
796+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
797+ "version": "4.50.1",
798+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz",
799+ "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==",
800+ "cpu": [
801+ "s390x"
802+ ],
803+ "dev": true,
804+ "license": "MIT",
805+ "optional": true,
806+ "os": [
807+ "linux"
808+ ]
809+ },
810+ "node_modules/@rollup/rollup-linux-x64-gnu": {
811+ "version": "4.50.1",
812+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz",
813+ "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==",
814+ "cpu": [
815+ "x64"
816+ ],
817+ "dev": true,
818+ "license": "MIT",
819+ "optional": true,
820+ "os": [
821+ "linux"
822+ ]
823+ },
824+ "node_modules/@rollup/rollup-linux-x64-musl": {
825+ "version": "4.50.1",
826+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz",
827+ "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==",
828+ "cpu": [
829+ "x64"
830+ ],
831+ "dev": true,
832+ "license": "MIT",
833+ "optional": true,
834+ "os": [
835+ "linux"
836+ ]
837+ },
838+ "node_modules/@rollup/rollup-openharmony-arm64": {
839+ "version": "4.50.1",
840+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz",
841+ "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==",
842+ "cpu": [
843+ "arm64"
844+ ],
845+ "dev": true,
846+ "license": "MIT",
847+ "optional": true,
848+ "os": [
849+ "openharmony"
850+ ]
851+ },
852+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
853+ "version": "4.50.1",
854+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz",
855+ "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==",
856+ "cpu": [
857+ "arm64"
858+ ],
859+ "dev": true,
860+ "license": "MIT",
861+ "optional": true,
862+ "os": [
863+ "win32"
864+ ]
865+ },
866+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
867+ "version": "4.50.1",
868+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz",
869+ "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==",
870+ "cpu": [
871+ "ia32"
872+ ],
873+ "dev": true,
874+ "license": "MIT",
875+ "optional": true,
876+ "os": [
877+ "win32"
878+ ]
879+ },
880+ "node_modules/@rollup/rollup-win32-x64-msvc": {
881+ "version": "4.50.1",
882+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz",
883+ "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==",
884+ "cpu": [
885+ "x64"
886+ ],
887+ "dev": true,
888+ "license": "MIT",
889+ "optional": true,
890+ "os": [
891+ "win32"
892+ ]
893+ },
894+ "node_modules/@sveltejs/acorn-typescript": {
895+ "version": "1.0.5",
896+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
897+ "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
898+ "dev": true,
899+ "license": "MIT",
900+ "peerDependencies": {
901+ "acorn": "^8.9.0"
902+ }
903+ },
904+ "node_modules/@sveltejs/vite-plugin-svelte": {
905+ "version": "4.0.4",
906+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz",
907+ "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==",
908+ "dev": true,
909+ "license": "MIT",
910+ "dependencies": {
911+ "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0",
912+ "debug": "^4.3.7",
913+ "deepmerge": "^4.3.1",
914+ "kleur": "^4.1.5",
915+ "magic-string": "^0.30.12",
916+ "vitefu": "^1.0.3"
917+ },
918+ "engines": {
919+ "node": "^18.0.0 || ^20.0.0 || >=22"
920+ },
921+ "peerDependencies": {
922+ "svelte": "^5.0.0-next.96 || ^5.0.0",
923+ "vite": "^5.0.0"
924+ }
925+ },
926+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
927+ "version": "3.0.1",
928+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
929+ "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
930+ "dev": true,
931+ "license": "MIT",
932+ "dependencies": {
933+ "debug": "^4.3.7"
934+ },
935+ "engines": {
936+ "node": "^18.0.0 || ^20.0.0 || >=22"
937+ },
938+ "peerDependencies": {
939+ "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0",
940+ "svelte": "^5.0.0-next.96 || ^5.0.0",
941+ "vite": "^5.0.0"
942+ }
943+ },
944+ "node_modules/@tailwindcss/cli": {
945+ "version": "4.1.13",
946+ "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.13.tgz",
947+ "integrity": "sha512-KEu/iL4CYBzGza/2yZBLXqjCCZB/eRWkRLP8Vg2kkEWk4usC8HLGJW0QAhLS7U5DsAWumsisxgabuppE6NinLw==",
948+ "dev": true,
949+ "license": "MIT",
950+ "dependencies": {
951+ "@parcel/watcher": "^2.5.1",
952+ "@tailwindcss/node": "4.1.13",
953+ "@tailwindcss/oxide": "4.1.13",
954+ "enhanced-resolve": "^5.18.3",
955+ "mri": "^1.2.0",
956+ "picocolors": "^1.1.1",
957+ "tailwindcss": "4.1.13"
958+ },
959+ "bin": {
960+ "tailwindcss": "dist/index.mjs"
961+ }
962+ },
963+ "node_modules/@tailwindcss/node": {
964+ "version": "4.1.13",
965+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz",
966+ "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==",
967+ "dev": true,
968+ "license": "MIT",
969+ "dependencies": {
970+ "@jridgewell/remapping": "^2.3.4",
971+ "enhanced-resolve": "^5.18.3",
972+ "jiti": "^2.5.1",
973+ "lightningcss": "1.30.1",
974+ "magic-string": "^0.30.18",
975+ "source-map-js": "^1.2.1",
976+ "tailwindcss": "4.1.13"
977+ }
978+ },
979+ "node_modules/@tailwindcss/oxide": {
980+ "version": "4.1.13",
981+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz",
982+ "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==",
983+ "dev": true,
984+ "hasInstallScript": true,
985+ "license": "MIT",
986+ "dependencies": {
987+ "detect-libc": "^2.0.4",
988+ "tar": "^7.4.3"
989+ },
990+ "engines": {
991+ "node": ">= 10"
992+ },
993+ "optionalDependencies": {
994+ "@tailwindcss/oxide-android-arm64": "4.1.13",
995+ "@tailwindcss/oxide-darwin-arm64": "4.1.13",
996+ "@tailwindcss/oxide-darwin-x64": "4.1.13",
997+ "@tailwindcss/oxide-freebsd-x64": "4.1.13",
998+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13",
999+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13",
1000+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.13",
1001+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.13",
1002+ "@tailwindcss/oxide-linux-x64-musl": "4.1.13",
1003+ "@tailwindcss/oxide-wasm32-wasi": "4.1.13",
1004+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13",
1005+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.13"
1006+ }
1007+ },
1008+ "node_modules/@tailwindcss/oxide-android-arm64": {
1009+ "version": "4.1.13",
1010+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz",
1011+ "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==",
1012+ "cpu": [
1013+ "arm64"
1014+ ],
1015+ "dev": true,
1016+ "license": "MIT",
1017+ "optional": true,
1018+ "os": [
1019+ "android"
1020+ ],
1021+ "engines": {
1022+ "node": ">= 10"
1023+ }
1024+ },
1025+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
1026+ "version": "4.1.13",
1027+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz",
1028+ "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==",
1029+ "cpu": [
1030+ "arm64"
1031+ ],
1032+ "dev": true,
1033+ "license": "MIT",
1034+ "optional": true,
1035+ "os": [
1036+ "darwin"
1037+ ],
1038+ "engines": {
1039+ "node": ">= 10"
1040+ }
1041+ },
1042+ "node_modules/@tailwindcss/oxide-darwin-x64": {
1043+ "version": "4.1.13",
1044+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz",
1045+ "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==",
1046+ "cpu": [
1047+ "x64"
1048+ ],
1049+ "dev": true,
1050+ "license": "MIT",
1051+ "optional": true,
1052+ "os": [
1053+ "darwin"
1054+ ],
1055+ "engines": {
1056+ "node": ">= 10"
1057+ }
1058+ },
1059+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
1060+ "version": "4.1.13",
1061+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz",
1062+ "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==",
1063+ "cpu": [
1064+ "x64"
1065+ ],
1066+ "dev": true,
1067+ "license": "MIT",
1068+ "optional": true,
1069+ "os": [
1070+ "freebsd"
1071+ ],
1072+ "engines": {
1073+ "node": ">= 10"
1074+ }
1075+ },
1076+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
1077+ "version": "4.1.13",
1078+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz",
1079+ "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==",
1080+ "cpu": [
1081+ "arm"
1082+ ],
1083+ "dev": true,
1084+ "license": "MIT",
1085+ "optional": true,
1086+ "os": [
1087+ "linux"
1088+ ],
1089+ "engines": {
1090+ "node": ">= 10"
1091+ }
1092+ },
1093+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
1094+ "version": "4.1.13",
1095+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz",
1096+ "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==",
1097+ "cpu": [
1098+ "arm64"
1099+ ],
1100+ "dev": true,
1101+ "license": "MIT",
1102+ "optional": true,
1103+ "os": [
1104+ "linux"
1105+ ],
1106+ "engines": {
1107+ "node": ">= 10"
1108+ }
1109+ },
1110+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
1111+ "version": "4.1.13",
1112+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz",
1113+ "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==",
1114+ "cpu": [
1115+ "arm64"
1116+ ],
1117+ "dev": true,
1118+ "license": "MIT",
1119+ "optional": true,
1120+ "os": [
1121+ "linux"
1122+ ],
1123+ "engines": {
1124+ "node": ">= 10"
1125+ }
1126+ },
1127+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
1128+ "version": "4.1.13",
1129+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz",
1130+ "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==",
1131+ "cpu": [
1132+ "x64"
1133+ ],
1134+ "dev": true,
1135+ "license": "MIT",
1136+ "optional": true,
1137+ "os": [
1138+ "linux"
1139+ ],
1140+ "engines": {
1141+ "node": ">= 10"
1142+ }
1143+ },
1144+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
1145+ "version": "4.1.13",
1146+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz",
1147+ "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==",
1148+ "cpu": [
1149+ "x64"
1150+ ],
1151+ "dev": true,
1152+ "license": "MIT",
1153+ "optional": true,
1154+ "os": [
1155+ "linux"
1156+ ],
1157+ "engines": {
1158+ "node": ">= 10"
1159+ }
1160+ },
1161+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
1162+ "version": "4.1.13",
1163+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz",
1164+ "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==",
1165+ "bundleDependencies": [
1166+ "@napi-rs/wasm-runtime",
1167+ "@emnapi/core",
1168+ "@emnapi/runtime",
1169+ "@tybys/wasm-util",
1170+ "@emnapi/wasi-threads",
1171+ "tslib"
1172+ ],
1173+ "cpu": [
1174+ "wasm32"
1175+ ],
1176+ "dev": true,
1177+ "license": "MIT",
1178+ "optional": true,
1179+ "dependencies": {
1180+ "@emnapi/core": "^1.4.5",
1181+ "@emnapi/runtime": "^1.4.5",
1182+ "@emnapi/wasi-threads": "^1.0.4",
1183+ "@napi-rs/wasm-runtime": "^0.2.12",
1184+ "@tybys/wasm-util": "^0.10.0",
1185+ "tslib": "^2.8.0"
1186+ },
1187+ "engines": {
1188+ "node": ">=14.0.0"
1189+ }
1190+ },
1191+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
1192+ "version": "4.1.13",
1193+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz",
1194+ "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==",
1195+ "cpu": [
1196+ "arm64"
1197+ ],
1198+ "dev": true,
1199+ "license": "MIT",
1200+ "optional": true,
1201+ "os": [
1202+ "win32"
1203+ ],
1204+ "engines": {
1205+ "node": ">= 10"
1206+ }
1207+ },
1208+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
1209+ "version": "4.1.13",
1210+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz",
1211+ "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==",
1212+ "cpu": [
1213+ "x64"
1214+ ],
1215+ "dev": true,
1216+ "license": "MIT",
1217+ "optional": true,
1218+ "os": [
1219+ "win32"
1220+ ],
1221+ "engines": {
1222+ "node": ">= 10"
1223+ }
1224+ },
1225+ "node_modules/@tailwindcss/oxide/node_modules/detect-libc": {
1226+ "version": "2.0.4",
1227+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
1228+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
1229+ "dev": true,
1230+ "license": "Apache-2.0",
1231+ "engines": {
1232+ "node": ">=8"
1233+ }
1234+ },
1235+ "node_modules/@tailwindcss/postcss": {
1236+ "version": "4.1.13",
1237+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz",
1238+ "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==",
1239+ "dev": true,
1240+ "license": "MIT",
1241+ "dependencies": {
1242+ "@alloc/quick-lru": "^5.2.0",
1243+ "@tailwindcss/node": "4.1.13",
1244+ "@tailwindcss/oxide": "4.1.13",
1245+ "postcss": "^8.4.41",
1246+ "tailwindcss": "4.1.13"
1247+ }
1248+ },
1249+ "node_modules/@types/estree": {
1250+ "version": "1.0.8",
1251+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1252+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1253+ "dev": true,
1254+ "license": "MIT"
1255+ },
1256+ "node_modules/@types/node": {
1257+ "version": "20.19.13",
1258+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz",
1259+ "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
1260+ "dev": true,
1261+ "license": "MIT",
1262+ "dependencies": {
1263+ "undici-types": "~6.21.0"
1264+ }
1265+ },
1266+ "node_modules/@typescript-eslint/eslint-plugin": {
1267+ "version": "7.18.0",
1268+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
1269+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
1270+ "dev": true,
1271+ "license": "MIT",
1272+ "dependencies": {
1273+ "@eslint-community/regexpp": "^4.10.0",
1274+ "@typescript-eslint/scope-manager": "7.18.0",
1275+ "@typescript-eslint/type-utils": "7.18.0",
1276+ "@typescript-eslint/utils": "7.18.0",
1277+ "@typescript-eslint/visitor-keys": "7.18.0",
1278+ "graphemer": "^1.4.0",
1279+ "ignore": "^5.3.1",
1280+ "natural-compare": "^1.4.0",
1281+ "ts-api-utils": "^1.3.0"
1282+ },
1283+ "engines": {
1284+ "node": "^18.18.0 || >=20.0.0"
1285+ },
1286+ "funding": {
1287+ "type": "opencollective",
1288+ "url": "https://opencollective.com/typescript-eslint"
1289+ },
1290+ "peerDependencies": {
1291+ "@typescript-eslint/parser": "^7.0.0",
1292+ "eslint": "^8.56.0"
1293+ },
1294+ "peerDependenciesMeta": {
1295+ "typescript": {
1296+ "optional": true
1297+ }
1298+ }
1299+ },
1300+ "node_modules/@typescript-eslint/parser": {
1301+ "version": "7.18.0",
1302+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
1303+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
1304+ "dev": true,
1305+ "license": "BSD-2-Clause",
1306+ "dependencies": {
1307+ "@typescript-eslint/scope-manager": "7.18.0",
1308+ "@typescript-eslint/types": "7.18.0",
1309+ "@typescript-eslint/typescript-estree": "7.18.0",
1310+ "@typescript-eslint/visitor-keys": "7.18.0",
1311+ "debug": "^4.3.4"
1312+ },
1313+ "engines": {
1314+ "node": "^18.18.0 || >=20.0.0"
1315+ },
1316+ "funding": {
1317+ "type": "opencollective",
1318+ "url": "https://opencollective.com/typescript-eslint"
1319+ },
1320+ "peerDependencies": {
1321+ "eslint": "^8.56.0"
1322+ },
1323+ "peerDependenciesMeta": {
1324+ "typescript": {
1325+ "optional": true
1326+ }
1327+ }
1328+ },
1329+ "node_modules/@typescript-eslint/project-service": {
1330+ "version": "8.43.0",
1331+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.43.0.tgz",
1332+ "integrity": "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==",
1333+ "dev": true,
1334+ "license": "MIT",
1335+ "dependencies": {
1336+ "@typescript-eslint/tsconfig-utils": "^8.43.0",
1337+ "@typescript-eslint/types": "^8.43.0",
1338+ "debug": "^4.3.4"
1339+ },
1340+ "engines": {
1341+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1342+ },
1343+ "funding": {
1344+ "type": "opencollective",
1345+ "url": "https://opencollective.com/typescript-eslint"
1346+ },
1347+ "peerDependencies": {
1348+ "typescript": ">=4.8.4 <6.0.0"
1349+ }
1350+ },
1351+ "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
1352+ "version": "8.43.0",
1353+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz",
1354+ "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==",
1355+ "dev": true,
1356+ "license": "MIT",
1357+ "engines": {
1358+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1359+ },
1360+ "funding": {
1361+ "type": "opencollective",
1362+ "url": "https://opencollective.com/typescript-eslint"
1363+ }
1364+ },
1365+ "node_modules/@typescript-eslint/scope-manager": {
1366+ "version": "7.18.0",
1367+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
1368+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
1369+ "dev": true,
1370+ "license": "MIT",
1371+ "dependencies": {
1372+ "@typescript-eslint/types": "7.18.0",
1373+ "@typescript-eslint/visitor-keys": "7.18.0"
1374+ },
1375+ "engines": {
1376+ "node": "^18.18.0 || >=20.0.0"
1377+ },
1378+ "funding": {
1379+ "type": "opencollective",
1380+ "url": "https://opencollective.com/typescript-eslint"
1381+ }
1382+ },
1383+ "node_modules/@typescript-eslint/tsconfig-utils": {
1384+ "version": "8.43.0",
1385+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.43.0.tgz",
1386+ "integrity": "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==",
1387+ "dev": true,
1388+ "license": "MIT",
1389+ "engines": {
1390+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1391+ },
1392+ "funding": {
1393+ "type": "opencollective",
1394+ "url": "https://opencollective.com/typescript-eslint"
1395+ },
1396+ "peerDependencies": {
1397+ "typescript": ">=4.8.4 <6.0.0"
1398+ }
1399+ },
1400+ "node_modules/@typescript-eslint/type-utils": {
1401+ "version": "7.18.0",
1402+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
1403+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
1404+ "dev": true,
1405+ "license": "MIT",
1406+ "dependencies": {
1407+ "@typescript-eslint/typescript-estree": "7.18.0",
1408+ "@typescript-eslint/utils": "7.18.0",
1409+ "debug": "^4.3.4",
1410+ "ts-api-utils": "^1.3.0"
1411+ },
1412+ "engines": {
1413+ "node": "^18.18.0 || >=20.0.0"
1414+ },
1415+ "funding": {
1416+ "type": "opencollective",
1417+ "url": "https://opencollective.com/typescript-eslint"
1418+ },
1419+ "peerDependencies": {
1420+ "eslint": "^8.56.0"
1421+ },
1422+ "peerDependenciesMeta": {
1423+ "typescript": {
1424+ "optional": true
1425+ }
1426+ }
1427+ },
1428+ "node_modules/@typescript-eslint/types": {
1429+ "version": "7.18.0",
1430+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
1431+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
1432+ "dev": true,
1433+ "license": "MIT",
1434+ "engines": {
1435+ "node": "^18.18.0 || >=20.0.0"
1436+ },
1437+ "funding": {
1438+ "type": "opencollective",
1439+ "url": "https://opencollective.com/typescript-eslint"
1440+ }
1441+ },
1442+ "node_modules/@typescript-eslint/typescript-estree": {
1443+ "version": "7.18.0",
1444+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
1445+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
1446+ "dev": true,
1447+ "license": "BSD-2-Clause",
1448+ "dependencies": {
1449+ "@typescript-eslint/types": "7.18.0",
1450+ "@typescript-eslint/visitor-keys": "7.18.0",
1451+ "debug": "^4.3.4",
1452+ "globby": "^11.1.0",
1453+ "is-glob": "^4.0.3",
1454+ "minimatch": "^9.0.4",
1455+ "semver": "^7.6.0",
1456+ "ts-api-utils": "^1.3.0"
1457+ },
1458+ "engines": {
1459+ "node": "^18.18.0 || >=20.0.0"
1460+ },
1461+ "funding": {
1462+ "type": "opencollective",
1463+ "url": "https://opencollective.com/typescript-eslint"
1464+ },
1465+ "peerDependenciesMeta": {
1466+ "typescript": {
1467+ "optional": true
1468+ }
1469+ }
1470+ },
1471+ "node_modules/@typescript-eslint/utils": {
1472+ "version": "7.18.0",
1473+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
1474+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
1475+ "dev": true,
1476+ "license": "MIT",
1477+ "dependencies": {
1478+ "@eslint-community/eslint-utils": "^4.4.0",
1479+ "@typescript-eslint/scope-manager": "7.18.0",
1480+ "@typescript-eslint/types": "7.18.0",
1481+ "@typescript-eslint/typescript-estree": "7.18.0"
1482+ },
1483+ "engines": {
1484+ "node": "^18.18.0 || >=20.0.0"
1485+ },
1486+ "funding": {
1487+ "type": "opencollective",
1488+ "url": "https://opencollective.com/typescript-eslint"
1489+ },
1490+ "peerDependencies": {
1491+ "eslint": "^8.56.0"
1492+ }
1493+ },
1494+ "node_modules/@typescript-eslint/visitor-keys": {
1495+ "version": "7.18.0",
1496+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
1497+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
1498+ "dev": true,
1499+ "license": "MIT",
1500+ "dependencies": {
1501+ "@typescript-eslint/types": "7.18.0",
1502+ "eslint-visitor-keys": "^3.4.3"
1503+ },
1504+ "engines": {
1505+ "node": "^18.18.0 || >=20.0.0"
1506+ },
1507+ "funding": {
1508+ "type": "opencollective",
1509+ "url": "https://opencollective.com/typescript-eslint"
1510+ }
1511+ },
1512+ "node_modules/@ungap/structured-clone": {
1513+ "version": "1.3.0",
1514+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1515+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1516+ "dev": true,
1517+ "license": "ISC"
1518+ },
1519+ "node_modules/acorn": {
1520+ "version": "8.15.0",
1521+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
1522+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
1523+ "dev": true,
1524+ "license": "MIT",
1525+ "bin": {
1526+ "acorn": "bin/acorn"
1527+ },
1528+ "engines": {
1529+ "node": ">=0.4.0"
1530+ }
1531+ },
1532+ "node_modules/acorn-jsx": {
1533+ "version": "5.3.2",
1534+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
1535+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
1536+ "dev": true,
1537+ "license": "MIT",
1538+ "peerDependencies": {
1539+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
1540+ }
1541+ },
1542+ "node_modules/ajv": {
1543+ "version": "6.12.6",
1544+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
1545+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
1546+ "dev": true,
1547+ "license": "MIT",
1548+ "dependencies": {
1549+ "fast-deep-equal": "^3.1.1",
1550+ "fast-json-stable-stringify": "^2.0.0",
1551+ "json-schema-traverse": "^0.4.1",
1552+ "uri-js": "^4.2.2"
1553+ },
1554+ "funding": {
1555+ "type": "github",
1556+ "url": "https://github.com/sponsors/epoberezkin"
1557+ }
1558+ },
1559+ "node_modules/ansi-regex": {
1560+ "version": "5.0.1",
1561+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1562+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1563+ "dev": true,
1564+ "license": "MIT",
1565+ "engines": {
1566+ "node": ">=8"
1567+ }
1568+ },
1569+ "node_modules/ansi-styles": {
1570+ "version": "4.3.0",
1571+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
1572+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
1573+ "dev": true,
1574+ "license": "MIT",
1575+ "dependencies": {
1576+ "color-convert": "^2.0.1"
1577+ },
1578+ "engines": {
1579+ "node": ">=8"
1580+ },
1581+ "funding": {
1582+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1583+ }
1584+ },
1585+ "node_modules/argparse": {
1586+ "version": "2.0.1",
1587+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
1588+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
1589+ "dev": true,
1590+ "license": "Python-2.0"
1591+ },
1592+ "node_modules/aria-query": {
1593+ "version": "5.3.2",
1594+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
1595+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
1596+ "dev": true,
1597+ "license": "Apache-2.0",
1598+ "engines": {
1599+ "node": ">= 0.4"
1600+ }
1601+ },
1602+ "node_modules/array-union": {
1603+ "version": "2.1.0",
1604+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
1605+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
1606+ "dev": true,
1607+ "license": "MIT",
1608+ "engines": {
1609+ "node": ">=8"
1610+ }
1611+ },
1612+ "node_modules/autoprefixer": {
1613+ "version": "10.4.21",
1614+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
1615+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
1616+ "dev": true,
1617+ "funding": [
1618+ {
1619+ "type": "opencollective",
1620+ "url": "https://opencollective.com/postcss/"
1621+ },
1622+ {
1623+ "type": "tidelift",
1624+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
1625+ },
1626+ {
1627+ "type": "github",
1628+ "url": "https://github.com/sponsors/ai"
1629+ }
1630+ ],
1631+ "license": "MIT",
1632+ "dependencies": {
1633+ "browserslist": "^4.24.4",
1634+ "caniuse-lite": "^1.0.30001702",
1635+ "fraction.js": "^4.3.7",
1636+ "normalize-range": "^0.1.2",
1637+ "picocolors": "^1.1.1",
1638+ "postcss-value-parser": "^4.2.0"
1639+ },
1640+ "bin": {
1641+ "autoprefixer": "bin/autoprefixer"
1642+ },
1643+ "engines": {
1644+ "node": "^10 || ^12 || >=14"
1645+ },
1646+ "peerDependencies": {
1647+ "postcss": "^8.1.0"
1648+ }
1649+ },
1650+ "node_modules/axobject-query": {
1651+ "version": "4.1.0",
1652+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
1653+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
1654+ "dev": true,
1655+ "license": "Apache-2.0",
1656+ "engines": {
1657+ "node": ">= 0.4"
1658+ }
1659+ },
1660+ "node_modules/balanced-match": {
1661+ "version": "1.0.2",
1662+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1663+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
1664+ "dev": true,
1665+ "license": "MIT"
1666+ },
1667+ "node_modules/baseline-browser-mapping": {
1668+ "version": "2.8.3",
1669+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.3.tgz",
1670+ "integrity": "sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==",
1671+ "dev": true,
1672+ "license": "Apache-2.0",
1673+ "bin": {
1674+ "baseline-browser-mapping": "dist/cli.js"
1675+ }
1676+ },
1677+ "node_modules/brace-expansion": {
1678+ "version": "2.0.2",
1679+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
1680+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
1681+ "dev": true,
1682+ "license": "MIT",
1683+ "dependencies": {
1684+ "balanced-match": "^1.0.0"
1685+ }
1686+ },
1687+ "node_modules/braces": {
1688+ "version": "3.0.3",
1689+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
1690+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1691+ "dev": true,
1692+ "license": "MIT",
1693+ "dependencies": {
1694+ "fill-range": "^7.1.1"
1695+ },
1696+ "engines": {
1697+ "node": ">=8"
1698+ }
1699+ },
1700+ "node_modules/browserslist": {
1701+ "version": "4.26.0",
1702+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz",
1703+ "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==",
1704+ "dev": true,
1705+ "funding": [
1706+ {
1707+ "type": "opencollective",
1708+ "url": "https://opencollective.com/browserslist"
1709+ },
1710+ {
1711+ "type": "tidelift",
1712+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1713+ },
1714+ {
1715+ "type": "github",
1716+ "url": "https://github.com/sponsors/ai"
1717+ }
1718+ ],
1719+ "license": "MIT",
1720+ "dependencies": {
1721+ "baseline-browser-mapping": "^2.8.2",
1722+ "caniuse-lite": "^1.0.30001741",
1723+ "electron-to-chromium": "^1.5.218",
1724+ "node-releases": "^2.0.21",
1725+ "update-browserslist-db": "^1.1.3"
1726+ },
1727+ "bin": {
1728+ "browserslist": "cli.js"
1729+ },
1730+ "engines": {
1731+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1732+ }
1733+ },
1734+ "node_modules/callsites": {
1735+ "version": "3.1.0",
1736+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
1737+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
1738+ "dev": true,
1739+ "license": "MIT",
1740+ "engines": {
1741+ "node": ">=6"
1742+ }
1743+ },
1744+ "node_modules/caniuse-lite": {
1745+ "version": "1.0.30001741",
1746+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
1747+ "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==",
1748+ "dev": true,
1749+ "funding": [
1750+ {
1751+ "type": "opencollective",
1752+ "url": "https://opencollective.com/browserslist"
1753+ },
1754+ {
1755+ "type": "tidelift",
1756+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1757+ },
1758+ {
1759+ "type": "github",
1760+ "url": "https://github.com/sponsors/ai"
1761+ }
1762+ ],
1763+ "license": "CC-BY-4.0"
1764+ },
1765+ "node_modules/chalk": {
1766+ "version": "4.1.2",
1767+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
1768+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
1769+ "dev": true,
1770+ "license": "MIT",
1771+ "dependencies": {
1772+ "ansi-styles": "^4.1.0",
1773+ "supports-color": "^7.1.0"
1774+ },
1775+ "engines": {
1776+ "node": ">=10"
1777+ },
1778+ "funding": {
1779+ "url": "https://github.com/chalk/chalk?sponsor=1"
1780+ }
1781+ },
1782+ "node_modules/chokidar": {
1783+ "version": "4.0.3",
1784+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
1785+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
1786+ "dev": true,
1787+ "license": "MIT",
1788+ "dependencies": {
1789+ "readdirp": "^4.0.1"
1790+ },
1791+ "engines": {
1792+ "node": ">= 14.16.0"
1793+ },
1794+ "funding": {
1795+ "url": "https://paulmillr.com/funding/"
1796+ }
1797+ },
1798+ "node_modules/chownr": {
1799+ "version": "3.0.0",
1800+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
1801+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
1802+ "dev": true,
1803+ "license": "BlueOak-1.0.0",
1804+ "engines": {
1805+ "node": ">=18"
1806+ }
1807+ },
1808+ "node_modules/clsx": {
1809+ "version": "2.1.1",
1810+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
1811+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
1812+ "dev": true,
1813+ "license": "MIT",
1814+ "engines": {
1815+ "node": ">=6"
1816+ }
1817+ },
1818+ "node_modules/color-convert": {
1819+ "version": "2.0.1",
1820+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1821+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1822+ "dev": true,
1823+ "license": "MIT",
1824+ "dependencies": {
1825+ "color-name": "~1.1.4"
1826+ },
1827+ "engines": {
1828+ "node": ">=7.0.0"
1829+ }
1830+ },
1831+ "node_modules/color-name": {
1832+ "version": "1.1.4",
1833+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1834+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1835+ "dev": true,
1836+ "license": "MIT"
1837+ },
1838+ "node_modules/concat-map": {
1839+ "version": "0.0.1",
1840+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1841+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
1842+ "dev": true,
1843+ "license": "MIT"
1844+ },
1845+ "node_modules/cross-spawn": {
1846+ "version": "7.0.6",
1847+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
1848+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
1849+ "dev": true,
1850+ "license": "MIT",
1851+ "dependencies": {
1852+ "path-key": "^3.1.0",
1853+ "shebang-command": "^2.0.0",
1854+ "which": "^2.0.1"
1855+ },
1856+ "engines": {
1857+ "node": ">= 8"
1858+ }
1859+ },
1860+ "node_modules/debug": {
1861+ "version": "4.4.1",
1862+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
1863+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
1864+ "dev": true,
1865+ "license": "MIT",
1866+ "dependencies": {
1867+ "ms": "^2.1.3"
1868+ },
1869+ "engines": {
1870+ "node": ">=6.0"
1871+ },
1872+ "peerDependenciesMeta": {
1873+ "supports-color": {
1874+ "optional": true
1875+ }
1876+ }
1877+ },
1878+ "node_modules/deep-is": {
1879+ "version": "0.1.4",
1880+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
1881+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
1882+ "dev": true,
1883+ "license": "MIT"
1884+ },
1885+ "node_modules/deepmerge": {
1886+ "version": "4.3.1",
1887+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
1888+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
1889+ "dev": true,
1890+ "license": "MIT",
1891+ "engines": {
1892+ "node": ">=0.10.0"
1893+ }
1894+ },
1895+ "node_modules/detect-libc": {
1896+ "version": "1.0.3",
1897+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
1898+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
1899+ "dev": true,
1900+ "license": "Apache-2.0",
1901+ "bin": {
1902+ "detect-libc": "bin/detect-libc.js"
1903+ },
1904+ "engines": {
1905+ "node": ">=0.10"
1906+ }
1907+ },
1908+ "node_modules/dir-glob": {
1909+ "version": "3.0.1",
1910+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
1911+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
1912+ "dev": true,
1913+ "license": "MIT",
1914+ "dependencies": {
1915+ "path-type": "^4.0.0"
1916+ },
1917+ "engines": {
1918+ "node": ">=8"
1919+ }
1920+ },
1921+ "node_modules/doctrine": {
1922+ "version": "3.0.0",
1923+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
1924+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
1925+ "dev": true,
1926+ "license": "Apache-2.0",
1927+ "dependencies": {
1928+ "esutils": "^2.0.2"
1929+ },
1930+ "engines": {
1931+ "node": ">=6.0.0"
1932+ }
1933+ },
1934+ "node_modules/electron-to-chromium": {
1935+ "version": "1.5.218",
1936+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz",
1937+ "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==",
1938+ "dev": true,
1939+ "license": "ISC"
1940+ },
1941+ "node_modules/enhanced-resolve": {
1942+ "version": "5.18.3",
1943+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
1944+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
1945+ "dev": true,
1946+ "license": "MIT",
1947+ "dependencies": {
1948+ "graceful-fs": "^4.2.4",
1949+ "tapable": "^2.2.0"
1950+ },
1951+ "engines": {
1952+ "node": ">=10.13.0"
1953+ }
1954+ },
1955+ "node_modules/escalade": {
1956+ "version": "3.2.0",
1957+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1958+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1959+ "dev": true,
1960+ "license": "MIT",
1961+ "engines": {
1962+ "node": ">=6"
1963+ }
1964+ },
1965+ "node_modules/escape-string-regexp": {
1966+ "version": "4.0.0",
1967+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
1968+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
1969+ "dev": true,
1970+ "license": "MIT",
1971+ "engines": {
1972+ "node": ">=10"
1973+ },
1974+ "funding": {
1975+ "url": "https://github.com/sponsors/sindresorhus"
1976+ }
1977+ },
1978+ "node_modules/eslint": {
1979+ "version": "8.57.1",
1980+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
1981+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
1982+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
1983+ "dev": true,
1984+ "license": "MIT",
1985+ "dependencies": {
1986+ "@eslint-community/eslint-utils": "^4.2.0",
1987+ "@eslint-community/regexpp": "^4.6.1",
1988+ "@eslint/eslintrc": "^2.1.4",
1989+ "@eslint/js": "8.57.1",
1990+ "@humanwhocodes/config-array": "^0.13.0",
1991+ "@humanwhocodes/module-importer": "^1.0.1",
1992+ "@nodelib/fs.walk": "^1.2.8",
1993+ "@ungap/structured-clone": "^1.2.0",
1994+ "ajv": "^6.12.4",
1995+ "chalk": "^4.0.0",
1996+ "cross-spawn": "^7.0.2",
1997+ "debug": "^4.3.2",
1998+ "doctrine": "^3.0.0",
1999+ "escape-string-regexp": "^4.0.0",
2000+ "eslint-scope": "^7.2.2",
2001+ "eslint-visitor-keys": "^3.4.3",
2002+ "espree": "^9.6.1",
2003+ "esquery": "^1.4.2",
2004+ "esutils": "^2.0.2",
2005+ "fast-deep-equal": "^3.1.3",
2006+ "file-entry-cache": "^6.0.1",
2007+ "find-up": "^5.0.0",
2008+ "glob-parent": "^6.0.2",
2009+ "globals": "^13.19.0",
2010+ "graphemer": "^1.4.0",
2011+ "ignore": "^5.2.0",
2012+ "imurmurhash": "^0.1.4",
2013+ "is-glob": "^4.0.0",
2014+ "is-path-inside": "^3.0.3",
2015+ "js-yaml": "^4.1.0",
2016+ "json-stable-stringify-without-jsonify": "^1.0.1",
2017+ "levn": "^0.4.1",
2018+ "lodash.merge": "^4.6.2",
2019+ "minimatch": "^3.1.2",
2020+ "natural-compare": "^1.4.0",
2021+ "optionator": "^0.9.3",
2022+ "strip-ansi": "^6.0.1",
2023+ "text-table": "^0.2.0"
2024+ },
2025+ "bin": {
2026+ "eslint": "bin/eslint.js"
2027+ },
2028+ "engines": {
2029+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2030+ },
2031+ "funding": {
2032+ "url": "https://opencollective.com/eslint"
2033+ }
2034+ },
2035+ "node_modules/eslint-scope": {
2036+ "version": "7.2.2",
2037+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
2038+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
2039+ "dev": true,
2040+ "license": "BSD-2-Clause",
2041+ "dependencies": {
2042+ "esrecurse": "^4.3.0",
2043+ "estraverse": "^5.2.0"
2044+ },
2045+ "engines": {
2046+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2047+ },
2048+ "funding": {
2049+ "url": "https://opencollective.com/eslint"
2050+ }
2051+ },
2052+ "node_modules/eslint-visitor-keys": {
2053+ "version": "3.4.3",
2054+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
2055+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
2056+ "dev": true,
2057+ "license": "Apache-2.0",
2058+ "engines": {
2059+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2060+ },
2061+ "funding": {
2062+ "url": "https://opencollective.com/eslint"
2063+ }
2064+ },
2065+ "node_modules/eslint/node_modules/brace-expansion": {
2066+ "version": "1.1.12",
2067+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
2068+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
2069+ "dev": true,
2070+ "license": "MIT",
2071+ "dependencies": {
2072+ "balanced-match": "^1.0.0",
2073+ "concat-map": "0.0.1"
2074+ }
2075+ },
2076+ "node_modules/eslint/node_modules/minimatch": {
2077+ "version": "3.1.2",
2078+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2079+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2080+ "dev": true,
2081+ "license": "ISC",
2082+ "dependencies": {
2083+ "brace-expansion": "^1.1.7"
2084+ },
2085+ "engines": {
2086+ "node": "*"
2087+ }
2088+ },
2089+ "node_modules/esm-env": {
2090+ "version": "1.2.2",
2091+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
2092+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
2093+ "dev": true,
2094+ "license": "MIT"
2095+ },
2096+ "node_modules/espree": {
2097+ "version": "9.6.1",
2098+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
2099+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
2100+ "dev": true,
2101+ "license": "BSD-2-Clause",
2102+ "dependencies": {
2103+ "acorn": "^8.9.0",
2104+ "acorn-jsx": "^5.3.2",
2105+ "eslint-visitor-keys": "^3.4.1"
2106+ },
2107+ "engines": {
2108+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2109+ },
2110+ "funding": {
2111+ "url": "https://opencollective.com/eslint"
2112+ }
2113+ },
2114+ "node_modules/esquery": {
2115+ "version": "1.6.0",
2116+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
2117+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
2118+ "dev": true,
2119+ "license": "BSD-3-Clause",
2120+ "dependencies": {
2121+ "estraverse": "^5.1.0"
2122+ },
2123+ "engines": {
2124+ "node": ">=0.10"
2125+ }
2126+ },
2127+ "node_modules/esrap": {
2128+ "version": "2.1.0",
2129+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz",
2130+ "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==",
2131+ "dev": true,
2132+ "license": "MIT",
2133+ "dependencies": {
2134+ "@jridgewell/sourcemap-codec": "^1.4.15"
2135+ }
2136+ },
2137+ "node_modules/esrecurse": {
2138+ "version": "4.3.0",
2139+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
2140+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
2141+ "dev": true,
2142+ "license": "BSD-2-Clause",
2143+ "dependencies": {
2144+ "estraverse": "^5.2.0"
2145+ },
2146+ "engines": {
2147+ "node": ">=4.0"
2148+ }
2149+ },
2150+ "node_modules/estraverse": {
2151+ "version": "5.3.0",
2152+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
2153+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
2154+ "dev": true,
2155+ "license": "BSD-2-Clause",
2156+ "engines": {
2157+ "node": ">=4.0"
2158+ }
2159+ },
2160+ "node_modules/esutils": {
2161+ "version": "2.0.3",
2162+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
2163+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
2164+ "dev": true,
2165+ "license": "BSD-2-Clause",
2166+ "engines": {
2167+ "node": ">=0.10.0"
2168+ }
2169+ },
2170+ "node_modules/fast-deep-equal": {
2171+ "version": "3.1.3",
2172+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
2173+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
2174+ "dev": true,
2175+ "license": "MIT"
2176+ },
2177+ "node_modules/fast-glob": {
2178+ "version": "3.3.3",
2179+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
2180+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
2181+ "dev": true,
2182+ "license": "MIT",
2183+ "dependencies": {
2184+ "@nodelib/fs.stat": "^2.0.2",
2185+ "@nodelib/fs.walk": "^1.2.3",
2186+ "glob-parent": "^5.1.2",
2187+ "merge2": "^1.3.0",
2188+ "micromatch": "^4.0.8"
2189+ },
2190+ "engines": {
2191+ "node": ">=8.6.0"
2192+ }
2193+ },
2194+ "node_modules/fast-glob/node_modules/glob-parent": {
2195+ "version": "5.1.2",
2196+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
2197+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
2198+ "dev": true,
2199+ "license": "ISC",
2200+ "dependencies": {
2201+ "is-glob": "^4.0.1"
2202+ },
2203+ "engines": {
2204+ "node": ">= 6"
2205+ }
2206+ },
2207+ "node_modules/fast-json-stable-stringify": {
2208+ "version": "2.1.0",
2209+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
2210+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
2211+ "dev": true,
2212+ "license": "MIT"
2213+ },
2214+ "node_modules/fast-levenshtein": {
2215+ "version": "2.0.6",
2216+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
2217+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
2218+ "dev": true,
2219+ "license": "MIT"
2220+ },
2221+ "node_modules/fastq": {
2222+ "version": "1.19.1",
2223+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
2224+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
2225+ "dev": true,
2226+ "license": "ISC",
2227+ "dependencies": {
2228+ "reusify": "^1.0.4"
2229+ }
2230+ },
2231+ "node_modules/file-entry-cache": {
2232+ "version": "6.0.1",
2233+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
2234+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
2235+ "dev": true,
2236+ "license": "MIT",
2237+ "dependencies": {
2238+ "flat-cache": "^3.0.4"
2239+ },
2240+ "engines": {
2241+ "node": "^10.12.0 || >=12.0.0"
2242+ }
2243+ },
2244+ "node_modules/fill-range": {
2245+ "version": "7.1.1",
2246+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
2247+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
2248+ "dev": true,
2249+ "license": "MIT",
2250+ "dependencies": {
2251+ "to-regex-range": "^5.0.1"
2252+ },
2253+ "engines": {
2254+ "node": ">=8"
2255+ }
2256+ },
2257+ "node_modules/find-up": {
2258+ "version": "5.0.0",
2259+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
2260+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
2261+ "dev": true,
2262+ "license": "MIT",
2263+ "dependencies": {
2264+ "locate-path": "^6.0.0",
2265+ "path-exists": "^4.0.0"
2266+ },
2267+ "engines": {
2268+ "node": ">=10"
2269+ },
2270+ "funding": {
2271+ "url": "https://github.com/sponsors/sindresorhus"
2272+ }
2273+ },
2274+ "node_modules/flat-cache": {
2275+ "version": "3.2.0",
2276+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
2277+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
2278+ "dev": true,
2279+ "license": "MIT",
2280+ "dependencies": {
2281+ "flatted": "^3.2.9",
2282+ "keyv": "^4.5.3",
2283+ "rimraf": "^3.0.2"
2284+ },
2285+ "engines": {
2286+ "node": "^10.12.0 || >=12.0.0"
2287+ }
2288+ },
2289+ "node_modules/flatted": {
2290+ "version": "3.3.3",
2291+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
2292+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
2293+ "dev": true,
2294+ "license": "ISC"
2295+ },
2296+ "node_modules/fraction.js": {
2297+ "version": "4.3.7",
2298+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
2299+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
2300+ "dev": true,
2301+ "license": "MIT",
2302+ "engines": {
2303+ "node": "*"
2304+ },
2305+ "funding": {
2306+ "type": "patreon",
2307+ "url": "https://github.com/sponsors/rawify"
2308+ }
2309+ },
2310+ "node_modules/fs.realpath": {
2311+ "version": "1.0.0",
2312+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
2313+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
2314+ "dev": true,
2315+ "license": "ISC"
2316+ },
2317+ "node_modules/fsevents": {
2318+ "version": "2.3.3",
2319+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
2320+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
2321+ "dev": true,
2322+ "hasInstallScript": true,
2323+ "license": "MIT",
2324+ "optional": true,
2325+ "os": [
2326+ "darwin"
2327+ ],
2328+ "engines": {
2329+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
2330+ }
2331+ },
2332+ "node_modules/glob": {
2333+ "version": "7.2.3",
2334+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
2335+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
2336+ "deprecated": "Glob versions prior to v9 are no longer supported",
2337+ "dev": true,
2338+ "license": "ISC",
2339+ "dependencies": {
2340+ "fs.realpath": "^1.0.0",
2341+ "inflight": "^1.0.4",
2342+ "inherits": "2",
2343+ "minimatch": "^3.1.1",
2344+ "once": "^1.3.0",
2345+ "path-is-absolute": "^1.0.0"
2346+ },
2347+ "engines": {
2348+ "node": "*"
2349+ },
2350+ "funding": {
2351+ "url": "https://github.com/sponsors/isaacs"
2352+ }
2353+ },
2354+ "node_modules/glob-parent": {
2355+ "version": "6.0.2",
2356+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
2357+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
2358+ "dev": true,
2359+ "license": "ISC",
2360+ "dependencies": {
2361+ "is-glob": "^4.0.3"
2362+ },
2363+ "engines": {
2364+ "node": ">=10.13.0"
2365+ }
2366+ },
2367+ "node_modules/glob/node_modules/brace-expansion": {
2368+ "version": "1.1.12",
2369+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
2370+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
2371+ "dev": true,
2372+ "license": "MIT",
2373+ "dependencies": {
2374+ "balanced-match": "^1.0.0",
2375+ "concat-map": "0.0.1"
2376+ }
2377+ },
2378+ "node_modules/glob/node_modules/minimatch": {
2379+ "version": "3.1.2",
2380+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2381+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2382+ "dev": true,
2383+ "license": "ISC",
2384+ "dependencies": {
2385+ "brace-expansion": "^1.1.7"
2386+ },
2387+ "engines": {
2388+ "node": "*"
2389+ }
2390+ },
2391+ "node_modules/globals": {
2392+ "version": "13.24.0",
2393+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
2394+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
2395+ "dev": true,
2396+ "license": "MIT",
2397+ "dependencies": {
2398+ "type-fest": "^0.20.2"
2399+ },
2400+ "engines": {
2401+ "node": ">=8"
2402+ },
2403+ "funding": {
2404+ "url": "https://github.com/sponsors/sindresorhus"
2405+ }
2406+ },
2407+ "node_modules/globby": {
2408+ "version": "11.1.0",
2409+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
2410+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
2411+ "dev": true,
2412+ "license": "MIT",
2413+ "dependencies": {
2414+ "array-union": "^2.1.0",
2415+ "dir-glob": "^3.0.1",
2416+ "fast-glob": "^3.2.9",
2417+ "ignore": "^5.2.0",
2418+ "merge2": "^1.4.1",
2419+ "slash": "^3.0.0"
2420+ },
2421+ "engines": {
2422+ "node": ">=10"
2423+ },
2424+ "funding": {
2425+ "url": "https://github.com/sponsors/sindresorhus"
2426+ }
2427+ },
2428+ "node_modules/graceful-fs": {
2429+ "version": "4.2.11",
2430+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
2431+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
2432+ "dev": true,
2433+ "license": "ISC"
2434+ },
2435+ "node_modules/graphemer": {
2436+ "version": "1.4.0",
2437+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
2438+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
2439+ "dev": true,
2440+ "license": "MIT"
2441+ },
2442+ "node_modules/has-flag": {
2443+ "version": "4.0.0",
2444+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
2445+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
2446+ "dev": true,
2447+ "license": "MIT",
2448+ "engines": {
2449+ "node": ">=8"
2450+ }
2451+ },
2452+ "node_modules/ignore": {
2453+ "version": "5.3.2",
2454+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
2455+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
2456+ "dev": true,
2457+ "license": "MIT",
2458+ "engines": {
2459+ "node": ">= 4"
2460+ }
2461+ },
2462+ "node_modules/import-fresh": {
2463+ "version": "3.3.1",
2464+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
2465+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
2466+ "dev": true,
2467+ "license": "MIT",
2468+ "dependencies": {
2469+ "parent-module": "^1.0.0",
2470+ "resolve-from": "^4.0.0"
2471+ },
2472+ "engines": {
2473+ "node": ">=6"
2474+ },
2475+ "funding": {
2476+ "url": "https://github.com/sponsors/sindresorhus"
2477+ }
2478+ },
2479+ "node_modules/imurmurhash": {
2480+ "version": "0.1.4",
2481+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
2482+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
2483+ "dev": true,
2484+ "license": "MIT",
2485+ "engines": {
2486+ "node": ">=0.8.19"
2487+ }
2488+ },
2489+ "node_modules/inflight": {
2490+ "version": "1.0.6",
2491+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
2492+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
2493+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
2494+ "dev": true,
2495+ "license": "ISC",
2496+ "dependencies": {
2497+ "once": "^1.3.0",
2498+ "wrappy": "1"
2499+ }
2500+ },
2501+ "node_modules/inherits": {
2502+ "version": "2.0.4",
2503+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
2504+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
2505+ "dev": true,
2506+ "license": "ISC"
2507+ },
2508+ "node_modules/is-extglob": {
2509+ "version": "2.1.1",
2510+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
2511+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
2512+ "dev": true,
2513+ "license": "MIT",
2514+ "engines": {
2515+ "node": ">=0.10.0"
2516+ }
2517+ },
2518+ "node_modules/is-glob": {
2519+ "version": "4.0.3",
2520+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
2521+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
2522+ "dev": true,
2523+ "license": "MIT",
2524+ "dependencies": {
2525+ "is-extglob": "^2.1.1"
2526+ },
2527+ "engines": {
2528+ "node": ">=0.10.0"
2529+ }
2530+ },
2531+ "node_modules/is-number": {
2532+ "version": "7.0.0",
2533+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
2534+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
2535+ "dev": true,
2536+ "license": "MIT",
2537+ "engines": {
2538+ "node": ">=0.12.0"
2539+ }
2540+ },
2541+ "node_modules/is-path-inside": {
2542+ "version": "3.0.3",
2543+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
2544+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
2545+ "dev": true,
2546+ "license": "MIT",
2547+ "engines": {
2548+ "node": ">=8"
2549+ }
2550+ },
2551+ "node_modules/is-reference": {
2552+ "version": "3.0.3",
2553+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
2554+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
2555+ "dev": true,
2556+ "license": "MIT",
2557+ "dependencies": {
2558+ "@types/estree": "^1.0.6"
2559+ }
2560+ },
2561+ "node_modules/isexe": {
2562+ "version": "2.0.0",
2563+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
2564+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
2565+ "dev": true,
2566+ "license": "ISC"
2567+ },
2568+ "node_modules/jiti": {
2569+ "version": "2.5.1",
2570+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
2571+ "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
2572+ "dev": true,
2573+ "license": "MIT",
2574+ "bin": {
2575+ "jiti": "lib/jiti-cli.mjs"
2576+ }
2577+ },
2578+ "node_modules/js-yaml": {
2579+ "version": "4.1.0",
2580+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
2581+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
2582+ "dev": true,
2583+ "license": "MIT",
2584+ "dependencies": {
2585+ "argparse": "^2.0.1"
2586+ },
2587+ "bin": {
2588+ "js-yaml": "bin/js-yaml.js"
2589+ }
2590+ },
2591+ "node_modules/json-buffer": {
2592+ "version": "3.0.1",
2593+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
2594+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
2595+ "dev": true,
2596+ "license": "MIT"
2597+ },
2598+ "node_modules/json-schema-traverse": {
2599+ "version": "0.4.1",
2600+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
2601+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
2602+ "dev": true,
2603+ "license": "MIT"
2604+ },
2605+ "node_modules/json-stable-stringify-without-jsonify": {
2606+ "version": "1.0.1",
2607+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
2608+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
2609+ "dev": true,
2610+ "license": "MIT"
2611+ },
2612+ "node_modules/keyv": {
2613+ "version": "4.5.4",
2614+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
2615+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
2616+ "dev": true,
2617+ "license": "MIT",
2618+ "dependencies": {
2619+ "json-buffer": "3.0.1"
2620+ }
2621+ },
2622+ "node_modules/kleur": {
2623+ "version": "4.1.5",
2624+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
2625+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
2626+ "dev": true,
2627+ "license": "MIT",
2628+ "engines": {
2629+ "node": ">=6"
2630+ }
2631+ },
2632+ "node_modules/levn": {
2633+ "version": "0.4.1",
2634+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
2635+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
2636+ "dev": true,
2637+ "license": "MIT",
2638+ "dependencies": {
2639+ "prelude-ls": "^1.2.1",
2640+ "type-check": "~0.4.0"
2641+ },
2642+ "engines": {
2643+ "node": ">= 0.8.0"
2644+ }
2645+ },
2646+ "node_modules/lightningcss": {
2647+ "version": "1.30.1",
2648+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
2649+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
2650+ "dev": true,
2651+ "license": "MPL-2.0",
2652+ "dependencies": {
2653+ "detect-libc": "^2.0.3"
2654+ },
2655+ "engines": {
2656+ "node": ">= 12.0.0"
2657+ },
2658+ "funding": {
2659+ "type": "opencollective",
2660+ "url": "https://opencollective.com/parcel"
2661+ },
2662+ "optionalDependencies": {
2663+ "lightningcss-darwin-arm64": "1.30.1",
2664+ "lightningcss-darwin-x64": "1.30.1",
2665+ "lightningcss-freebsd-x64": "1.30.1",
2666+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
2667+ "lightningcss-linux-arm64-gnu": "1.30.1",
2668+ "lightningcss-linux-arm64-musl": "1.30.1",
2669+ "lightningcss-linux-x64-gnu": "1.30.1",
2670+ "lightningcss-linux-x64-musl": "1.30.1",
2671+ "lightningcss-win32-arm64-msvc": "1.30.1",
2672+ "lightningcss-win32-x64-msvc": "1.30.1"
2673+ }
2674+ },
2675+ "node_modules/lightningcss-darwin-arm64": {
2676+ "version": "1.30.1",
2677+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
2678+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
2679+ "cpu": [
2680+ "arm64"
2681+ ],
2682+ "dev": true,
2683+ "license": "MPL-2.0",
2684+ "optional": true,
2685+ "os": [
2686+ "darwin"
2687+ ],
2688+ "engines": {
2689+ "node": ">= 12.0.0"
2690+ },
2691+ "funding": {
2692+ "type": "opencollective",
2693+ "url": "https://opencollective.com/parcel"
2694+ }
2695+ },
2696+ "node_modules/lightningcss-darwin-x64": {
2697+ "version": "1.30.1",
2698+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
2699+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
2700+ "cpu": [
2701+ "x64"
2702+ ],
2703+ "dev": true,
2704+ "license": "MPL-2.0",
2705+ "optional": true,
2706+ "os": [
2707+ "darwin"
2708+ ],
2709+ "engines": {
2710+ "node": ">= 12.0.0"
2711+ },
2712+ "funding": {
2713+ "type": "opencollective",
2714+ "url": "https://opencollective.com/parcel"
2715+ }
2716+ },
2717+ "node_modules/lightningcss-freebsd-x64": {
2718+ "version": "1.30.1",
2719+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
2720+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
2721+ "cpu": [
2722+ "x64"
2723+ ],
2724+ "dev": true,
2725+ "license": "MPL-2.0",
2726+ "optional": true,
2727+ "os": [
2728+ "freebsd"
2729+ ],
2730+ "engines": {
2731+ "node": ">= 12.0.0"
2732+ },
2733+ "funding": {
2734+ "type": "opencollective",
2735+ "url": "https://opencollective.com/parcel"
2736+ }
2737+ },
2738+ "node_modules/lightningcss-linux-arm-gnueabihf": {
2739+ "version": "1.30.1",
2740+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
2741+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
2742+ "cpu": [
2743+ "arm"
2744+ ],
2745+ "dev": true,
2746+ "license": "MPL-2.0",
2747+ "optional": true,
2748+ "os": [
2749+ "linux"
2750+ ],
2751+ "engines": {
2752+ "node": ">= 12.0.0"
2753+ },
2754+ "funding": {
2755+ "type": "opencollective",
2756+ "url": "https://opencollective.com/parcel"
2757+ }
2758+ },
2759+ "node_modules/lightningcss-linux-arm64-gnu": {
2760+ "version": "1.30.1",
2761+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
2762+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
2763+ "cpu": [
2764+ "arm64"
2765+ ],
2766+ "dev": true,
2767+ "license": "MPL-2.0",
2768+ "optional": true,
2769+ "os": [
2770+ "linux"
2771+ ],
2772+ "engines": {
2773+ "node": ">= 12.0.0"
2774+ },
2775+ "funding": {
2776+ "type": "opencollective",
2777+ "url": "https://opencollective.com/parcel"
2778+ }
2779+ },
2780+ "node_modules/lightningcss-linux-arm64-musl": {
2781+ "version": "1.30.1",
2782+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
2783+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
2784+ "cpu": [
2785+ "arm64"
2786+ ],
2787+ "dev": true,
2788+ "license": "MPL-2.0",
2789+ "optional": true,
2790+ "os": [
2791+ "linux"
2792+ ],
2793+ "engines": {
2794+ "node": ">= 12.0.0"
2795+ },
2796+ "funding": {
2797+ "type": "opencollective",
2798+ "url": "https://opencollective.com/parcel"
2799+ }
2800+ },
2801+ "node_modules/lightningcss-linux-x64-gnu": {
2802+ "version": "1.30.1",
2803+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
2804+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
2805+ "cpu": [
2806+ "x64"
2807+ ],
2808+ "dev": true,
2809+ "license": "MPL-2.0",
2810+ "optional": true,
2811+ "os": [
2812+ "linux"
2813+ ],
2814+ "engines": {
2815+ "node": ">= 12.0.0"
2816+ },
2817+ "funding": {
2818+ "type": "opencollective",
2819+ "url": "https://opencollective.com/parcel"
2820+ }
2821+ },
2822+ "node_modules/lightningcss-linux-x64-musl": {
2823+ "version": "1.30.1",
2824+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
2825+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
2826+ "cpu": [
2827+ "x64"
2828+ ],
2829+ "dev": true,
2830+ "license": "MPL-2.0",
2831+ "optional": true,
2832+ "os": [
2833+ "linux"
2834+ ],
2835+ "engines": {
2836+ "node": ">= 12.0.0"
2837+ },
2838+ "funding": {
2839+ "type": "opencollective",
2840+ "url": "https://opencollective.com/parcel"
2841+ }
2842+ },
2843+ "node_modules/lightningcss-win32-arm64-msvc": {
2844+ "version": "1.30.1",
2845+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
2846+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
2847+ "cpu": [
2848+ "arm64"
2849+ ],
2850+ "dev": true,
2851+ "license": "MPL-2.0",
2852+ "optional": true,
2853+ "os": [
2854+ "win32"
2855+ ],
2856+ "engines": {
2857+ "node": ">= 12.0.0"
2858+ },
2859+ "funding": {
2860+ "type": "opencollective",
2861+ "url": "https://opencollective.com/parcel"
2862+ }
2863+ },
2864+ "node_modules/lightningcss-win32-x64-msvc": {
2865+ "version": "1.30.1",
2866+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
2867+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
2868+ "cpu": [
2869+ "x64"
2870+ ],
2871+ "dev": true,
2872+ "license": "MPL-2.0",
2873+ "optional": true,
2874+ "os": [
2875+ "win32"
2876+ ],
2877+ "engines": {
2878+ "node": ">= 12.0.0"
2879+ },
2880+ "funding": {
2881+ "type": "opencollective",
2882+ "url": "https://opencollective.com/parcel"
2883+ }
2884+ },
2885+ "node_modules/lightningcss/node_modules/detect-libc": {
2886+ "version": "2.0.4",
2887+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
2888+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
2889+ "dev": true,
2890+ "license": "Apache-2.0",
2891+ "engines": {
2892+ "node": ">=8"
2893+ }
2894+ },
2895+ "node_modules/locate-character": {
2896+ "version": "3.0.0",
2897+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
2898+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
2899+ "dev": true,
2900+ "license": "MIT"
2901+ },
2902+ "node_modules/locate-path": {
2903+ "version": "6.0.0",
2904+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
2905+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
2906+ "dev": true,
2907+ "license": "MIT",
2908+ "dependencies": {
2909+ "p-locate": "^5.0.0"
2910+ },
2911+ "engines": {
2912+ "node": ">=10"
2913+ },
2914+ "funding": {
2915+ "url": "https://github.com/sponsors/sindresorhus"
2916+ }
2917+ },
2918+ "node_modules/lodash.merge": {
2919+ "version": "4.6.2",
2920+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
2921+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
2922+ "dev": true,
2923+ "license": "MIT"
2924+ },
2925+ "node_modules/magic-string": {
2926+ "version": "0.30.19",
2927+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
2928+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
2929+ "dev": true,
2930+ "license": "MIT",
2931+ "dependencies": {
2932+ "@jridgewell/sourcemap-codec": "^1.5.5"
2933+ }
2934+ },
2935+ "node_modules/merge2": {
2936+ "version": "1.4.1",
2937+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
2938+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
2939+ "dev": true,
2940+ "license": "MIT",
2941+ "engines": {
2942+ "node": ">= 8"
2943+ }
2944+ },
2945+ "node_modules/micromatch": {
2946+ "version": "4.0.8",
2947+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
2948+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
2949+ "dev": true,
2950+ "license": "MIT",
2951+ "dependencies": {
2952+ "braces": "^3.0.3",
2953+ "picomatch": "^2.3.1"
2954+ },
2955+ "engines": {
2956+ "node": ">=8.6"
2957+ }
2958+ },
2959+ "node_modules/minimatch": {
2960+ "version": "9.0.5",
2961+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2962+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2963+ "dev": true,
2964+ "license": "ISC",
2965+ "dependencies": {
2966+ "brace-expansion": "^2.0.1"
2967+ },
2968+ "engines": {
2969+ "node": ">=16 || 14 >=14.17"
2970+ },
2971+ "funding": {
2972+ "url": "https://github.com/sponsors/isaacs"
2973+ }
2974+ },
2975+ "node_modules/minipass": {
2976+ "version": "7.1.2",
2977+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
2978+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
2979+ "dev": true,
2980+ "license": "ISC",
2981+ "engines": {
2982+ "node": ">=16 || 14 >=14.17"
2983+ }
2984+ },
2985+ "node_modules/minizlib": {
2986+ "version": "3.0.2",
2987+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
2988+ "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
2989+ "dev": true,
2990+ "license": "MIT",
2991+ "dependencies": {
2992+ "minipass": "^7.1.2"
2993+ },
2994+ "engines": {
2995+ "node": ">= 18"
2996+ }
2997+ },
2998+ "node_modules/mkdirp": {
2999+ "version": "3.0.1",
3000+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
3001+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
3002+ "dev": true,
3003+ "license": "MIT",
3004+ "bin": {
3005+ "mkdirp": "dist/cjs/src/bin.js"
3006+ },
3007+ "engines": {
3008+ "node": ">=10"
3009+ },
3010+ "funding": {
3011+ "url": "https://github.com/sponsors/isaacs"
3012+ }
3013+ },
3014+ "node_modules/mri": {
3015+ "version": "1.2.0",
3016+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
3017+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
3018+ "dev": true,
3019+ "license": "MIT",
3020+ "engines": {
3021+ "node": ">=4"
3022+ }
3023+ },
3024+ "node_modules/ms": {
3025+ "version": "2.1.3",
3026+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
3027+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
3028+ "dev": true,
3029+ "license": "MIT"
3030+ },
3031+ "node_modules/nanoid": {
3032+ "version": "3.3.11",
3033+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
3034+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
3035+ "dev": true,
3036+ "funding": [
3037+ {
3038+ "type": "github",
3039+ "url": "https://github.com/sponsors/ai"
3040+ }
3041+ ],
3042+ "license": "MIT",
3043+ "bin": {
3044+ "nanoid": "bin/nanoid.cjs"
3045+ },
3046+ "engines": {
3047+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
3048+ }
3049+ },
3050+ "node_modules/natural-compare": {
3051+ "version": "1.4.0",
3052+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
3053+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
3054+ "dev": true,
3055+ "license": "MIT"
3056+ },
3057+ "node_modules/node-addon-api": {
3058+ "version": "7.1.1",
3059+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
3060+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
3061+ "dev": true,
3062+ "license": "MIT"
3063+ },
3064+ "node_modules/node-releases": {
3065+ "version": "2.0.21",
3066+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
3067+ "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
3068+ "dev": true,
3069+ "license": "MIT"
3070+ },
3071+ "node_modules/normalize-range": {
3072+ "version": "0.1.2",
3073+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
3074+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
3075+ "dev": true,
3076+ "license": "MIT",
3077+ "engines": {
3078+ "node": ">=0.10.0"
3079+ }
3080+ },
3081+ "node_modules/once": {
3082+ "version": "1.4.0",
3083+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
3084+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
3085+ "dev": true,
3086+ "license": "ISC",
3087+ "dependencies": {
3088+ "wrappy": "1"
3089+ }
3090+ },
3091+ "node_modules/optionator": {
3092+ "version": "0.9.4",
3093+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
3094+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
3095+ "dev": true,
3096+ "license": "MIT",
3097+ "dependencies": {
3098+ "deep-is": "^0.1.3",
3099+ "fast-levenshtein": "^2.0.6",
3100+ "levn": "^0.4.1",
3101+ "prelude-ls": "^1.2.1",
3102+ "type-check": "^0.4.0",
3103+ "word-wrap": "^1.2.5"
3104+ },
3105+ "engines": {
3106+ "node": ">= 0.8.0"
3107+ }
3108+ },
3109+ "node_modules/p-limit": {
3110+ "version": "3.1.0",
3111+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
3112+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
3113+ "dev": true,
3114+ "license": "MIT",
3115+ "dependencies": {
3116+ "yocto-queue": "^0.1.0"
3117+ },
3118+ "engines": {
3119+ "node": ">=10"
3120+ },
3121+ "funding": {
3122+ "url": "https://github.com/sponsors/sindresorhus"
3123+ }
3124+ },
3125+ "node_modules/p-locate": {
3126+ "version": "5.0.0",
3127+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
3128+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
3129+ "dev": true,
3130+ "license": "MIT",
3131+ "dependencies": {
3132+ "p-limit": "^3.0.2"
3133+ },
3134+ "engines": {
3135+ "node": ">=10"
3136+ },
3137+ "funding": {
3138+ "url": "https://github.com/sponsors/sindresorhus"
3139+ }
3140+ },
3141+ "node_modules/parent-module": {
3142+ "version": "1.0.1",
3143+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
3144+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
3145+ "dev": true,
3146+ "license": "MIT",
3147+ "dependencies": {
3148+ "callsites": "^3.0.0"
3149+ },
3150+ "engines": {
3151+ "node": ">=6"
3152+ }
3153+ },
3154+ "node_modules/path-exists": {
3155+ "version": "4.0.0",
3156+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
3157+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
3158+ "dev": true,
3159+ "license": "MIT",
3160+ "engines": {
3161+ "node": ">=8"
3162+ }
3163+ },
3164+ "node_modules/path-is-absolute": {
3165+ "version": "1.0.1",
3166+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
3167+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
3168+ "dev": true,
3169+ "license": "MIT",
3170+ "engines": {
3171+ "node": ">=0.10.0"
3172+ }
3173+ },
3174+ "node_modules/path-key": {
3175+ "version": "3.1.1",
3176+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
3177+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
3178+ "dev": true,
3179+ "license": "MIT",
3180+ "engines": {
3181+ "node": ">=8"
3182+ }
3183+ },
3184+ "node_modules/path-type": {
3185+ "version": "4.0.0",
3186+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
3187+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
3188+ "dev": true,
3189+ "license": "MIT",
3190+ "engines": {
3191+ "node": ">=8"
3192+ }
3193+ },
3194+ "node_modules/picocolors": {
3195+ "version": "1.1.1",
3196+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
3197+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
3198+ "dev": true,
3199+ "license": "ISC"
3200+ },
3201+ "node_modules/picomatch": {
3202+ "version": "2.3.1",
3203+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
3204+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
3205+ "dev": true,
3206+ "license": "MIT",
3207+ "engines": {
3208+ "node": ">=8.6"
3209+ },
3210+ "funding": {
3211+ "url": "https://github.com/sponsors/jonschlinkert"
3212+ }
3213+ },
3214+ "node_modules/postcss": {
3215+ "version": "8.5.6",
3216+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
3217+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
3218+ "dev": true,
3219+ "funding": [
3220+ {
3221+ "type": "opencollective",
3222+ "url": "https://opencollective.com/postcss/"
3223+ },
3224+ {
3225+ "type": "tidelift",
3226+ "url": "https://tidelift.com/funding/github/npm/postcss"
3227+ },
3228+ {
3229+ "type": "github",
3230+ "url": "https://github.com/sponsors/ai"
3231+ }
3232+ ],
3233+ "license": "MIT",
3234+ "dependencies": {
3235+ "nanoid": "^3.3.11",
3236+ "picocolors": "^1.1.1",
3237+ "source-map-js": "^1.2.1"
3238+ },
3239+ "engines": {
3240+ "node": "^10 || ^12 || >=14"
3241+ }
3242+ },
3243+ "node_modules/postcss-value-parser": {
3244+ "version": "4.2.0",
3245+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
3246+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
3247+ "dev": true,
3248+ "license": "MIT"
3249+ },
3250+ "node_modules/prelude-ls": {
3251+ "version": "1.2.1",
3252+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
3253+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
3254+ "dev": true,
3255+ "license": "MIT",
3256+ "engines": {
3257+ "node": ">= 0.8.0"
3258+ }
3259+ },
3260+ "node_modules/punycode": {
3261+ "version": "2.3.1",
3262+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
3263+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
3264+ "dev": true,
3265+ "license": "MIT",
3266+ "engines": {
3267+ "node": ">=6"
3268+ }
3269+ },
3270+ "node_modules/queue-microtask": {
3271+ "version": "1.2.3",
3272+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
3273+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
3274+ "dev": true,
3275+ "funding": [
3276+ {
3277+ "type": "github",
3278+ "url": "https://github.com/sponsors/feross"
3279+ },
3280+ {
3281+ "type": "patreon",
3282+ "url": "https://www.patreon.com/feross"
3283+ },
3284+ {
3285+ "type": "consulting",
3286+ "url": "https://feross.org/support"
3287+ }
3288+ ],
3289+ "license": "MIT"
3290+ },
3291+ "node_modules/readdirp": {
3292+ "version": "4.1.2",
3293+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
3294+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
3295+ "dev": true,
3296+ "license": "MIT",
3297+ "engines": {
3298+ "node": ">= 14.18.0"
3299+ },
3300+ "funding": {
3301+ "type": "individual",
3302+ "url": "https://paulmillr.com/funding/"
3303+ }
3304+ },
3305+ "node_modules/resolve-from": {
3306+ "version": "4.0.0",
3307+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
3308+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
3309+ "dev": true,
3310+ "license": "MIT",
3311+ "engines": {
3312+ "node": ">=4"
3313+ }
3314+ },
3315+ "node_modules/reusify": {
3316+ "version": "1.1.0",
3317+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
3318+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
3319+ "dev": true,
3320+ "license": "MIT",
3321+ "engines": {
3322+ "iojs": ">=1.0.0",
3323+ "node": ">=0.10.0"
3324+ }
3325+ },
3326+ "node_modules/rimraf": {
3327+ "version": "3.0.2",
3328+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
3329+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
3330+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
3331+ "dev": true,
3332+ "license": "ISC",
3333+ "dependencies": {
3334+ "glob": "^7.1.3"
3335+ },
3336+ "bin": {
3337+ "rimraf": "bin.js"
3338+ },
3339+ "funding": {
3340+ "url": "https://github.com/sponsors/isaacs"
3341+ }
3342+ },
3343+ "node_modules/rollup": {
3344+ "version": "4.50.1",
3345+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz",
3346+ "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==",
3347+ "dev": true,
3348+ "license": "MIT",
3349+ "dependencies": {
3350+ "@types/estree": "1.0.8"
3351+ },
3352+ "bin": {
3353+ "rollup": "dist/bin/rollup"
3354+ },
3355+ "engines": {
3356+ "node": ">=18.0.0",
3357+ "npm": ">=8.0.0"
3358+ },
3359+ "optionalDependencies": {
3360+ "@rollup/rollup-android-arm-eabi": "4.50.1",
3361+ "@rollup/rollup-android-arm64": "4.50.1",
3362+ "@rollup/rollup-darwin-arm64": "4.50.1",
3363+ "@rollup/rollup-darwin-x64": "4.50.1",
3364+ "@rollup/rollup-freebsd-arm64": "4.50.1",
3365+ "@rollup/rollup-freebsd-x64": "4.50.1",
3366+ "@rollup/rollup-linux-arm-gnueabihf": "4.50.1",
3367+ "@rollup/rollup-linux-arm-musleabihf": "4.50.1",
3368+ "@rollup/rollup-linux-arm64-gnu": "4.50.1",
3369+ "@rollup/rollup-linux-arm64-musl": "4.50.1",
3370+ "@rollup/rollup-linux-loongarch64-gnu": "4.50.1",
3371+ "@rollup/rollup-linux-ppc64-gnu": "4.50.1",
3372+ "@rollup/rollup-linux-riscv64-gnu": "4.50.1",
3373+ "@rollup/rollup-linux-riscv64-musl": "4.50.1",
3374+ "@rollup/rollup-linux-s390x-gnu": "4.50.1",
3375+ "@rollup/rollup-linux-x64-gnu": "4.50.1",
3376+ "@rollup/rollup-linux-x64-musl": "4.50.1",
3377+ "@rollup/rollup-openharmony-arm64": "4.50.1",
3378+ "@rollup/rollup-win32-arm64-msvc": "4.50.1",
3379+ "@rollup/rollup-win32-ia32-msvc": "4.50.1",
3380+ "@rollup/rollup-win32-x64-msvc": "4.50.1",
3381+ "fsevents": "~2.3.2"
3382+ }
3383+ },
3384+ "node_modules/run-parallel": {
3385+ "version": "1.2.0",
3386+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
3387+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
3388+ "dev": true,
3389+ "funding": [
3390+ {
3391+ "type": "github",
3392+ "url": "https://github.com/sponsors/feross"
3393+ },
3394+ {
3395+ "type": "patreon",
3396+ "url": "https://www.patreon.com/feross"
3397+ },
3398+ {
3399+ "type": "consulting",
3400+ "url": "https://feross.org/support"
3401+ }
3402+ ],
3403+ "license": "MIT",
3404+ "dependencies": {
3405+ "queue-microtask": "^1.2.2"
3406+ }
3407+ },
3408+ "node_modules/sade": {
3409+ "version": "1.8.1",
3410+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
3411+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
3412+ "dev": true,
3413+ "license": "MIT",
3414+ "dependencies": {
3415+ "mri": "^1.1.0"
3416+ },
3417+ "engines": {
3418+ "node": ">=6"
3419+ }
3420+ },
3421+ "node_modules/semver": {
3422+ "version": "7.7.2",
3423+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
3424+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
3425+ "dev": true,
3426+ "license": "ISC",
3427+ "bin": {
3428+ "semver": "bin/semver.js"
3429+ },
3430+ "engines": {
3431+ "node": ">=10"
3432+ }
3433+ },
3434+ "node_modules/shebang-command": {
3435+ "version": "2.0.0",
3436+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
3437+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
3438+ "dev": true,
3439+ "license": "MIT",
3440+ "dependencies": {
3441+ "shebang-regex": "^3.0.0"
3442+ },
3443+ "engines": {
3444+ "node": ">=8"
3445+ }
3446+ },
3447+ "node_modules/shebang-regex": {
3448+ "version": "3.0.0",
3449+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
3450+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
3451+ "dev": true,
3452+ "license": "MIT",
3453+ "engines": {
3454+ "node": ">=8"
3455+ }
3456+ },
3457+ "node_modules/slash": {
3458+ "version": "3.0.0",
3459+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
3460+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
3461+ "dev": true,
3462+ "license": "MIT",
3463+ "engines": {
3464+ "node": ">=8"
3465+ }
3466+ },
3467+ "node_modules/source-map-js": {
3468+ "version": "1.2.1",
3469+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
3470+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
3471+ "dev": true,
3472+ "license": "BSD-3-Clause",
3473+ "engines": {
3474+ "node": ">=0.10.0"
3475+ }
3476+ },
3477+ "node_modules/strip-ansi": {
3478+ "version": "6.0.1",
3479+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
3480+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
3481+ "dev": true,
3482+ "license": "MIT",
3483+ "dependencies": {
3484+ "ansi-regex": "^5.0.1"
3485+ },
3486+ "engines": {
3487+ "node": ">=8"
3488+ }
3489+ },
3490+ "node_modules/strip-json-comments": {
3491+ "version": "3.1.1",
3492+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
3493+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
3494+ "dev": true,
3495+ "license": "MIT",
3496+ "engines": {
3497+ "node": ">=8"
3498+ },
3499+ "funding": {
3500+ "url": "https://github.com/sponsors/sindresorhus"
3501+ }
3502+ },
3503+ "node_modules/supports-color": {
3504+ "version": "7.2.0",
3505+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
3506+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
3507+ "dev": true,
3508+ "license": "MIT",
3509+ "dependencies": {
3510+ "has-flag": "^4.0.0"
3511+ },
3512+ "engines": {
3513+ "node": ">=8"
3514+ }
3515+ },
3516+ "node_modules/svelte": {
3517+ "version": "5.38.10",
3518+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.10.tgz",
3519+ "integrity": "sha512-UY+OhrWK7WI22bCZ00P/M3HtyWgwJPi9IxSRkoAE2MeAy6kl7ZlZWJZ8RaB+X4KD/G+wjis+cGVnVYaoqbzBqg==",
3520+ "dev": true,
3521+ "license": "MIT",
3522+ "dependencies": {
3523+ "@jridgewell/remapping": "^2.3.4",
3524+ "@jridgewell/sourcemap-codec": "^1.5.0",
3525+ "@sveltejs/acorn-typescript": "^1.0.5",
3526+ "@types/estree": "^1.0.5",
3527+ "acorn": "^8.12.1",
3528+ "aria-query": "^5.3.1",
3529+ "axobject-query": "^4.1.0",
3530+ "clsx": "^2.1.1",
3531+ "esm-env": "^1.2.1",
3532+ "esrap": "^2.1.0",
3533+ "is-reference": "^3.0.3",
3534+ "locate-character": "^3.0.0",
3535+ "magic-string": "^0.30.11",
3536+ "zimmerframe": "^1.1.2"
3537+ },
3538+ "engines": {
3539+ "node": ">=18"
3540+ }
3541+ },
3542+ "node_modules/svelte-check": {
3543+ "version": "4.3.1",
3544+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.1.tgz",
3545+ "integrity": "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==",
3546+ "dev": true,
3547+ "license": "MIT",
3548+ "dependencies": {
3549+ "@jridgewell/trace-mapping": "^0.3.25",
3550+ "chokidar": "^4.0.1",
3551+ "fdir": "^6.2.0",
3552+ "picocolors": "^1.0.0",
3553+ "sade": "^1.7.4"
3554+ },
3555+ "bin": {
3556+ "svelte-check": "bin/svelte-check"
3557+ },
3558+ "engines": {
3559+ "node": ">= 18.0.0"
3560+ },
3561+ "peerDependencies": {
3562+ "svelte": "^4.0.0 || ^5.0.0-next.0",
3563+ "typescript": ">=5.0.0"
3564+ }
3565+ },
3566+ "node_modules/svelte-check/node_modules/fdir": {
3567+ "version": "6.5.0",
3568+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
3569+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
3570+ "dev": true,
3571+ "license": "MIT",
3572+ "engines": {
3573+ "node": ">=12.0.0"
3574+ },
3575+ "peerDependencies": {
3576+ "picomatch": "^3 || ^4"
3577+ },
3578+ "peerDependenciesMeta": {
3579+ "picomatch": {
3580+ "optional": true
3581+ }
3582+ }
3583+ },
3584+ "node_modules/svelte-check/node_modules/picomatch": {
3585+ "version": "4.0.3",
3586+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
3587+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3588+ "dev": true,
3589+ "license": "MIT",
3590+ "optional": true,
3591+ "peer": true,
3592+ "engines": {
3593+ "node": ">=12"
3594+ },
3595+ "funding": {
3596+ "url": "https://github.com/sponsors/jonschlinkert"
3597+ }
3598+ },
3599+ "node_modules/tailwindcss": {
3600+ "version": "4.1.13",
3601+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
3602+ "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
3603+ "dev": true,
3604+ "license": "MIT"
3605+ },
3606+ "node_modules/tapable": {
3607+ "version": "2.2.3",
3608+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
3609+ "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
3610+ "dev": true,
3611+ "license": "MIT",
3612+ "engines": {
3613+ "node": ">=6"
3614+ },
3615+ "funding": {
3616+ "type": "opencollective",
3617+ "url": "https://opencollective.com/webpack"
3618+ }
3619+ },
3620+ "node_modules/tar": {
3621+ "version": "7.4.3",
3622+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
3623+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
3624+ "dev": true,
3625+ "license": "ISC",
3626+ "dependencies": {
3627+ "@isaacs/fs-minipass": "^4.0.0",
3628+ "chownr": "^3.0.0",
3629+ "minipass": "^7.1.2",
3630+ "minizlib": "^3.0.1",
3631+ "mkdirp": "^3.0.1",
3632+ "yallist": "^5.0.0"
3633+ },
3634+ "engines": {
3635+ "node": ">=18"
3636+ }
3637+ },
3638+ "node_modules/text-table": {
3639+ "version": "0.2.0",
3640+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
3641+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
3642+ "dev": true,
3643+ "license": "MIT"
3644+ },
3645+ "node_modules/to-regex-range": {
3646+ "version": "5.0.1",
3647+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
3648+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
3649+ "dev": true,
3650+ "license": "MIT",
3651+ "dependencies": {
3652+ "is-number": "^7.0.0"
3653+ },
3654+ "engines": {
3655+ "node": ">=8.0"
3656+ }
3657+ },
3658+ "node_modules/ts-api-utils": {
3659+ "version": "1.4.3",
3660+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
3661+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
3662+ "dev": true,
3663+ "license": "MIT",
3664+ "engines": {
3665+ "node": ">=16"
3666+ },
3667+ "peerDependencies": {
3668+ "typescript": ">=4.2.0"
3669+ }
3670+ },
3671+ "node_modules/type-check": {
3672+ "version": "0.4.0",
3673+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
3674+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
3675+ "dev": true,
3676+ "license": "MIT",
3677+ "dependencies": {
3678+ "prelude-ls": "^1.2.1"
3679+ },
3680+ "engines": {
3681+ "node": ">= 0.8.0"
3682+ }
3683+ },
3684+ "node_modules/type-fest": {
3685+ "version": "0.20.2",
3686+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
3687+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
3688+ "dev": true,
3689+ "license": "(MIT OR CC0-1.0)",
3690+ "engines": {
3691+ "node": ">=10"
3692+ },
3693+ "funding": {
3694+ "url": "https://github.com/sponsors/sindresorhus"
3695+ }
3696+ },
3697+ "node_modules/typescript": {
3698+ "version": "5.9.2",
3699+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
3700+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
3701+ "dev": true,
3702+ "license": "Apache-2.0",
3703+ "bin": {
3704+ "tsc": "bin/tsc",
3705+ "tsserver": "bin/tsserver"
3706+ },
3707+ "engines": {
3708+ "node": ">=14.17"
3709+ }
3710+ },
3711+ "node_modules/typescript-eslint": {
3712+ "version": "8.43.0",
3713+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.43.0.tgz",
3714+ "integrity": "sha512-FyRGJKUGvcFekRRcBKFBlAhnp4Ng8rhe8tuvvkR9OiU0gfd4vyvTRQHEckO6VDlH57jbeUQem2IpqPq9kLJH+w==",
3715+ "dev": true,
3716+ "license": "MIT",
3717+ "dependencies": {
3718+ "@typescript-eslint/eslint-plugin": "8.43.0",
3719+ "@typescript-eslint/parser": "8.43.0",
3720+ "@typescript-eslint/typescript-estree": "8.43.0",
3721+ "@typescript-eslint/utils": "8.43.0"
3722+ },
3723+ "engines": {
3724+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3725+ },
3726+ "funding": {
3727+ "type": "opencollective",
3728+ "url": "https://opencollective.com/typescript-eslint"
3729+ },
3730+ "peerDependencies": {
3731+ "eslint": "^8.57.0 || ^9.0.0",
3732+ "typescript": ">=4.8.4 <6.0.0"
3733+ }
3734+ },
3735+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": {
3736+ "version": "8.43.0",
3737+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.43.0.tgz",
3738+ "integrity": "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==",
3739+ "dev": true,
3740+ "license": "MIT",
3741+ "dependencies": {
3742+ "@eslint-community/regexpp": "^4.10.0",
3743+ "@typescript-eslint/scope-manager": "8.43.0",
3744+ "@typescript-eslint/type-utils": "8.43.0",
3745+ "@typescript-eslint/utils": "8.43.0",
3746+ "@typescript-eslint/visitor-keys": "8.43.0",
3747+ "graphemer": "^1.4.0",
3748+ "ignore": "^7.0.0",
3749+ "natural-compare": "^1.4.0",
3750+ "ts-api-utils": "^2.1.0"
3751+ },
3752+ "engines": {
3753+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3754+ },
3755+ "funding": {
3756+ "type": "opencollective",
3757+ "url": "https://opencollective.com/typescript-eslint"
3758+ },
3759+ "peerDependencies": {
3760+ "@typescript-eslint/parser": "^8.43.0",
3761+ "eslint": "^8.57.0 || ^9.0.0",
3762+ "typescript": ">=4.8.4 <6.0.0"
3763+ }
3764+ },
3765+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
3766+ "version": "8.43.0",
3767+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.43.0.tgz",
3768+ "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
3769+ "dev": true,
3770+ "license": "MIT",
3771+ "dependencies": {
3772+ "@typescript-eslint/scope-manager": "8.43.0",
3773+ "@typescript-eslint/types": "8.43.0",
3774+ "@typescript-eslint/typescript-estree": "8.43.0",
3775+ "@typescript-eslint/visitor-keys": "8.43.0",
3776+ "debug": "^4.3.4"
3777+ },
3778+ "engines": {
3779+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3780+ },
3781+ "funding": {
3782+ "type": "opencollective",
3783+ "url": "https://opencollective.com/typescript-eslint"
3784+ },
3785+ "peerDependencies": {
3786+ "eslint": "^8.57.0 || ^9.0.0",
3787+ "typescript": ">=4.8.4 <6.0.0"
3788+ }
3789+ },
3790+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": {
3791+ "version": "8.43.0",
3792+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.43.0.tgz",
3793+ "integrity": "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==",
3794+ "dev": true,
3795+ "license": "MIT",
3796+ "dependencies": {
3797+ "@typescript-eslint/types": "8.43.0",
3798+ "@typescript-eslint/visitor-keys": "8.43.0"
3799+ },
3800+ "engines": {
3801+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3802+ },
3803+ "funding": {
3804+ "type": "opencollective",
3805+ "url": "https://opencollective.com/typescript-eslint"
3806+ }
3807+ },
3808+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": {
3809+ "version": "8.43.0",
3810+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.43.0.tgz",
3811+ "integrity": "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==",
3812+ "dev": true,
3813+ "license": "MIT",
3814+ "dependencies": {
3815+ "@typescript-eslint/types": "8.43.0",
3816+ "@typescript-eslint/typescript-estree": "8.43.0",
3817+ "@typescript-eslint/utils": "8.43.0",
3818+ "debug": "^4.3.4",
3819+ "ts-api-utils": "^2.1.0"
3820+ },
3821+ "engines": {
3822+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3823+ },
3824+ "funding": {
3825+ "type": "opencollective",
3826+ "url": "https://opencollective.com/typescript-eslint"
3827+ },
3828+ "peerDependencies": {
3829+ "eslint": "^8.57.0 || ^9.0.0",
3830+ "typescript": ">=4.8.4 <6.0.0"
3831+ }
3832+ },
3833+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": {
3834+ "version": "8.43.0",
3835+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.43.0.tgz",
3836+ "integrity": "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==",
3837+ "dev": true,
3838+ "license": "MIT",
3839+ "engines": {
3840+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3841+ },
3842+ "funding": {
3843+ "type": "opencollective",
3844+ "url": "https://opencollective.com/typescript-eslint"
3845+ }
3846+ },
3847+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
3848+ "version": "8.43.0",
3849+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.43.0.tgz",
3850+ "integrity": "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==",
3851+ "dev": true,
3852+ "license": "MIT",
3853+ "dependencies": {
3854+ "@typescript-eslint/project-service": "8.43.0",
3855+ "@typescript-eslint/tsconfig-utils": "8.43.0",
3856+ "@typescript-eslint/types": "8.43.0",
3857+ "@typescript-eslint/visitor-keys": "8.43.0",
3858+ "debug": "^4.3.4",
3859+ "fast-glob": "^3.3.2",
3860+ "is-glob": "^4.0.3",
3861+ "minimatch": "^9.0.4",
3862+ "semver": "^7.6.0",
3863+ "ts-api-utils": "^2.1.0"
3864+ },
3865+ "engines": {
3866+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3867+ },
3868+ "funding": {
3869+ "type": "opencollective",
3870+ "url": "https://opencollective.com/typescript-eslint"
3871+ },
3872+ "peerDependencies": {
3873+ "typescript": ">=4.8.4 <6.0.0"
3874+ }
3875+ },
3876+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
3877+ "version": "8.43.0",
3878+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.43.0.tgz",
3879+ "integrity": "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==",
3880+ "dev": true,
3881+ "license": "MIT",
3882+ "dependencies": {
3883+ "@eslint-community/eslint-utils": "^4.7.0",
3884+ "@typescript-eslint/scope-manager": "8.43.0",
3885+ "@typescript-eslint/types": "8.43.0",
3886+ "@typescript-eslint/typescript-estree": "8.43.0"
3887+ },
3888+ "engines": {
3889+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3890+ },
3891+ "funding": {
3892+ "type": "opencollective",
3893+ "url": "https://opencollective.com/typescript-eslint"
3894+ },
3895+ "peerDependencies": {
3896+ "eslint": "^8.57.0 || ^9.0.0",
3897+ "typescript": ">=4.8.4 <6.0.0"
3898+ }
3899+ },
3900+ "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": {
3901+ "version": "8.43.0",
3902+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.43.0.tgz",
3903+ "integrity": "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==",
3904+ "dev": true,
3905+ "license": "MIT",
3906+ "dependencies": {
3907+ "@typescript-eslint/types": "8.43.0",
3908+ "eslint-visitor-keys": "^4.2.1"
3909+ },
3910+ "engines": {
3911+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3912+ },
3913+ "funding": {
3914+ "type": "opencollective",
3915+ "url": "https://opencollective.com/typescript-eslint"
3916+ }
3917+ },
3918+ "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": {
3919+ "version": "4.2.1",
3920+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
3921+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
3922+ "dev": true,
3923+ "license": "Apache-2.0",
3924+ "engines": {
3925+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3926+ },
3927+ "funding": {
3928+ "url": "https://opencollective.com/eslint"
3929+ }
3930+ },
3931+ "node_modules/typescript-eslint/node_modules/ignore": {
3932+ "version": "7.0.5",
3933+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
3934+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
3935+ "dev": true,
3936+ "license": "MIT",
3937+ "engines": {
3938+ "node": ">= 4"
3939+ }
3940+ },
3941+ "node_modules/typescript-eslint/node_modules/ts-api-utils": {
3942+ "version": "2.1.0",
3943+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
3944+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
3945+ "dev": true,
3946+ "license": "MIT",
3947+ "engines": {
3948+ "node": ">=18.12"
3949+ },
3950+ "peerDependencies": {
3951+ "typescript": ">=4.8.4"
3952+ }
3953+ },
3954+ "node_modules/undici-types": {
3955+ "version": "6.21.0",
3956+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
3957+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
3958+ "dev": true,
3959+ "license": "MIT"
3960+ },
3961+ "node_modules/update-browserslist-db": {
3962+ "version": "1.1.3",
3963+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
3964+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
3965+ "dev": true,
3966+ "funding": [
3967+ {
3968+ "type": "opencollective",
3969+ "url": "https://opencollective.com/browserslist"
3970+ },
3971+ {
3972+ "type": "tidelift",
3973+ "url": "https://tidelift.com/funding/github/npm/browserslist"
3974+ },
3975+ {
3976+ "type": "github",
3977+ "url": "https://github.com/sponsors/ai"
3978+ }
3979+ ],
3980+ "license": "MIT",
3981+ "dependencies": {
3982+ "escalade": "^3.2.0",
3983+ "picocolors": "^1.1.1"
3984+ },
3985+ "bin": {
3986+ "update-browserslist-db": "cli.js"
3987+ },
3988+ "peerDependencies": {
3989+ "browserslist": ">= 4.21.0"
3990+ }
3991+ },
3992+ "node_modules/uri-js": {
3993+ "version": "4.4.1",
3994+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
3995+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
3996+ "dev": true,
3997+ "license": "BSD-2-Clause",
3998+ "dependencies": {
3999+ "punycode": "^2.1.0"
4000+ }
4001+ },
4002+ "node_modules/vite": {
4003+ "version": "5.4.20",
4004+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
4005+ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
4006+ "dev": true,
4007+ "license": "MIT",
4008+ "dependencies": {
4009+ "esbuild": "^0.21.3",
4010+ "postcss": "^8.4.43",
4011+ "rollup": "^4.20.0"
4012+ },
4013+ "bin": {
4014+ "vite": "bin/vite.js"
4015+ },
4016+ "engines": {
4017+ "node": "^18.0.0 || >=20.0.0"
4018+ },
4019+ "funding": {
4020+ "url": "https://github.com/vitejs/vite?sponsor=1"
4021+ },
4022+ "optionalDependencies": {
4023+ "fsevents": "~2.3.3"
4024+ },
4025+ "peerDependencies": {
4026+ "@types/node": "^18.0.0 || >=20.0.0",
4027+ "less": "*",
4028+ "lightningcss": "^1.21.0",
4029+ "sass": "*",
4030+ "sass-embedded": "*",
4031+ "stylus": "*",
4032+ "sugarss": "*",
4033+ "terser": "^5.4.0"
4034+ },
4035+ "peerDependenciesMeta": {
4036+ "@types/node": {
4037+ "optional": true
4038+ },
4039+ "less": {
4040+ "optional": true
4041+ },
4042+ "lightningcss": {
4043+ "optional": true
4044+ },
4045+ "sass": {
4046+ "optional": true
4047+ },
4048+ "sass-embedded": {
4049+ "optional": true
4050+ },
4051+ "stylus": {
4052+ "optional": true
4053+ },
4054+ "sugarss": {
4055+ "optional": true
4056+ },
4057+ "terser": {
4058+ "optional": true
4059+ }
4060+ }
4061+ },
4062+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
4063+ "version": "0.21.5",
4064+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
4065+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
4066+ "cpu": [
4067+ "ppc64"
4068+ ],
4069+ "dev": true,
4070+ "license": "MIT",
4071+ "optional": true,
4072+ "os": [
4073+ "aix"
4074+ ],
4075+ "engines": {
4076+ "node": ">=12"
4077+ }
4078+ },
4079+ "node_modules/vite/node_modules/@esbuild/android-arm": {
4080+ "version": "0.21.5",
4081+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
4082+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
4083+ "cpu": [
4084+ "arm"
4085+ ],
4086+ "dev": true,
4087+ "license": "MIT",
4088+ "optional": true,
4089+ "os": [
4090+ "android"
4091+ ],
4092+ "engines": {
4093+ "node": ">=12"
4094+ }
4095+ },
4096+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
4097+ "version": "0.21.5",
4098+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
4099+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
4100+ "cpu": [
4101+ "arm64"
4102+ ],
4103+ "dev": true,
4104+ "license": "MIT",
4105+ "optional": true,
4106+ "os": [
4107+ "android"
4108+ ],
4109+ "engines": {
4110+ "node": ">=12"
4111+ }
4112+ },
4113+ "node_modules/vite/node_modules/@esbuild/android-x64": {
4114+ "version": "0.21.5",
4115+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
4116+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
4117+ "cpu": [
4118+ "x64"
4119+ ],
4120+ "dev": true,
4121+ "license": "MIT",
4122+ "optional": true,
4123+ "os": [
4124+ "android"
4125+ ],
4126+ "engines": {
4127+ "node": ">=12"
4128+ }
4129+ },
4130+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
4131+ "version": "0.21.5",
4132+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
4133+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
4134+ "cpu": [
4135+ "arm64"
4136+ ],
4137+ "dev": true,
4138+ "license": "MIT",
4139+ "optional": true,
4140+ "os": [
4141+ "darwin"
4142+ ],
4143+ "engines": {
4144+ "node": ">=12"
4145+ }
4146+ },
4147+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
4148+ "version": "0.21.5",
4149+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
4150+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
4151+ "cpu": [
4152+ "x64"
4153+ ],
4154+ "dev": true,
4155+ "license": "MIT",
4156+ "optional": true,
4157+ "os": [
4158+ "darwin"
4159+ ],
4160+ "engines": {
4161+ "node": ">=12"
4162+ }
4163+ },
4164+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
4165+ "version": "0.21.5",
4166+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
4167+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
4168+ "cpu": [
4169+ "arm64"
4170+ ],
4171+ "dev": true,
4172+ "license": "MIT",
4173+ "optional": true,
4174+ "os": [
4175+ "freebsd"
4176+ ],
4177+ "engines": {
4178+ "node": ">=12"
4179+ }
4180+ },
4181+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
4182+ "version": "0.21.5",
4183+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
4184+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
4185+ "cpu": [
4186+ "x64"
4187+ ],
4188+ "dev": true,
4189+ "license": "MIT",
4190+ "optional": true,
4191+ "os": [
4192+ "freebsd"
4193+ ],
4194+ "engines": {
4195+ "node": ">=12"
4196+ }
4197+ },
4198+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
4199+ "version": "0.21.5",
4200+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
4201+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
4202+ "cpu": [
4203+ "arm"
4204+ ],
4205+ "dev": true,
4206+ "license": "MIT",
4207+ "optional": true,
4208+ "os": [
4209+ "linux"
4210+ ],
4211+ "engines": {
4212+ "node": ">=12"
4213+ }
4214+ },
4215+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
4216+ "version": "0.21.5",
4217+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
4218+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
4219+ "cpu": [
4220+ "arm64"
4221+ ],
4222+ "dev": true,
4223+ "license": "MIT",
4224+ "optional": true,
4225+ "os": [
4226+ "linux"
4227+ ],
4228+ "engines": {
4229+ "node": ">=12"
4230+ }
4231+ },
4232+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
4233+ "version": "0.21.5",
4234+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
4235+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
4236+ "cpu": [
4237+ "ia32"
4238+ ],
4239+ "dev": true,
4240+ "license": "MIT",
4241+ "optional": true,
4242+ "os": [
4243+ "linux"
4244+ ],
4245+ "engines": {
4246+ "node": ">=12"
4247+ }
4248+ },
4249+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
4250+ "version": "0.21.5",
4251+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
4252+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
4253+ "cpu": [
4254+ "loong64"
4255+ ],
4256+ "dev": true,
4257+ "license": "MIT",
4258+ "optional": true,
4259+ "os": [
4260+ "linux"
4261+ ],
4262+ "engines": {
4263+ "node": ">=12"
4264+ }
4265+ },
4266+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
4267+ "version": "0.21.5",
4268+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
4269+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
4270+ "cpu": [
4271+ "mips64el"
4272+ ],
4273+ "dev": true,
4274+ "license": "MIT",
4275+ "optional": true,
4276+ "os": [
4277+ "linux"
4278+ ],
4279+ "engines": {
4280+ "node": ">=12"
4281+ }
4282+ },
4283+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
4284+ "version": "0.21.5",
4285+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
4286+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
4287+ "cpu": [
4288+ "ppc64"
4289+ ],
4290+ "dev": true,
4291+ "license": "MIT",
4292+ "optional": true,
4293+ "os": [
4294+ "linux"
4295+ ],
4296+ "engines": {
4297+ "node": ">=12"
4298+ }
4299+ },
4300+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
4301+ "version": "0.21.5",
4302+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
4303+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
4304+ "cpu": [
4305+ "riscv64"
4306+ ],
4307+ "dev": true,
4308+ "license": "MIT",
4309+ "optional": true,
4310+ "os": [
4311+ "linux"
4312+ ],
4313+ "engines": {
4314+ "node": ">=12"
4315+ }
4316+ },
4317+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
4318+ "version": "0.21.5",
4319+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
4320+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
4321+ "cpu": [
4322+ "s390x"
4323+ ],
4324+ "dev": true,
4325+ "license": "MIT",
4326+ "optional": true,
4327+ "os": [
4328+ "linux"
4329+ ],
4330+ "engines": {
4331+ "node": ">=12"
4332+ }
4333+ },
4334+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
4335+ "version": "0.21.5",
4336+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
4337+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
4338+ "cpu": [
4339+ "x64"
4340+ ],
4341+ "dev": true,
4342+ "license": "MIT",
4343+ "optional": true,
4344+ "os": [
4345+ "linux"
4346+ ],
4347+ "engines": {
4348+ "node": ">=12"
4349+ }
4350+ },
4351+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
4352+ "version": "0.21.5",
4353+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
4354+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
4355+ "cpu": [
4356+ "x64"
4357+ ],
4358+ "dev": true,
4359+ "license": "MIT",
4360+ "optional": true,
4361+ "os": [
4362+ "netbsd"
4363+ ],
4364+ "engines": {
4365+ "node": ">=12"
4366+ }
4367+ },
4368+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
4369+ "version": "0.21.5",
4370+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
4371+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
4372+ "cpu": [
4373+ "x64"
4374+ ],
4375+ "dev": true,
4376+ "license": "MIT",
4377+ "optional": true,
4378+ "os": [
4379+ "openbsd"
4380+ ],
4381+ "engines": {
4382+ "node": ">=12"
4383+ }
4384+ },
4385+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
4386+ "version": "0.21.5",
4387+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
4388+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
4389+ "cpu": [
4390+ "x64"
4391+ ],
4392+ "dev": true,
4393+ "license": "MIT",
4394+ "optional": true,
4395+ "os": [
4396+ "sunos"
4397+ ],
4398+ "engines": {
4399+ "node": ">=12"
4400+ }
4401+ },
4402+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
4403+ "version": "0.21.5",
4404+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
4405+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
4406+ "cpu": [
4407+ "arm64"
4408+ ],
4409+ "dev": true,
4410+ "license": "MIT",
4411+ "optional": true,
4412+ "os": [
4413+ "win32"
4414+ ],
4415+ "engines": {
4416+ "node": ">=12"
4417+ }
4418+ },
4419+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
4420+ "version": "0.21.5",
4421+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
4422+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
4423+ "cpu": [
4424+ "ia32"
4425+ ],
4426+ "dev": true,
4427+ "license": "MIT",
4428+ "optional": true,
4429+ "os": [
4430+ "win32"
4431+ ],
4432+ "engines": {
4433+ "node": ">=12"
4434+ }
4435+ },
4436+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
4437+ "version": "0.21.5",
4438+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
4439+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
4440+ "cpu": [
4441+ "x64"
4442+ ],
4443+ "dev": true,
4444+ "license": "MIT",
4445+ "optional": true,
4446+ "os": [
4447+ "win32"
4448+ ],
4449+ "engines": {
4450+ "node": ">=12"
4451+ }
4452+ },
4453+ "node_modules/vite/node_modules/esbuild": {
4454+ "version": "0.21.5",
4455+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
4456+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
4457+ "dev": true,
4458+ "hasInstallScript": true,
4459+ "license": "MIT",
4460+ "bin": {
4461+ "esbuild": "bin/esbuild"
4462+ },
4463+ "engines": {
4464+ "node": ">=12"
4465+ },
4466+ "optionalDependencies": {
4467+ "@esbuild/aix-ppc64": "0.21.5",
4468+ "@esbuild/android-arm": "0.21.5",
4469+ "@esbuild/android-arm64": "0.21.5",
4470+ "@esbuild/android-x64": "0.21.5",
4471+ "@esbuild/darwin-arm64": "0.21.5",
4472+ "@esbuild/darwin-x64": "0.21.5",
4473+ "@esbuild/freebsd-arm64": "0.21.5",
4474+ "@esbuild/freebsd-x64": "0.21.5",
4475+ "@esbuild/linux-arm": "0.21.5",
4476+ "@esbuild/linux-arm64": "0.21.5",
4477+ "@esbuild/linux-ia32": "0.21.5",
4478+ "@esbuild/linux-loong64": "0.21.5",
4479+ "@esbuild/linux-mips64el": "0.21.5",
4480+ "@esbuild/linux-ppc64": "0.21.5",
4481+ "@esbuild/linux-riscv64": "0.21.5",
4482+ "@esbuild/linux-s390x": "0.21.5",
4483+ "@esbuild/linux-x64": "0.21.5",
4484+ "@esbuild/netbsd-x64": "0.21.5",
4485+ "@esbuild/openbsd-x64": "0.21.5",
4486+ "@esbuild/sunos-x64": "0.21.5",
4487+ "@esbuild/win32-arm64": "0.21.5",
4488+ "@esbuild/win32-ia32": "0.21.5",
4489+ "@esbuild/win32-x64": "0.21.5"
4490+ }
4491+ },
4492+ "node_modules/vitefu": {
4493+ "version": "1.1.1",
4494+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
4495+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
4496+ "dev": true,
4497+ "license": "MIT",
4498+ "workspaces": [
4499+ "tests/deps/*",
4500+ "tests/projects/*",
4501+ "tests/projects/workspace/packages/*"
4502+ ],
4503+ "peerDependencies": {
4504+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
4505+ },
4506+ "peerDependenciesMeta": {
4507+ "vite": {
4508+ "optional": true
4509+ }
4510+ }
4511+ },
4512+ "node_modules/which": {
4513+ "version": "2.0.2",
4514+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
4515+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
4516+ "dev": true,
4517+ "license": "ISC",
4518+ "dependencies": {
4519+ "isexe": "^2.0.0"
4520+ },
4521+ "bin": {
4522+ "node-which": "bin/node-which"
4523+ },
4524+ "engines": {
4525+ "node": ">= 8"
4526+ }
4527+ },
4528+ "node_modules/word-wrap": {
4529+ "version": "1.2.5",
4530+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
4531+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
4532+ "dev": true,
4533+ "license": "MIT",
4534+ "engines": {
4535+ "node": ">=0.10.0"
4536+ }
4537+ },
4538+ "node_modules/wrappy": {
4539+ "version": "1.0.2",
4540+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
4541+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
4542+ "dev": true,
4543+ "license": "ISC"
4544+ },
4545+ "node_modules/yallist": {
4546+ "version": "5.0.0",
4547+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
4548+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
4549+ "dev": true,
4550+ "license": "BlueOak-1.0.0",
4551+ "engines": {
4552+ "node": ">=18"
4553+ }
4554+ },
4555+ "node_modules/yocto-queue": {
4556+ "version": "0.1.0",
4557+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
4558+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
4559+ "dev": true,
4560+ "license": "MIT",
4561+ "engines": {
4562+ "node": ">=10"
4563+ },
4564+ "funding": {
4565+ "url": "https://github.com/sponsors/sindresorhus"
4566+ }
4567+ },
4568+ "node_modules/zimmerframe": {
4569+ "version": "1.1.4",
4570+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
4571+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
4572+ "dev": true,
4573+ "license": "MIT"
4574+ }
4575+ }
4576+}
A gui/package.json +33 -0
@@ -0,0 +1,33 @@
1+{
2+ "name": "@dumber/gui",
3+ "version": "0.1.0",
4+ "private": true,
5+ "type": "module",
6+ "scripts": {
7+ "build": "npm run build:gui && npm run build:homepage",
8+ "build:gui": "vite build --config vite.config.gui.ts",
9+ "build:homepage": "vite build --config vite.config.homepage.ts",
10+ "dev": "npm run build:gui -- --watch",
11+ "dev:homepage": "npm run build:homepage -- --watch",
12+ "check": "svelte-check",
13+ "lint": "eslint src --fix",
14+ "generate:types": "node ../scripts/generate-types.js"
15+ },
16+ "devDependencies": {
17+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
18+ "@tailwindcss/cli": "^4.0.0",
19+ "@tailwindcss/postcss": "^4.1.13",
20+ "@types/node": "^20.0.0",
21+ "@typescript-eslint/eslint-plugin": "^7.0.0",
22+ "@typescript-eslint/parser": "^7.0.0",
23+ "autoprefixer": "^10.4.20",
24+ "eslint": "^8.57.0",
25+ "postcss": "^8.4.47",
26+ "svelte": "^5.0.0",
27+ "svelte-check": "^4.0.0",
28+ "tailwindcss": "^4.0.0",
29+ "typescript": "^5.3.0",
30+ "typescript-eslint": "^8.43.0",
31+ "vite": "^5.0.0"
32+ }
33+}
A gui/postcss.config.js +6 -0
@@ -0,0 +1,6 @@
1+export default {
2+ plugins: {
3+ '@tailwindcss/postcss': {},
4+ autoprefixer: {},
5+ },
6+};⏎̸
A gui/src/build-html.ts +24 -0
@@ -0,0 +1,24 @@
1+import { writeFileSync } from 'fs';
2+import { join } from 'path';
3+import { HTMLBuilder } from './utils/html-builder.js';
4+
5+// Generate the HTML file
6+function buildHTML() {
7+ try {
8+ const html = HTMLBuilder.createBaseHTML();
9+ const outputPath = join(process.cwd(), 'dist', 'index.html');
10+ writeFileSync(outputPath, html);
11+ // Also emit favicon.svg as a file for dumb:// scheme lookups
12+ const favPath = join(process.cwd(), 'dist', 'favicon.svg');
13+ writeFileSync(favPath, HTMLBuilder.getFaviconSVG());
14+ console.log('✓ Generated index.html');
15+ } catch (error) {
16+ console.error('✗ Failed to generate HTML:', error);
17+ process.exit(1);
18+ }
19+}
20+
21+// Only run when called directly (not imported)
22+if (import.meta.url === `file://${process.argv[1]}`) {
23+ buildHTML();
24+}
A gui/src/components/omnibox/Omnibox.svelte +209 -0
@@ -0,0 +1,209 @@
1+<!--
2+ Omnibox Component
3+
4+ Main container for omnibox/find functionality using Svelte 5 runes
5+-->
6+<script lang="ts">
7+ import { onMount, onDestroy } from 'svelte';
8+ import OmniboxInput from './OmniboxInput.svelte';
9+ import OmniboxSuggestions from './OmniboxSuggestions.svelte';
10+ import OmniboxFind from './OmniboxFind.svelte';
11+ import { omniboxStore } from './stores.svelte.ts';
12+ import { omniboxBridge } from './messaging';
13+
14+ // Reactive state from store
15+ let visible = $derived(omniboxStore.visible);
16+ let mode = $derived(omniboxStore.mode);
17+ let faded = $derived(omniboxStore.faded);
18+
19+ // Component refs
20+ let boxElement: HTMLDivElement;
21+ let inputElement = $state<HTMLInputElement>();
22+
23+ // Handle click outside to close
24+ function handleOverlayClick(event: MouseEvent) {
25+ const target = event.target as HTMLElement;
26+ if (target && boxElement && !boxElement.contains(target)) {
27+ event.preventDefault();
28+ event.stopPropagation();
29+ omniboxStore.close();
30+ }
31+ }
32+
33+ // Handle box click to prevent propagation
34+ function handleBoxClick(event: MouseEvent) {
35+ event.stopPropagation();
36+ }
37+
38+ // Handle mouse enter to focus input and clear fade
39+ function handleMouseEnter() {
40+ if (visible && inputElement) {
41+ try {
42+ inputElement.focus({ preventScroll: true });
43+ } catch {
44+ inputElement.focus();
45+ }
46+ omniboxStore.setFaded(false);
47+ }
48+ }
49+
50+ // Focus input when component becomes visible
51+ $effect(() => {
52+ if (visible && inputElement) {
53+ inputElement.focus();
54+ }
55+ });
56+
57+
58+ // Apply faded styling effect (matching original JS implementation)
59+ $effect(() => {
60+ if (!boxElement || !inputElement) return;
61+
62+ if (faded) {
63+ // Faded state - semi-transparent with backdrop blur
64+ boxElement.style.background = 'rgba(27,27,27,0.25)';
65+ boxElement.style.backdropFilter = 'blur(2px) saturate(110%)';
66+ (boxElement.style as any).webkitBackdropFilter = 'blur(2px) saturate(110%)';
67+ inputElement.style.background = 'rgba(18,18,18,0.35)';
68+ inputElement.style.color = '#eee';
69+ } else {
70+ // Normal state - solid background
71+ boxElement.style.background = '#1b1b1b';
72+ boxElement.style.backdropFilter = '';
73+ (boxElement.style as any).webkitBackdropFilter = '';
74+ inputElement.style.background = '#121212';
75+ inputElement.style.color = '#eee';
76+ }
77+ });
78+
79+ // Global click handler to close omnibox when clicking anywhere outside
80+ function handleGlobalClick(event: MouseEvent) {
81+ if (!visible) return;
82+
83+ const target = event.target as HTMLElement;
84+ const omniboxContainer = document.getElementById('dumber-omnibox-root');
85+
86+ // Close if clicking outside the omnibox container
87+ if (omniboxContainer && !omniboxContainer.contains(target)) {
88+ omniboxStore.close();
89+ }
90+ }
91+
92+ onMount(() => {
93+ console.log('🔧 Omnibox component mounted');
94+
95+ // Add global click listener
96+ document.addEventListener('click', handleGlobalClick, true);
97+
98+ try {
99+ // Set up global API for Go bridge
100+ window.__dumber_omnibox = {
101+ setSuggestions: (suggestions) => {
102+ try {
103+ console.log('📋 setSuggestions called with:', suggestions?.length || 0, 'items');
104+ omniboxBridge.setSuggestions(suggestions);
105+ } catch (error) {
106+ console.error('Error in setSuggestions:', error);
107+ }
108+ },
109+ toggle: () => {
110+ try {
111+ console.log('🔄 Omnibox toggle called, current visible:', omniboxStore.visible);
112+ omniboxStore.toggle();
113+ console.log('🔄 After toggle, visible:', omniboxStore.visible);
114+ } catch (error) {
115+ console.error('Error in toggle:', error);
116+ }
117+ },
118+ open: (mode, query) => {
119+ try {
120+ console.log('🚪 Omnibox open called with mode:', mode, 'query:', query);
121+ omniboxStore.open(mode as any, query);
122+ console.log('🚪 After open, visible:', omniboxStore.visible, 'mode:', omniboxStore.mode);
123+ } catch (error) {
124+ console.error('Error in open:', error);
125+ }
126+ },
127+ close: () => {
128+ try {
129+ console.log('🚪 Omnibox close called');
130+ omniboxStore.close();
131+ } catch (error) {
132+ console.error('Error in close:', error);
133+ }
134+ },
135+ findQuery: (query) => {
136+ try {
137+ console.log('🔍 Find query called with:', query);
138+ if (omniboxStore.mode !== 'find') {
139+ omniboxStore.setMode('find');
140+ }
141+ if (!omniboxStore.visible) {
142+ omniboxStore.setVisible(true);
143+ }
144+ omniboxStore.setInputValue(query || '');
145+ // Find will be triggered by input value change
146+ } catch (error) {
147+ console.error('Error in findQuery:', error);
148+ }
149+ }
150+ };
151+
152+ console.log('✅ Omnibox API exposed:', Object.keys(window.__dumber_omnibox));
153+ } catch (error) {
154+ console.error('❌ Failed to set up omnibox global API:', error);
155+ }
156+ });
157+
158+ onDestroy(() => {
159+ // Cleanup global click listener
160+ document.removeEventListener('click', handleGlobalClick, true);
161+
162+ // Reset omnibox state completely
163+ omniboxStore.reset();
164+
165+ // Cleanup global API
166+ if (window.__dumber_omnibox) {
167+ delete window.__dumber_omnibox;
168+ }
169+ });
170+</script>
171+
172+<!-- Overlay container -->
173+<div
174+ class="fixed inset-0 z-[2147483647]"
175+ style="display: {visible ? 'block' : 'none'}"
176+ onmousedown={handleOverlayClick}
177+ role="presentation"
178+>
179+ <!-- Main omnibox container -->
180+ <div
181+ bind:this={boxElement}
182+ class="w-[min(90vw,720px)] mx-auto mt-[8vh] p-3
183+ bg-[#1b1b1b] border border-[#444] rounded-lg shadow-[0_10px_30px_rgba(0,0,0,0.6)]
184+ text-[#eee]
185+ sm:w-[95vw] sm:p-2
186+ xl:w-[min(90vw,840px)]"
187+ style="font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif"
188+ onmousedown={handleBoxClick}
189+ onmouseenter={handleMouseEnter}
190+ role="dialog"
191+ tabindex="-1"
192+ aria-modal="true"
193+ aria-label={mode === 'find' ? 'Find in page' : 'Omnibox search'}
194+ >
195+ <!-- Input component -->
196+ <OmniboxInput bind:inputElement />
197+
198+ <!-- Content based on mode -->
199+ {#if mode === 'omnibox'}
200+ <OmniboxSuggestions />
201+ {:else if mode === 'find'}
202+ <OmniboxFind />
203+ {/if}
204+ </div>
205+</div>
206+
207+<style>
208+ /* Additional custom styles if needed */
209+</style>⏎̸
A gui/src/components/omnibox/OmniboxFind.svelte +102 -0
@@ -0,0 +1,102 @@
1+<!--
2+ Omnibox Find Component
3+
4+ Displays find results and match navigation
5+-->
6+<script lang="ts">
7+ import { omniboxStore } from './stores.svelte.ts';
8+ import { revealMatch } from './find';
9+
10+ // Reactive state
11+ let matches = $derived(omniboxStore.matches);
12+ let selectedIndex = $derived(omniboxStore.selectedIndex);
13+ let hasContent = $state(false);
14+
15+ // Update hasContent when matches change
16+ $effect(() => {
17+ hasContent = omniboxStore.mode === 'find' && matches.length > 0;
18+ });
19+
20+ // Computed total count
21+ let totalMatches = $derived(matches.length);
22+
23+ // Handle match item mouse enter
24+ function handleItemMouseEnter(index: number) {
25+ omniboxStore.setSelectedIndex(index);
26+ omniboxStore.setFaded(true);
27+ scrollToSelection();
28+ }
29+
30+ // Handle match item click
31+ function handleItemClick(index: number) {
32+ omniboxStore.setSelectedIndex(index);
33+ revealMatch(index);
34+ omniboxStore.close();
35+ }
36+
37+ // Scroll list to show selected item
38+ function scrollToSelection() {
39+ const selectedItem = document.getElementById(`find-item-${selectedIndex}`);
40+ if (selectedItem && selectedItem.scrollIntoView) {
41+ try {
42+ selectedItem.scrollIntoView({ block: 'nearest' });
43+ } catch {
44+ selectedItem.scrollIntoView();
45+ }
46+ }
47+ }
48+
49+ // Watch for selection changes to scroll
50+ $effect(() => {
51+ if (selectedIndex >= 0) {
52+ scrollToSelection();
53+ }
54+ });
55+</script>
56+
57+<div
58+ id="find-list"
59+ class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"
60+ role="listbox"
61+ aria-label="Find results"
62+>
63+ <!-- Header with match count -->
64+ <div
65+ class="px-2.5 py-1.5 text-xs text-[#bbb]
66+ border-b border-[#2a2a2a]"
67+ >
68+ {#if totalMatches > 0}
69+ {totalMatches} match{totalMatches === 1 ? '' : 'es'}
70+ {:else}
71+ No matches
72+ {/if}
73+ </div>
74+
75+ <!-- Match results -->
76+ {#if hasContent}
77+ {#each matches as match, index (index)}
78+ {@const isSelected = index === selectedIndex}
79+
80+ <div
81+ id="find-item-{index}"
82+ class="px-2.5 py-2 cursor-pointer
83+ border-b border-[#2a2a2a] last:border-b-0
84+ {isSelected ? 'bg-[#0a0a0a]' : ''}"
85+ role="option"
86+ tabindex="-1"
87+ aria-selected={isSelected}
88+ onmouseenter={() => handleItemMouseEnter(index)}
89+ onclick={() => handleItemClick(index)}
90+ >
91+ <!-- Match context -->
92+ <div
93+ class="text-[#ddd] text-sm
94+ whitespace-nowrap overflow-hidden text-ellipsis"
95+ >
96+ {match.context || ''}
97+ </div>
98+ </div>
99+ {/each}
100+ {/if}
101+</div>
102+
A gui/src/components/omnibox/OmniboxInput.svelte +209 -0
@@ -0,0 +1,209 @@
1+<!--
2+ Omnibox Input Component
3+
4+ Input field with keyboard handling and debounced search
5+-->
6+<script lang="ts">
7+ import { omniboxStore } from './stores.svelte.ts';
8+ import { debouncedQuery, omniboxBridge } from './messaging';
9+ import { findInPage } from './find';
10+
11+ // Reactive state
12+ let mode = $derived(omniboxStore.mode);
13+ let inputValue = $derived(omniboxStore.inputValue);
14+ let selectedIndex = $derived(omniboxStore.selectedIndex);
15+ let suggestions = $derived(omniboxStore.suggestions);
16+ let matches = $derived(omniboxStore.matches);
17+
18+ // Input element ref (props for parent to bind)
19+ interface Props {
20+ inputElement?: HTMLInputElement;
21+ }
22+
23+ let { inputElement = $bindable() }: Props = $props();
24+
25+ // Computed placeholder
26+ let placeholder = $derived(
27+ mode === 'find' ? 'Find in page…' : 'Type URL or search…'
28+ );
29+
30+ // Handle input changes
31+ function handleInput(event: Event) {
32+ const target = event.target as HTMLInputElement;
33+ const value = target.value || '';
34+
35+ omniboxStore.setInputValue(value);
36+ omniboxStore.setFaded(false);
37+
38+ if (mode === 'omnibox') {
39+ // Debounced search for omnibox
40+ debouncedQuery(value);
41+ } else if (mode === 'find') {
42+ // Immediate find for search
43+ findInPage(value);
44+ }
45+ }
46+
47+ // Handle key events
48+ function handleKeyDown(event: KeyboardEvent) {
49+ switch (event.key) {
50+ case 'Escape':
51+ omniboxStore.close();
52+ break;
53+
54+ case 'Enter':
55+ event.preventDefault();
56+ handleEnterKey(event);
57+ break;
58+
59+ case 'ArrowDown':
60+ case 'ArrowUp':
61+ event.preventDefault();
62+ event.stopPropagation();
63+ handleArrowKeys(event.key);
64+ break;
65+
66+ default:
67+ // Any other key should restore full opacity
68+ omniboxStore.setFaded(false);
69+ break;
70+ }
71+ }
72+
73+ // Handle Enter key based on mode
74+ function handleEnterKey(event: KeyboardEvent) {
75+ if (mode === 'omnibox') {
76+ const selectedSuggestion = selectedIndex >= 0 ? suggestions[selectedIndex] : null;
77+ const url = selectedSuggestion?.url || inputValue || '';
78+
79+ if (url) {
80+ omniboxBridge.navigate(url);
81+ omniboxStore.close();
82+ }
83+ } else if (mode === 'find') {
84+ if (event.shiftKey) {
85+ // Shift+Enter: jump to previous match
86+ jumpToMatch(-1);
87+ } else if (event.altKey) {
88+ // Alt+Enter: center on current match but keep overlay open
89+ revealCurrentMatch();
90+ } else {
91+ // Enter: center and close
92+ revealCurrentMatch();
93+ omniboxStore.close();
94+ }
95+ }
96+ }
97+
98+ // Handle arrow key navigation
99+ function handleArrowKeys(key: string) {
100+ const totalItems = mode === 'omnibox' ? suggestions.length : matches.length;
101+
102+ if (totalItems > 0) {
103+ if (key === 'ArrowDown') {
104+ omniboxStore.selectNext();
105+ } else {
106+ omniboxStore.selectPrevious();
107+ }
108+
109+ // Fade overlay while navigating results
110+ omniboxStore.setFaded(true);
111+
112+ // For find mode, reveal the current match
113+ if (mode === 'find') {
114+ revealCurrentMatch();
115+ }
116+ }
117+ }
118+
119+ // Jump to match (for find mode)
120+ function jumpToMatch(delta: number) {
121+ if (mode !== 'find' || matches.length === 0) return;
122+
123+ const currentIndex = selectedIndex || 0;
124+ const newIndex = ((currentIndex + delta) % matches.length + matches.length) % matches.length;
125+
126+ omniboxStore.setSelectedIndex(newIndex);
127+ revealCurrentMatch();
128+ }
129+
130+ // Reveal current match (for find mode)
131+ function revealCurrentMatch() {
132+ if (mode !== 'find' || selectedIndex < 0 || !matches[selectedIndex]) return;
133+
134+ // Remove previous active class
135+ const prevMatch = matches[omniboxStore.activeIndex];
136+ if (prevMatch?.element?.classList) {
137+ prevMatch.element.classList.remove('find-active');
138+ }
139+
140+ // Add active class to current match
141+ const currentMatch = matches[selectedIndex];
142+ if (currentMatch?.element?.classList) {
143+ currentMatch.element.classList.add('find-active');
144+ omniboxStore.setActiveIndex(selectedIndex);
145+
146+ // Scroll into view
147+ try {
148+ currentMatch.element.scrollIntoView({
149+ block: 'center',
150+ inline: 'nearest'
151+ });
152+ } catch {
153+ currentMatch.element.scrollIntoView();
154+ }
155+ }
156+ }
157+
158+ // Handle mouse events
159+ function handleMouseDown() {
160+ omniboxStore.setFaded(false);
161+ }
162+
163+ function handleClick() {
164+ omniboxStore.setFaded(false);
165+ }
166+
167+ function handleFocus() {
168+ omniboxStore.setFaded(false);
169+ }
170+
171+ function handleMouseEnter() {
172+ if (omniboxStore.visible && inputElement) {
173+ try {
174+ inputElement.focus({ preventScroll: true });
175+ inputElement.select();
176+ } catch {
177+ inputElement.focus();
178+ }
179+ }
180+ }
181+</script>
182+
183+<input
184+ bind:this={inputElement}
185+ type="text"
186+ {placeholder}
187+ value={inputValue}
188+ class="w-full box-border px-3 py-2.5
189+ bg-[#121212] text-[#eee]
190+ border border-[#555] rounded-md
191+ focus:outline-none
192+ text-base
193+ sm:px-2.5 sm:py-2 sm:text-sm
194+ xl:text-lg xl:px-3 xl:py-3"
195+ oninput={handleInput}
196+ onkeydown={handleKeyDown}
197+ onmousedown={handleMouseDown}
198+ onclick={handleClick}
199+ onfocus={handleFocus}
200+ onmouseenter={handleMouseEnter}
201+ autocomplete="off"
202+ spellcheck="false"
203+ role="combobox"
204+ aria-expanded={omniboxStore.hasContent}
205+ aria-controls="omnibox-list"
206+ aria-haspopup="listbox"
207+ aria-owns="omnibox-list"
208+ aria-activedescendant={selectedIndex >= 0 ? `omnibox-item-${selectedIndex}` : undefined}
209+/>⏎̸
A gui/src/components/omnibox/OmniboxSuggestions.svelte +144 -0
@@ -0,0 +1,144 @@
1+<!--
2+ Omnibox Suggestions Component
3+
4+ Displays search suggestions with favicons and URL highlighting
5+-->
6+<script lang="ts">
7+ import { omniboxStore } from './stores.svelte.ts';
8+ import { omniboxBridge } from './messaging';
9+
10+ // Reactive state
11+ let suggestions = $derived(omniboxStore.suggestions);
12+ let selectedIndex = $derived(omniboxStore.selectedIndex);
13+ let hasContent = $state(false);
14+
15+ // Update hasContent when suggestions change
16+ $effect(() => {
17+ hasContent = omniboxStore.mode === 'omnibox' && suggestions.length > 0;
18+ });
19+
20+ // Handle suggestion item mouse enter
21+ function handleItemMouseEnter(index: number) {
22+ omniboxStore.setSelectedIndex(index);
23+ omniboxStore.setFaded(true);
24+ scrollToSelection();
25+ }
26+
27+ // Handle suggestion item click
28+ function handleItemClick(suggestion: any) {
29+ omniboxBridge.navigate(suggestion.url);
30+ omniboxStore.close();
31+ }
32+
33+ // Scroll list to show selected item
34+ function scrollToSelection() {
35+ const selectedItem = document.getElementById(`omnibox-item-${selectedIndex}`);
36+ if (selectedItem && selectedItem.scrollIntoView) {
37+ try {
38+ selectedItem.scrollIntoView({ block: 'nearest' });
39+ } catch {
40+ selectedItem.scrollIntoView();
41+ }
42+ }
43+ }
44+
45+ // Parse URL for display
46+ function parseUrl(url: string) {
47+ try {
48+ const urlObj = new URL(url, window.location.href);
49+ return {
50+ domain: urlObj.hostname,
51+ path: (urlObj.pathname || '') + (urlObj.search || '') + (urlObj.hash || '')
52+ };
53+ } catch {
54+ return {
55+ domain: url || '',
56+ path: ''
57+ };
58+ }
59+ }
60+
61+ // Handle favicon error
62+ function handleFaviconError(event: Event) {
63+ const target = event.target as HTMLImageElement;
64+ const chip = target.parentElement;
65+ if (chip) {
66+ chip.style.display = 'none';
67+ }
68+ }
69+
70+ // Watch for selection changes to scroll
71+ $effect(() => {
72+ if (selectedIndex >= 0) {
73+ scrollToSelection();
74+ }
75+ });
76+</script>
77+
78+{#if hasContent}
79+ <div
80+ id="omnibox-list"
81+ class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"
82+ role="listbox"
83+ aria-label="Search suggestions"
84+ >
85+ {#each suggestions as suggestion, index (suggestion.url)}
86+ {@const { domain, path } = parseUrl(suggestion.url)}
87+ {@const isSelected = index === selectedIndex}
88+
89+ <div
90+ id="omnibox-item-{index}"
91+ class="px-2.5 py-2 flex gap-2.5 items-center cursor-pointer
92+ border-b border-[#2a2a2a] last:border-b-0
93+ {isSelected ? 'bg-[#0a0a0a]' : ''}"
94+ role="option"
95+ tabindex="-1"
96+ aria-selected={isSelected}
97+ onmouseenter={() => handleItemMouseEnter(index)}
98+ onclick={() => handleItemClick(suggestion)}
99+ >
100+ <!-- Favicon chip -->
101+ {#if suggestion.favicon}
102+ <div
103+ class="flex-shrink-0 w-5 h-5 rounded-full
104+ bg-[#ccc] border border-black/12
105+ shadow-sm flex items-center justify-center"
106+ >
107+ <img
108+ src={suggestion.favicon}
109+ alt=""
110+ class="w-4 h-4 filter brightness-105 contrast-103"
111+ loading="lazy"
112+ onerror={handleFaviconError}
113+ style="image-rendering: -webkit-optimize-contrast;"
114+ />
115+ </div>
116+ {/if}
117+
118+ <!-- URL text with gradient fade -->
119+ <div
120+ class="flex-1 min-w-0 flex gap-2 items-center
121+ whitespace-nowrap overflow-hidden
122+ [mask-image:linear-gradient(90deg,black_85%,transparent_100%)]
123+ [-webkit-mask-image:linear-gradient(90deg,black_85%,transparent_100%)]"
124+ >
125+ <!-- Domain -->
126+ <span class="text-[#e6e6e6] opacity-95">
127+ {domain}
128+ </span>
129+
130+ <!-- Separator -->
131+ <span class="text-[#777]">
132+ {' | '}
133+ </span>
134+
135+ <!-- Path -->
136+ <span class="text-[#99aadd]">
137+ {path || '/'}
138+ </span>
139+ </div>
140+ </div>
141+ {/each}
142+ </div>
143+{/if}
144+
A gui/src/components/omnibox/find.ts +202 -0
@@ -0,0 +1,202 @@
1+/**
2+ * Find in Page Functionality
3+ *
4+ * Text search and highlighting implementation
5+ */
6+
7+import type { FindMatch, HighlightNode } from './types';
8+import { omniboxStore } from './stores.svelte.ts';
9+
10+/**
11+ * Find text in the current page and highlight matches
12+ */
13+export function findInPage(query: string): void {
14+ // Clear previous highlights
15+ omniboxStore.clearHighlights();
16+
17+ const trimmedQuery = (query || '').trim();
18+ if (!trimmedQuery) {
19+ return;
20+ }
21+
22+ const body = document.body;
23+ if (!body) {
24+ return;
25+ }
26+
27+ // Get omnibox root to exclude from search
28+ const omniboxRoot = document.getElementById('dumber-omnibox-root');
29+ const matches: FindMatch[] = [];
30+ const maxMatches = omniboxStore.config.maxMatches;
31+
32+ // Create tree walker to find text nodes
33+ const walker = document.createTreeWalker(
34+ body,
35+ NodeFilter.SHOW_TEXT,
36+ {
37+ acceptNode(node: Text): number {
38+ // Skip empty or whitespace-only nodes
39+ if (!node.nodeValue || !node.nodeValue.trim()) {
40+ return NodeFilter.FILTER_REJECT;
41+ }
42+
43+ const parentElement = node.parentElement;
44+ if (!parentElement) {
45+ return NodeFilter.FILTER_REJECT;
46+ }
47+
48+ // Skip omnibox content
49+ if (omniboxRoot && omniboxRoot.contains(parentElement)) {
50+ return NodeFilter.FILTER_REJECT;
51+ }
52+
53+ // Skip script, style, and noscript tags
54+ const tagName = parentElement.tagName;
55+ if (tagName === 'SCRIPT' || tagName === 'STYLE' || tagName === 'NOSCRIPT') {
56+ return NodeFilter.FILTER_REJECT;
57+ }
58+
59+ // Skip hidden elements
60+ const computedStyle = getComputedStyle(parentElement);
61+ if (computedStyle.visibility === 'hidden' || computedStyle.display === 'none') {
62+ return NodeFilter.FILTER_REJECT;
63+ }
64+
65+ return NodeFilter.FILTER_ACCEPT;
66+ }
67+ }
68+ );
69+
70+ const queryLowerCase = trimmedQuery.toLowerCase();
71+
72+ // Process each text node
73+ while (walker.nextNode() && matches.length < maxMatches) {
74+ const textNode = walker.currentNode as Text;
75+ let nodeValue = textNode.nodeValue || '';
76+ let searchIndex = 0;
77+
78+ // Find all matches in this text node
79+ while (nodeValue && matches.length < maxMatches) {
80+ const matchIndex = nodeValue.toLowerCase().indexOf(queryLowerCase, searchIndex);
81+ if (matchIndex === -1) {
82+ break;
83+ }
84+
85+ // Create text nodes for before, match, and after
86+ const beforeText = nodeValue.slice(0, matchIndex);
87+ const matchText = nodeValue.slice(matchIndex, matchIndex + trimmedQuery.length);
88+ const afterText = nodeValue.slice(matchIndex + trimmedQuery.length);
89+
90+ // Create elements
91+ const beforeNode = document.createTextNode(beforeText);
92+ const matchTextNode = document.createTextNode(matchText);
93+ const highlightSpan = document.createElement('span');
94+
95+ // Style the highlight span
96+ highlightSpan.className = 'find-highlight';
97+ highlightSpan.appendChild(matchTextNode);
98+
99+ // Insert into DOM
100+ const parent = textNode.parentNode;
101+ if (!parent) {
102+ break;
103+ }
104+
105+ parent.insertBefore(beforeNode, textNode);
106+ parent.insertBefore(highlightSpan, textNode);
107+ textNode.nodeValue = afterText;
108+
109+ // Track highlight node for cleanup
110+ const highlightNode: HighlightNode = {
111+ span: highlightSpan,
112+ text: matchTextNode
113+ };
114+ omniboxStore.addHighlightNode(highlightNode);
115+
116+ // Create match context
117+ const leftContext = beforeText.slice(-30);
118+ let rightContext = afterText.slice(0, 30);
119+
120+ // Try to break at word boundaries
121+ const boundaryMatch = rightContext.match(/[.,;:-]/);
122+ if (boundaryMatch && boundaryMatch.index !== undefined) {
123+ rightContext = rightContext.slice(0, boundaryMatch.index + 1);
124+ }
125+
126+ const context = (leftContext + matchText + rightContext)
127+ .replace(/\s+/g, ' ')
128+ .trim();
129+
130+ // Add to matches
131+ const findMatch: FindMatch = {
132+ element: highlightSpan,
133+ context
134+ };
135+ matches.push(findMatch);
136+
137+ // Continue searching in the remaining text
138+ nodeValue = afterText;
139+ searchIndex = 0;
140+ }
141+ }
142+
143+ // Update store with matches
144+ omniboxStore.updateMatches(matches);
145+
146+ // Reveal first match if any
147+ if (matches.length > 0) {
148+ revealMatch(0);
149+ }
150+}
151+
152+/**
153+ * Reveal a specific match by index
154+ */
155+export function revealMatch(index: number): void {
156+ const matches = omniboxStore.matches;
157+ if (index < 0 || index >= matches.length) {
158+ return;
159+ }
160+
161+ // Remove previous active class
162+ const prevActiveIndex = omniboxStore.activeIndex;
163+ if (prevActiveIndex >= 0 && matches[prevActiveIndex]) {
164+ const prevElement = matches[prevActiveIndex].element;
165+ if (prevElement.classList) {
166+ prevElement.classList.remove('find-active');
167+ }
168+ }
169+
170+ // Add active class to current match
171+ const currentMatch = matches[index];
172+ if (currentMatch && currentMatch.element.classList) {
173+ currentMatch.element.classList.add('find-active');
174+ omniboxStore.setActiveIndex(index);
175+
176+ // Scroll into view
177+ try {
178+ currentMatch.element.scrollIntoView({
179+ block: 'center',
180+ inline: 'nearest'
181+ });
182+ } catch {
183+ currentMatch.element.scrollIntoView();
184+ }
185+ }
186+}
187+
188+/**
189+ * Jump to next/previous match
190+ */
191+export function jumpToMatch(delta: number): void {
192+ const matches = omniboxStore.matches;
193+ if (matches.length === 0) {
194+ return;
195+ }
196+
197+ const currentIndex = omniboxStore.selectedIndex || 0;
198+ const newIndex = ((currentIndex + delta) % matches.length + matches.length) % matches.length;
199+
200+ omniboxStore.setSelectedIndex(newIndex);
201+ revealMatch(newIndex);
202+}⏎̸
A gui/src/components/omnibox/index.ts +16 -0
@@ -0,0 +1,16 @@
1+/**
2+ * Omnibox Component Exports
3+ *
4+ * Main entry point for omnibox functionality
5+ */
6+
7+export { default as Omnibox } from './Omnibox.svelte';
8+export { default as OmniboxInput } from './OmniboxInput.svelte';
9+export { default as OmniboxSuggestions } from './OmniboxSuggestions.svelte';
10+export { default as OmniboxFind } from './OmniboxFind.svelte';
11+
12+export { omniboxStore } from './stores.svelte.ts';
13+export { omniboxBridge } from './messaging';
14+export { findInPage, revealMatch, jumpToMatch } from './find';
15+
16+export type * from './types';⏎̸
A gui/src/components/omnibox/messaging.ts +95 -0
@@ -0,0 +1,95 @@
1+/**
2+ * Omnibox Message Bridge
3+ *
4+ * TypeScript interface for Go-JavaScript communication
5+ */
6+
7+import type { OmniboxMessage, OmniboxMessageBridge, Suggestion } from './types';
8+import { omniboxStore } from './stores.svelte.ts';
9+
10+export class OmniboxBridge implements OmniboxMessageBridge {
11+ /**
12+ * Send message to Go backend via WebKit message handler
13+ */
14+ postMessage(msg: OmniboxMessage): void {
15+ try {
16+ console.log('📨 Sending message to Go backend:', msg);
17+ const messageHandler = window.webkit?.messageHandlers?.dumber;
18+ if (messageHandler && messageHandler.postMessage) {
19+ const jsonMsg = JSON.stringify(msg);
20+ console.log('📨 JSON message:', jsonMsg);
21+ messageHandler.postMessage(jsonMsg);
22+ } else {
23+ console.warn('WebKit message handler not available');
24+ }
25+ } catch (error) {
26+ console.error('Failed to send message to Go backend:', error);
27+ }
28+ }
29+
30+ /**
31+ * Update suggestions from Go backend
32+ */
33+ setSuggestions(suggestions: Suggestion[]): void {
34+ omniboxStore.updateSuggestions(suggestions);
35+ }
36+
37+ /**
38+ * Handle navigation request
39+ */
40+ navigate(url: string): void {
41+ console.log('🚀 Omnibox navigate called with:', url);
42+ this.postMessage({
43+ type: 'navigate',
44+ url
45+ });
46+ }
47+
48+ /**
49+ * Handle search query
50+ */
51+ query(searchTerm: string, limit?: number): void {
52+ this.postMessage({
53+ type: 'query',
54+ q: searchTerm,
55+ limit: limit || omniboxStore.config.defaultLimit
56+ });
57+ }
58+}
59+
60+// Singleton instance
61+export const omniboxBridge = new OmniboxBridge();
62+
63+/**
64+ * Debounced query function for search input
65+ */
66+export function debouncedQuery(searchTerm: string): void {
67+ omniboxStore.clearDebounceTimer();
68+
69+ const timerId = window.setTimeout(() => {
70+ omniboxBridge.query(searchTerm);
71+ }, omniboxStore.config.debounceDelay);
72+
73+ omniboxStore.setDebounceTimer(timerId);
74+}
75+
76+// Extend global window interface for Go bridge compatibility
77+declare global {
78+ interface Window {
79+ webkit?: {
80+ messageHandlers?: {
81+ dumber?: {
82+ postMessage: (message: string) => void;
83+ };
84+ };
85+ };
86+ // Omnibox API for Go bridge
87+ __dumber_omnibox?: {
88+ setSuggestions: (suggestions: Suggestion[]) => void;
89+ toggle: () => void;
90+ open: (mode?: string, query?: string) => void;
91+ close: () => void;
92+ findQuery: (query: string) => void;
93+ };
94+ }
95+}⏎̸
A gui/src/components/omnibox/stores.svelte.ts +215 -0
@@ -0,0 +1,215 @@
1+/**
2+ * Omnibox State Management
3+ *
4+ * Svelte 5 rune-based state management for omnibox functionality
5+ */
6+
7+import type {
8+ OmniboxMode,
9+ Suggestion,
10+ FindMatch,
11+ HighlightNode,
12+ OmniboxConfig
13+} from './types';
14+
15+// Default configuration
16+const DEFAULT_CONFIG: Required<OmniboxConfig> = {
17+ maxMatches: 2000,
18+ debounceDelay: 120,
19+ defaultLimit: 10
20+};
21+
22+// Global state using Svelte 5 runes
23+let visible = $state(false);
24+let mode = $state<OmniboxMode>('omnibox');
25+let suggestions = $state<Suggestion[]>([]);
26+let matches = $state<FindMatch[]>([]);
27+let selectedIndex = $state(-1);
28+let activeIndex = $state(-1);
29+let faded = $state(false);
30+let inputValue = $state('');
31+let highlightNodes = $state<HighlightNode[]>([]);
32+let prevOverflow = $state('');
33+let debounceTimer = $state(0);
34+
35+// Configuration
36+let config = $state<Required<OmniboxConfig>>(DEFAULT_CONFIG);
37+
38+export const omniboxStore = {
39+ // State getters
40+ get visible() { return visible; },
41+ get mode() { return mode; },
42+ get suggestions() { return suggestions; },
43+ get matches() { return matches; },
44+ get selectedIndex() { return selectedIndex; },
45+ get activeIndex() { return activeIndex; },
46+ get faded() { return faded; },
47+ get inputValue() { return inputValue; },
48+ get highlightNodes() { return highlightNodes; },
49+ get prevOverflow() { return prevOverflow; },
50+ get config() { return config; },
51+
52+ // Computed getters
53+ get hasContent() {
54+ return (mode === 'omnibox' && suggestions.length > 0) ||
55+ (mode === 'find' && matches.length > 0);
56+ },
57+ get totalItems() {
58+ return mode === 'omnibox' ? suggestions.length : matches.length;
59+ },
60+ get selectedItem() {
61+ if (selectedIndex < 0) return null;
62+ return mode === 'omnibox' ? suggestions[selectedIndex] : matches[selectedIndex];
63+ },
64+
65+ // Actions
66+ setVisible(value: boolean) {
67+ if (value && !visible) {
68+ // Store scroll state when opening
69+ prevOverflow = document.documentElement.style.overflow;
70+ document.documentElement.style.overflow = 'hidden';
71+ } else if (!value && visible) {
72+ // Restore scroll state when closing
73+ document.documentElement.style.overflow = prevOverflow || '';
74+ prevOverflow = '';
75+
76+ // Clear find highlights when closing
77+ if (mode === 'find') {
78+ this.clearHighlights();
79+ }
80+ }
81+ visible = value;
82+ },
83+
84+ setMode(newMode: OmniboxMode) {
85+ // Clear previous mode's state
86+ if (newMode === 'omnibox') {
87+ // Switching to omnibox mode - clear find state
88+ this.clearHighlights();
89+ } else if (newMode === 'find') {
90+ // Switching to find mode - clear suggestions
91+ this.updateSuggestions([]);
92+ }
93+
94+ mode = newMode;
95+ selectedIndex = -1;
96+ faded = false;
97+ inputValue = ''; // Clear input for fresh start
98+ },
99+
100+ setInputValue(value: string) {
101+ inputValue = value;
102+ },
103+
104+ setFaded(value: boolean) {
105+ faded = value;
106+ },
107+
108+ setSelectedIndex(index: number) {
109+ const maxIndex = this.totalItems - 1;
110+ if (maxIndex < 0) {
111+ selectedIndex = -1;
112+ } else {
113+ selectedIndex = Math.max(0, Math.min(index, maxIndex));
114+ }
115+ },
116+
117+ selectNext() {
118+ if (this.totalItems === 0) return;
119+ const newIndex = selectedIndex < 0 ? 0 : (selectedIndex + 1) % this.totalItems;
120+ this.setSelectedIndex(newIndex);
121+ faded = true;
122+ },
123+
124+ selectPrevious() {
125+ if (this.totalItems === 0) return;
126+ const newIndex = selectedIndex <= 0 ? this.totalItems - 1 : selectedIndex - 1;
127+ this.setSelectedIndex(newIndex);
128+ faded = true;
129+ },
130+
131+ open(newMode: OmniboxMode = 'omnibox', initialQuery?: string) {
132+ this.setMode(newMode);
133+ this.setVisible(true);
134+ if (typeof initialQuery === 'string') {
135+ inputValue = initialQuery;
136+ }
137+ },
138+
139+ close() {
140+ this.setVisible(false);
141+ faded = false;
142+ },
143+
144+ toggle() {
145+ this.setVisible(!visible);
146+ },
147+
148+ updateSuggestions(newSuggestions: Suggestion[]) {
149+ suggestions = Array.isArray(newSuggestions) ? newSuggestions : [];
150+ selectedIndex = -1;
151+ },
152+
153+ updateMatches(newMatches: FindMatch[]) {
154+ matches = Array.isArray(newMatches) ? newMatches : [];
155+ selectedIndex = newMatches.length > 0 ? 0 : -1;
156+ },
157+
158+ clearHighlights() {
159+ try {
160+ highlightNodes.forEach(({ span, text }) => {
161+ const parent = span.parentNode;
162+ if (parent) {
163+ parent.replaceChild(text, span);
164+ parent.normalize();
165+ }
166+ });
167+ } catch (error) {
168+ console.warn('Error clearing highlights:', error);
169+ }
170+ highlightNodes = [];
171+ matches = [];
172+ selectedIndex = -1;
173+ activeIndex = -1;
174+ },
175+
176+ addHighlightNode(node: HighlightNode) {
177+ highlightNodes = [...highlightNodes, node];
178+ },
179+
180+ setActiveIndex(index: number) {
181+ activeIndex = index;
182+ },
183+
184+ setDebounceTimer(timerId: number) {
185+ if (debounceTimer) {
186+ clearTimeout(debounceTimer);
187+ }
188+ debounceTimer = timerId;
189+ },
190+
191+ clearDebounceTimer() {
192+ if (debounceTimer) {
193+ clearTimeout(debounceTimer);
194+ debounceTimer = 0;
195+ }
196+ },
197+
198+ updateConfig(newConfig: Partial<OmniboxConfig>) {
199+ config = { ...config, ...newConfig };
200+ },
201+
202+ reset() {
203+ visible = false;
204+ mode = 'omnibox';
205+ suggestions = [];
206+ matches = [];
207+ selectedIndex = -1;
208+ activeIndex = -1;
209+ faded = false;
210+ inputValue = '';
211+ prevOverflow = '';
212+ this.clearHighlights();
213+ this.clearDebounceTimer();
214+ }
215+};⏎̸
A gui/src/components/omnibox/types.ts +63 -0
@@ -0,0 +1,63 @@
1+/**
2+ * Omnibox Component Types
3+ *
4+ * TypeScript interfaces for omnibox/find functionality that match Go structs
5+ */
6+
7+export interface Suggestion {
8+ url: string;
9+ favicon?: string;
10+}
11+
12+export interface OmniboxMessage {
13+ type: 'navigate' | 'query';
14+ url?: string;
15+ q?: string;
16+ limit?: number;
17+}
18+
19+export interface FindMatch {
20+ element: HTMLElement;
21+ context: string;
22+}
23+
24+export interface HighlightNode {
25+ span: HTMLElement;
26+ text: Text;
27+}
28+
29+export type OmniboxMode = 'omnibox' | 'find';
30+
31+export interface OmniboxConfig {
32+ maxMatches?: number;
33+ debounceDelay?: number;
34+ defaultLimit?: number;
35+}
36+
37+export interface OmniboxState {
38+ visible: boolean;
39+ mode: OmniboxMode;
40+ suggestions: Suggestion[];
41+ matches: FindMatch[];
42+ selectedIndex: number;
43+ activeIndex: number;
44+ faded: boolean;
45+ inputValue: string;
46+ highlightNodes: HighlightNode[];
47+ prevOverflow: string;
48+}
49+
50+// Message bridge interface for Go communication
51+export interface OmniboxMessageBridge {
52+ postMessage(msg: OmniboxMessage): void;
53+ setSuggestions(suggestions: Suggestion[]): void;
54+}
55+
56+// Global API for Go bridge
57+export interface OmniboxAPI {
58+ setSuggestions: (suggestions: Suggestion[]) => void;
59+ toggle: () => void;
60+ open: (mode?: OmniboxMode, query?: string) => void;
61+ close: () => void;
62+ findQuery: (query: string) => void;
63+}⏎̸
A gui/src/components/toast/Toast.svelte +121 -0
@@ -0,0 +1,121 @@
1+<script lang="ts">
2+ import { onMount, onDestroy } from 'svelte';
3+
4+ interface ToastProps {
5+ id: number;
6+ message: string;
7+ duration?: number;
8+ type?: 'info' | 'success' | 'error';
9+ onDismiss: (id: number) => void;
10+ }
11+
12+ let { id, message, duration = 2500, type = 'info', onDismiss }: ToastProps = $props();
13+
14+ let isExiting = $state(false);
15+ let timeoutId: number | undefined;
16+
17+ onMount(() => {
18+ timeoutId = window.setTimeout(() => {
19+ dismiss();
20+ }, duration);
21+ });
22+
23+ onDestroy(() => {
24+ if (timeoutId) {
25+ window.clearTimeout(timeoutId);
26+ timeoutId = undefined;
27+ }
28+ });
29+
30+ function dismiss() {
31+ if (timeoutId) {
32+ window.clearTimeout(timeoutId);
33+ timeoutId = undefined;
34+ }
35+ isExiting = true;
36+ // Allow time for exit animation before removing from parent
37+ setTimeout(() => {
38+ onDismiss(id);
39+ }, 250);
40+ }
41+
42+ function handleClick() {
43+ dismiss();
44+ }
45+</script>
46+
47+<button
48+ class="toast-base {type === 'success' ? 'toast-success' : type === 'error' ? 'toast-error' : 'toast-info'} {isExiting ? 'exiting' : ''}"
49+ onclick={handleClick}
50+ data-toast-id={id}
51+ type="button"
52+ aria-label="Dismiss notification"
53+ title="Click to dismiss"
54+>
55+ <div class="w-full min-w-36 px-5 py-4 text-center text-white font-semibold text-sm rounded-xl shadow-2xl backdrop-blur-md border transition-all duration-250 {type === 'success' ? 'bg-gradient-to-br from-green-500/90 to-green-700/90 border-green-300/30' : type === 'error' ? 'bg-gradient-to-br from-red-500/90 to-red-700/90 border-red-300/30' : 'bg-gradient-to-br from-blue-500/90 to-blue-700/90 border-blue-300/30'}">
56+ {message || 'Notification'}
57+ </div>
58+</button>
59+
60+<style>
61+ .toast-base {
62+ /* Reset button styles */
63+ border: none;
64+ background: none;
65+ padding: 0;
66+ font: inherit;
67+ text-align: left;
68+
69+ /* Toast functionality */
70+ cursor: pointer;
71+ user-select: none;
72+ position: relative;
73+ overflow: visible;
74+
75+ /* Layout */
76+ display: block;
77+ visibility: visible;
78+ z-index: 2147483647;
79+ pointer-events: auto;
80+
81+ /* Animations */
82+ animation: slideIn 0.3s ease-out forwards;
83+ transition: transform 0.2s ease, filter 0.2s ease;
84+ }
85+
86+ .toast-base.exiting {
87+ animation: slideOut 0.25s ease-in forwards;
88+ }
89+
90+ @keyframes slideIn {
91+ from {
92+ transform: translateX(-100%);
93+ opacity: 0;
94+ }
95+ to {
96+ transform: translateX(0);
97+ opacity: 1;
98+ }
99+ }
100+
101+ @keyframes slideOut {
102+ from {
103+ transform: translateX(0);
104+ opacity: 1;
105+ }
106+ to {
107+ transform: translateX(-100%);
108+ opacity: 0;
109+ }
110+ }
111+
112+ .toast-base:hover {
113+ filter: brightness(1.1);
114+ transform: scale(1.02);
115+ }
116+
117+ .toast-base:active {
118+ transform: scale(0.98);
119+ }
120+
121+</style>⏎̸
A gui/src/components/toast/ToastContainer.svelte +144 -0
@@ -0,0 +1,144 @@
1+<script lang="ts">
2+ import Toast from './Toast.svelte';
3+
4+ interface ToastData {
5+ id: number;
6+ message: string;
7+ duration?: number;
8+ type?: 'info' | 'success' | 'error';
9+ }
10+
11+ // Svelte 5 rune for reactive state
12+ let toasts = $state<ToastData[]>([]);
13+ let counter = $state(0);
14+ let zoomToastId = $state<number | null>(null);
15+ let zoomDebounceTimer: number | undefined = undefined;
16+
17+ function addToast(message: string, duration = 2500, type: 'info' | 'success' | 'error' = 'info'): number {
18+ const id = ++counter;
19+ const toast: ToastData = { id, message, duration, type };
20+
21+ toasts.push(toast);
22+ console.log('📋 Toast:', message);
23+ return id;
24+ }
25+
26+ function dismissToast(id: number) {
27+ console.log(`[ToastContainer] Dismissing toast with id: ${id}`);
28+ const index = toasts.findIndex(t => t.id === id);
29+ if (index === -1) {
30+ console.log(`[ToastContainer] Toast with id ${id} not found in array`);
31+ return;
32+ }
33+
34+ // Clear zoom toast tracking if this is the zoom toast
35+ if (zoomToastId === id) {
36+ console.log(`[ToastContainer] Clearing zoom toast tracking for id: ${id}`);
37+ zoomToastId = null;
38+ }
39+
40+ console.log(`[ToastContainer] Removing toast at index ${index}, remaining: ${toasts.length - 1}`);
41+ toasts.splice(index, 1);
42+ }
43+
44+ function clearAllToasts() {
45+ toasts = [];
46+ zoomToastId = null;
47+ if (zoomDebounceTimer) {
48+ window.clearTimeout(zoomDebounceTimer);
49+ zoomDebounceTimer = undefined;
50+ }
51+ }
52+
53+ function showZoomToast(zoomLevel: number) {
54+ console.log('[dumber] showZoomToast called with level:', zoomLevel);
55+
56+ // Debug: Check if toast container exists in DOM
57+ const container = document.querySelector('.toast-container');
58+ const browserRoot = document.querySelector('.browser-component-root');
59+ console.log('[dumber] DOM check - container exists:', !!container, 'browser-root exists:', !!browserRoot, 'toasts array length:', toasts.length);
60+
61+ // Clear existing debounce timer
62+ if (zoomDebounceTimer) {
63+ window.clearTimeout(zoomDebounceTimer);
64+ }
65+
66+ // Remove existing zoom toasts immediately (no animation needed for replacement)
67+ toasts = toasts.filter(toast => {
68+ const isZoomToast = toast.message.includes('Zoom level:') || toast.id === zoomToastId;
69+ return !isZoomToast;
70+ });
71+
72+ // Show new zoom toast
73+ const percentage = Math.round(zoomLevel * 100);
74+ const message = `Zoom level: ${percentage}%`;
75+ const id = addToast(message, 1500, 'info');
76+ zoomToastId = id;
77+
78+ console.log('[dumber] Added zoom toast - id:', id, 'current toasts count:', toasts.length);
79+
80+ // Debounced cleanup for rapid zoom changes
81+ zoomDebounceTimer = window.setTimeout(() => {
82+ zoomToastId = null;
83+ zoomDebounceTimer = undefined;
84+ }, 1500);
85+ }
86+
87+ // Expose API to global window for Go integration immediately
88+ // This must happen outside onMount to ensure functions are available immediately
89+ if (typeof window !== 'undefined') {
90+ window.__dumber_showToast = addToast;
91+ window.__dumber_dismissToast = dismissToast;
92+ window.__dumber_clearToasts = clearAllToasts;
93+ window.__dumber_showZoomToast = showZoomToast;
94+ console.log('✅ Toast functions exposed to window:', {
95+ showToast: typeof window.__dumber_showToast,
96+ dismissToast: typeof window.__dumber_dismissToast,
97+ clearToasts: typeof window.__dumber_clearToasts,
98+ showZoomToast: typeof window.__dumber_showZoomToast
99+ });
100+ }
101+
102+ // Effect that runs when toasts array changes
103+ $effect(() => {
104+ const container = document.querySelector('.toast-container');
105+ const browserRoot = document.querySelector('.browser-component-root');
106+ console.log('[dumber] Toast state changed - container:', !!container, 'browser-root:', !!browserRoot, 'toasts:', toasts.length, 'toasts:', toasts.map(t => t.message));
107+ });
108+
109+ // One-time setup effect
110+ $effect(() => {
111+ console.log('🔄 ToastContainer initialized, functions exposed');
112+
113+ // Monitor container periodically for debugging
114+ const monitorInterval = setInterval(() => {
115+ const container = document.querySelector('.toast-container');
116+ const browserRoot = document.querySelector('.browser-component-root');
117+ console.log('[dumber] Periodic check - container:', !!container, 'browser-root:', !!browserRoot, 'toasts:', toasts.length);
118+ }, 5000);
119+
120+ return () => {
121+ clearInterval(monitorInterval);
122+ console.log('🗑️ ToastContainer cleanup');
123+ };
124+ });
125+</script>
126+
127+<div class="fixed top-5 left-5 z-[2147483647] pointer-events-none flex flex-col gap-2 max-w-80 visible opacity-100 backdrop-blur-sm">
128+ {#each toasts as toast (toast.id)}
129+ <Toast
130+ id={toast.id}
131+ message={toast.message}
132+ duration={toast.duration}
133+ type={toast.type}
134+ onDismiss={dismissToast}
135+ />
136+ {/each}
137+</div>
138+
139+<style>
140+ :global(.toast-base) {
141+ pointer-events: auto;
142+ will-change: transform, opacity;
143+ }
144+</style>⏎̸
A gui/src/injected/gui.ts +223 -0
@@ -0,0 +1,223 @@
1+/**
2+ * Unified GUI Bundle Entry Point
3+ *
4+ * Single entry point for all WebKit GUI functionality.
5+ * Loads immediately and provides modular initialization system.
6+ */
7+
8+// Import all GUI modules
9+import { initializeToast, type ToastConfig } from './modules/toast';
10+import { initializeOmnibox, type OmniboxInitConfig } from './modules/omnibox';
11+import { keyboardService, type KeyboardService } from '../lib/keyboard';
12+
13+// Global interface for the unified GUI system
14+interface DumberGUI {
15+ initializeToast: (config?: ToastConfig) => Promise<void>;
16+ initializeOmnibox: (config?: OmniboxInitConfig) => Promise<void>;
17+ keyboard: KeyboardService;
18+ isReady: boolean;
19+}
20+
21+// DOM readiness utility - handles all document ready states
22+function whenDOMReady(callback: () => void) {
23+ if (document.readyState === 'loading') {
24+ document.addEventListener('DOMContentLoaded', callback, { once: true });
25+ } else if (document.readyState === 'interactive' && !document.body) {
26+ // Interactive but no body yet, wait a bit more
27+ setTimeout(() => whenDOMReady(callback), 10);
28+ } else {
29+ // DOM ready and body exists
30+ callback();
31+ }
32+}
33+
34+// Create global namespace
35+declare global {
36+ interface Window {
37+ __dumber_gui?: DumberGUI;
38+ __dumber_gui_ready?: boolean;
39+ __dumber_showToast?: (message: string, duration?: number, type?: 'info' | 'success' | 'error') => number | void;
40+ __dumber_showZoomToast?: (level: number) => void;
41+ __dumber_keyboard?: KeyboardService;
42+ // Legacy compatibility functions for Go bridge
43+ __dumber_toggle?: () => void;
44+ __dumber_find_open?: (query?: string) => void;
45+ __dumber_find_close?: () => void;
46+ __dumber_setSuggestions?: (suggestions: any[]) => void;
47+ __dumber_find_query?: (query: string) => void;
48+ }
49+}
50+
51+// Prevent multiple initialization with enhanced logging
52+if (!window.__dumber_gui_ready) {
53+ // Skip initialization in iframes - GUI only needed in main frame
54+ if (window.self !== window.top) {
55+ console.log('🚫 Dumber GUI skipped in iframe');
56+ } else {
57+ window.__dumber_gui_ready = true;
58+
59+ // Initialize Svelte GUI systems immediately
60+ whenDOMReady(async () => {
61+ try {
62+ // Initialize toast system first
63+ await initializeToast();
64+
65+ // Verify toast functions are available
66+ if (typeof window.__dumber_showToast !== 'function') {
67+ throw new Error('Toast functions not exposed after initialization');
68+ }
69+
70+ // Initialize omnibox system
71+ await initializeOmnibox();
72+
73+ console.log('✅ All GUI systems initialized successfully');
74+ } catch (e) {
75+ console.error('❌ Failed to initialize Svelte toast system:', e);
76+
77+ // Enhanced fallback with better error recovery
78+ window.__dumber_showToast = (message: string, duration: number = 2500) => {
79+ // Remove existing toasts to prevent overlap
80+ document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
81+
82+ const toast = document.createElement('div');
83+ toast.className = 'dumber-fallback-toast';
84+ toast.textContent = message;
85+ toast.style.cssText = `
86+ position: fixed;
87+ bottom: 20px;
88+ right: 20px;
89+ background: rgba(0,0,0,0.9);
90+ color: white;
91+ padding: 12px 16px;
92+ border-radius: 6px;
93+ z-index: 2147483647;
94+ font-family: system-ui, -apple-system, sans-serif;
95+ font-size: 14px;
96+ opacity: 0;
97+ transform: translateX(100%);
98+ transition: all 0.3s ease;
99+ max-width: 300px;
100+ word-wrap: break-word;
101+ `;
102+
103+ if (document.body) {
104+ document.body.appendChild(toast);
105+
106+ // Animate in
107+ requestAnimationFrame(() => {
108+ toast.style.opacity = '1';
109+ toast.style.transform = 'translateX(0)';
110+ });
111+
112+ // Animate out and remove
113+ setTimeout(() => {
114+ toast.style.opacity = '0';
115+ toast.style.transform = 'translateX(100%)';
116+ setTimeout(() => toast.remove(), 300);
117+ }, duration);
118+
119+ }
120+ };
121+
122+ window.__dumber_showZoomToast = (level: number) => {
123+ const percentage = Math.round(level * 100);
124+ window.__dumber_showToast!(`Zoom: ${percentage}%`, 1500);
125+ };
126+
127+ // Expose other required functions
128+ window.__dumber_dismissToast = () => {
129+ document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
130+ };
131+
132+ window.__dumber_clearToasts = () => {
133+ document.querySelectorAll('.dumber-fallback-toast').forEach(el => el.remove());
134+ };
135+ }
136+ });
137+
138+ // Expose keyboard service globally for Go bridge
139+ window.__dumber_keyboard = keyboardService;
140+
141+ // Initialize global keyboard and mouse event listeners for the keyboard service
142+ whenDOMReady(() => {
143+ // Global keyboard event listener (capture phase for high priority)
144+ document.addEventListener('keydown', (event) => {
145+ keyboardService.handleKeyboardEvent(event);
146+ }, true);
147+
148+ // Global mouse event listener for navigation buttons
149+ document.addEventListener('mousedown', (event) => {
150+ keyboardService.handleMouseEvent(event);
151+ }, true);
152+
153+ console.log('✅ KeyboardService initialized with global listeners');
154+ });
155+
156+ // Legacy compatibility functions for existing Go code
157+ window.__dumber_toggle = () => {
158+ try {
159+ console.log('🎯 __dumber_toggle called');
160+ console.log('🔧 Omnibox API available:', !!window.__dumber_omnibox);
161+ if (window.__dumber_omnibox?.toggle) {
162+ console.log('✅ Using Svelte omnibox toggle');
163+ window.__dumber_omnibox.toggle();
164+ } else {
165+ console.log('⚠️ Falling back to keyboard service');
166+ keyboardService.handleNativeShortcut('cmdorctrl+l');
167+ }
168+ } catch (error) {
169+ console.error('❌ Error in __dumber_toggle:', error);
170+ }
171+ };
172+
173+ window.__dumber_find_open = (query?: string) => {
174+ try {
175+ if (window.__dumber_omnibox?.open) {
176+ window.__dumber_omnibox.open('find', query);
177+ } else {
178+ keyboardService.handleNativeShortcut('cmdorctrl+f');
179+ }
180+ } catch (error) {
181+ console.error('❌ Error in __dumber_find_open:', error);
182+ }
183+ };
184+
185+ window.__dumber_find_close = () => {
186+ if (window.__dumber_omnibox?.close) {
187+ window.__dumber_omnibox.close();
188+ } else {
189+ keyboardService.handleNativeShortcut('escape');
190+ }
191+ };
192+
193+ // Key function that Go uses to send search suggestions
194+ window.__dumber_setSuggestions = (suggestions: any[]) => {
195+ if (window.__dumber_omnibox?.setSuggestions) {
196+ window.__dumber_omnibox.setSuggestions(suggestions);
197+ }
198+ };
199+
200+ // Find query function that Go uses to set find query
201+ window.__dumber_find_query = (query: string) => {
202+ if (window.__dumber_omnibox?.findQuery) {
203+ window.__dumber_omnibox.findQuery(query);
204+ }
205+ };
206+
207+ // Create the global GUI object
208+ window.__dumber_gui = {
209+ initializeToast: async (config?: ToastConfig) => {
210+ return await initializeToast(config);
211+ },
212+
213+ initializeOmnibox: async (config?: OmniboxInitConfig) => {
214+ return await initializeOmnibox(config);
215+ },
216+
217+ keyboard: keyboardService,
218+
219+ isReady: true
220+ };
221+
222+ }
223+}⏎̸
A gui/src/injected/modules/controls.ts +109 -0
@@ -0,0 +1,109 @@
1+/**
2+ * Browser Controls Module
3+ *
4+ * Provides global keyboard and mouse controls that work on all domains.
5+ * Only includes essential controls that can work cross-origin:
6+ * - Navigation (back/forward)
7+ * - Mouse navigation buttons
8+ *
9+ * Zoom controls are handled natively by the GTK backend
10+ */
11+
12+export interface ControlsConfig {
13+ // Future configuration options can be added here
14+ _placeholder?: never; // Prevent empty interface warning
15+}
16+
17+let controlsInitialized = false;
18+
19+export function initializeControls(_config?: ControlsConfig): void {
20+ if (controlsInitialized) {
21+ console.log('🔄 Browser controls already initialized, skipping');
22+ return;
23+ }
24+
25+ console.log('🚀 Initializing browser controls on:', window.location.href);
26+
27+ // Keyboard event handler for global controls
28+ function handleKeyboardEvent(event: KeyboardEvent): void {
29+ const { altKey, key } = event;
30+ // Note: ctrlKey, metaKey reserved for future use
31+
32+ try {
33+ // Navigation: Alt + Left Arrow (Back)
34+ if (altKey && key === 'ArrowLeft') {
35+ event.preventDefault();
36+ event.stopPropagation();
37+
38+ if (window.history.length > 1) {
39+ console.log('⬅️ Navigating back');
40+ window.history.back();
41+ }
42+ return;
43+ }
44+
45+ // Navigation: Alt + Right Arrow (Forward)
46+ if (altKey && key === 'ArrowRight') {
47+ event.preventDefault();
48+ event.stopPropagation();
49+
50+ console.log('➡️ Navigating forward');
51+ window.history.forward();
52+ return;
53+ }
54+
55+ } catch (error) {
56+ console.error('❌ Error in keyboard handler:', error);
57+ }
58+ }
59+
60+ // Mouse event handler for navigation buttons
61+ function handleMouseEvent(event: MouseEvent): void {
62+ try {
63+ // Mouse button 3 (back button)
64+ if (event.button === 3) {
65+ event.preventDefault();
66+ event.stopPropagation();
67+
68+ if (window.history.length > 1) {
69+ console.log('⬅️ Mouse back button pressed');
70+ window.history.back();
71+ }
72+ return;
73+ }
74+
75+ // Mouse button 4 (forward button)
76+ if (event.button === 4) {
77+ event.preventDefault();
78+ event.stopPropagation();
79+
80+ console.log('➡️ Mouse forward button pressed');
81+ window.history.forward();
82+ return;
83+ }
84+
85+ } catch (error) {
86+ console.error('❌ Error in mouse handler:', error);
87+ }
88+ }
89+
90+ // Add event listeners with high priority (capture phase)
91+ try {
92+ document.addEventListener('keydown', handleKeyboardEvent, true);
93+ document.addEventListener('mousedown', handleMouseEvent, true);
94+
95+ controlsInitialized = true;
96+ console.log('✅ Browser controls initialized');
97+
98+ // Cleanup function for when page is unloaded
99+ window.addEventListener('beforeunload', () => {
100+ document.removeEventListener('keydown', handleKeyboardEvent, true);
101+ document.removeEventListener('mousedown', handleMouseEvent, true);
102+ controlsInitialized = false;
103+ console.log('🧹 Browser controls cleaned up');
104+ });
105+
106+ } catch (error) {
107+ console.error('❌ Failed to initialize browser controls:', error);
108+ }
109+}⏎̸
A gui/src/injected/modules/omnibox.ts +105 -0
@@ -0,0 +1,105 @@
1+/**
2+ * Omnibox Module Initialization
3+ *
4+ * Initializes the Svelte 5 omnibox component system
5+ */
6+
7+import { mount } from 'svelte';
8+import { Omnibox } from '../../components/omnibox';
9+import type { OmniboxConfig } from '../../components/omnibox/types';
10+
11+let omniboxComponent: ReturnType<typeof mount> | null = null;
12+let isInitialized = false;
13+
14+export interface OmniboxInitConfig extends OmniboxConfig {
15+ // Additional initialization options if needed
16+ containerId?: string;
17+}
18+
19+/**
20+ * Initialize the omnibox component system
21+ */
22+export async function initializeOmnibox(config: OmniboxInitConfig = {}): Promise<void> {
23+ if (isInitialized) {
24+ console.warn('Omnibox already initialized');
25+ return;
26+ }
27+
28+ try {
29+ // Ensure we're in the main frame (not an iframe)
30+ if (window.self !== window.top) {
31+ console.log('🚫 Omnibox skipped in iframe');
32+ return;
33+ }
34+
35+ // Check if DOM is ready
36+ if (!document.body) {
37+ console.warn('DOM body not ready for omnibox initialization');
38+ return;
39+ }
40+
41+ // Create container element
42+ const containerId = config.containerId || 'dumber-omnibox-root';
43+ let container = document.getElementById(containerId);
44+
45+ if (!container) {
46+ container = document.createElement('div');
47+ container.id = containerId;
48+ container.style.cssText = 'position:fixed;inset:0;z-index:2147483647;pointer-events:none;';
49+ document.documentElement.appendChild(container);
50+ }
51+
52+ // Mount the Svelte component
53+ omniboxComponent = mount(Omnibox, {
54+ target: container
55+ });
56+
57+ // Wait for the component to mount and set up the global API
58+ await new Promise<void>((resolve) => {
59+ const checkAPI = () => {
60+ if (window.__dumber_omnibox && typeof window.__dumber_omnibox === 'object') {
61+ console.log('✅ Omnibox component system initialized');
62+ console.log('🔧 Global API is available:', Object.keys(window.__dumber_omnibox));
63+ isInitialized = true;
64+ resolve();
65+ } else {
66+ setTimeout(checkAPI, 10); // Check again in 10ms
67+ }
68+ };
69+ checkAPI();
70+ });
71+
72+ } catch (error) {
73+ console.error('❌ Failed to initialize omnibox component:', error);
74+ throw error;
75+ }
76+}
77+
78+/**
79+ * Get the current initialization status
80+ */
81+export function isOmniboxInitialized(): boolean {
82+ return isInitialized;
83+}
84+
85+/**
86+ * Cleanup omnibox component (useful for hot reload during development)
87+ */
88+export function cleanupOmnibox(): void {
89+ if (omniboxComponent) {
90+ try {
91+ omniboxComponent.$destroy?.();
92+ } catch (error) {
93+ console.warn('Error destroying omnibox component:', error);
94+ }
95+ omniboxComponent = null;
96+ }
97+
98+ const container = document.getElementById('dumber-omnibox-root');
99+ if (container) {
100+ container.remove();
101+ }
102+
103+ isInitialized = false;
104+ console.log('🧹 Omnibox component cleaned up');
105+}⏎̸
A gui/src/injected/modules/toast.ts +147 -0
@@ -0,0 +1,147 @@
1+/**
2+ * Toast System Module
3+ *
4+ * Provides toast notification functionality using Svelte 5 components
5+ * with proper DOM ready checking and theme support.
6+ */
7+
8+import { mount, flushSync } from 'svelte';
9+import ToastContainer from '$components/toast/ToastContainer.svelte';
10+import '$lib/styles.css';
11+
12+export interface ToastConfig {
13+ theme?: 'light' | 'dark';
14+}
15+
16+// Type definitions for window globals
17+declare global {
18+ interface Window {
19+ __dumber_toast_loaded?: boolean;
20+ __dumber_initial_theme?: string;
21+ }
22+}
23+
24+let toastInitialized = false;
25+
26+// Apply theme based on current system/GTK preference
27+function updateTheme(isDark: boolean): void {
28+ if (isDark) {
29+ document.documentElement.classList.add('dark');
30+ } else {
31+ document.documentElement.classList.remove('dark');
32+ }
33+}
34+
35+export async function initializeToast(config?: ToastConfig): Promise<void> {
36+ if (toastInitialized || window.__dumber_toast_loaded) {
37+ // Verify functions still exist - if not, reinitialize
38+ if (typeof window.__dumber_showToast === 'function' &&
39+ typeof window.__dumber_showZoomToast === 'function') {
40+ console.log('✅ Toast system already initialized and functional');
41+ return;
42+ }
43+ // If functions are missing, reinitialize
44+ console.log('⚠️ Toast functions missing, reinitializing...');
45+ toastInitialized = false;
46+ window.__dumber_toast_loaded = false;
47+ }
48+
49+ console.log('🚀 Initializing Svelte toast system at:', document.readyState);
50+ console.log('📊 DOM state check - head:', !!document.head, 'body:', !!document.body);
51+ window.__dumber_toast_loaded = true;
52+
53+ // Ensure DOM is ready
54+ if (document.readyState === 'loading') {
55+ return new Promise((resolve) => {
56+ document.addEventListener('DOMContentLoaded', async () => {
57+ await initializeToast(config);
58+ resolve();
59+ });
60+ });
61+ }
62+
63+ if (!document.head || !document.body) {
64+ console.warn('❌ DOM structure not ready, deferring toast initialization. Retrying...');
65+ window.__dumber_toast_loaded = false; // Reset the flag so we can try again
66+ return new Promise((resolve) => {
67+ const checkDOM = () => {
68+ if (document.head && document.body) {
69+ console.log('✅ DOM structure now ready, initializing toast system');
70+ initializeToast(config).then(() => resolve());
71+ } else {
72+ console.log('⏳ DOM still not ready, retrying in 50ms...');
73+ setTimeout(checkDOM, 50);
74+ }
75+ };
76+ setTimeout(checkDOM, 100);
77+ });
78+ }
79+
80+ try {
81+ // Find or create browser component root
82+ let rootElement = document.querySelector('.browser-component-root') as HTMLDivElement;
83+ if (!rootElement) {
84+ console.log('🔧 Creating browser-component-root');
85+ rootElement = document.createElement('div');
86+ rootElement.className = 'browser-component-root';
87+ rootElement.style.cssText = 'position: fixed; top: 0; left: 0; pointer-events: none; z-index: 2147483647;';
88+ document.documentElement.appendChild(rootElement);
89+ } else {
90+ console.log('✅ Found existing browser-component-root');
91+ }
92+
93+ // Initialize theme
94+ const initialTheme = config?.theme || window.__dumber_initial_theme;
95+ if (initialTheme !== undefined) {
96+ updateTheme(initialTheme === 'dark');
97+ }
98+
99+ // Mount the Svelte toast container (Svelte 5 syntax)
100+ mount(ToastContainer, {
101+ target: rootElement
102+ });
103+
104+ // Force immediate effect execution to ensure onMount callbacks run
105+ flushSync();
106+
107+ // Toast functions are now exposed by the component itself
108+ console.log('✅ Toast functions exposed via component instantiation');
109+
110+ toastInitialized = true;
111+ console.log('✅ Toast system initialized with Svelte 5');
112+
113+ } catch (error) {
114+ console.error('❌ Failed to initialize toast system:', error);
115+
116+ // Fallback to basic toast system if Svelte fails
117+ initializeFallbackToast();
118+ }
119+}
120+
121+function exposeToastFunctions(): void {
122+ // The Svelte ToastContainer component already exposes these functions
123+ // We just need to make sure they exist, they're handled by the component
124+ console.log('✅ Toast functions will be exposed by ToastContainer component');
125+}
126+
127+function initializeFallbackToast(): void {
128+ console.warn('🔄 Falling back to basic toast system');
129+
130+ // Create basic toast container for fallback
131+ const fallbackContainer = document.createElement('div');
132+ fallbackContainer.id = 'dumber-fallback-toast';
133+ fallbackContainer.style.cssText = `
134+ position: fixed;
135+ top: 20px;
136+ right: 20px;
137+ z-index: 10000;
138+ pointer-events: none;
139+ `;
140+ document.documentElement.appendChild(fallbackContainer);
141+
142+ // Expose toast functions for fallback too
143+ exposeToastFunctions();
144+
145+ toastInitialized = true;
146+ console.log('✅ Fallback toast system ready');
147+}⏎̸
A gui/src/lib/keyboard/KeyboardService.ts +446 -0
@@ -0,0 +1,446 @@
1+/**
2+ * Keyboard Service
3+ *
4+ * Centralized keyboard shortcut management for the Dumber Browser GUI.
5+ * Bridges between native GTK keyboard events and Svelte component handlers.
6+ */
7+
8+import type {
9+ ShortcutConfig,
10+ ComponentShortcuts,
11+ KeyboardServiceConfig,
12+ KeyboardEventDetail,
13+ KeyboardEvent,
14+ KeyboardEventType
15+} from './types';
16+import { COMMON_SHORTCUTS } from './types';
17+
18+export class KeyboardService {
19+ private components = new Map<string, ComponentShortcuts>();
20+ private activeComponent: string | null = null;
21+ private globalShortcuts: ShortcutConfig[] = [];
22+ private eventListeners = new Map<KeyboardEventType, Set<(event: KeyboardEvent) => void>>();
23+ private debug: boolean = false;
24+
25+ constructor(config: KeyboardServiceConfig = {}) {
26+ this.debug = config.debug ?? false;
27+ this.globalShortcuts = config.globalShortcuts ?? [];
28+
29+ this.log('info', 'KeyboardService initialized', { config });
30+ }
31+
32+ /**
33+ * Register shortcuts for a component
34+ */
35+ registerShortcuts(componentId: string, shortcuts: ShortcutConfig[]): void {
36+ if (this.components.has(componentId)) {
37+ this.log('warn', `Component ${componentId} already registered, overwriting shortcuts`);
38+ }
39+
40+ const componentShortcuts: ComponentShortcuts = {
41+ componentId,
42+ shortcuts: shortcuts.map(shortcut => ({
43+ ...shortcut,
44+ preventDefault: shortcut.preventDefault ?? true,
45+ stopPropagation: shortcut.stopPropagation ?? true,
46+ whenFocused: shortcut.whenFocused ?? false
47+ })),
48+ focused: false
49+ };
50+
51+ this.components.set(componentId, componentShortcuts);
52+
53+ this.log('info', 'Shortcuts registered', { componentId, shortcuts: shortcuts.length });
54+ this.emit('shortcut-registered', {
55+ shortcut: componentId,
56+ timestamp: Date.now(),
57+ targetComponent: componentId
58+ });
59+ }
60+
61+ /**
62+ * Unregister shortcuts for a component
63+ */
64+ unregisterShortcuts(componentId: string): void {
65+ const component = this.components.get(componentId);
66+ if (!component) {
67+ this.log('warn', `Component ${componentId} not found for unregistration`);
68+ return;
69+ }
70+
71+ // Call cleanup function if provided
72+ if (component.cleanup) {
73+ component.cleanup();
74+ }
75+
76+ this.components.delete(componentId);
77+
78+ // Clear active component if it was this one
79+ if (this.activeComponent === componentId) {
80+ this.activeComponent = null;
81+ }
82+
83+ this.log('info', 'Shortcuts unregistered', { componentId });
84+ this.emit('shortcut-unregistered', {
85+ shortcut: componentId,
86+ timestamp: Date.now(),
87+ targetComponent: componentId
88+ });
89+ }
90+
91+ /**
92+ * Set which component is currently focused
93+ */
94+ setActiveComponent(componentId: string | null): void {
95+ // Blur previous component
96+ if (this.activeComponent && this.components.has(this.activeComponent)) {
97+ const prevComponent = this.components.get(this.activeComponent)!;
98+ prevComponent.focused = false;
99+ this.emit('component-blurred', {
100+ shortcut: this.activeComponent,
101+ timestamp: Date.now(),
102+ targetComponent: this.activeComponent
103+ });
104+ }
105+
106+ this.activeComponent = componentId;
107+
108+ // Focus new component
109+ if (componentId && this.components.has(componentId)) {
110+ const newComponent = this.components.get(componentId)!;
111+ newComponent.focused = true;
112+ this.emit('component-focused', {
113+ shortcut: componentId,
114+ timestamp: Date.now(),
115+ targetComponent: componentId
116+ });
117+ }
118+
119+ this.log('info', 'Active component changed', { from: this.activeComponent, to: componentId });
120+ }
121+
122+ /**
123+ * Handle a keyboard shortcut from native GTK layer
124+ */
125+ handleNativeShortcut(shortcut: string): boolean {
126+ this.log('info', 'Native shortcut received', { shortcut });
127+
128+ // Normalize shortcut string
129+ const normalizedShortcut = this.normalizeShortcut(shortcut);
130+
131+ // Try to handle with focused component first
132+ if (this.activeComponent) {
133+ const handled = this.handleComponentShortcut(this.activeComponent, normalizedShortcut, true);
134+ if (handled) {
135+ return true;
136+ }
137+ }
138+
139+ // Try global shortcuts
140+ const globalHandled = this.handleGlobalShortcuts(normalizedShortcut);
141+ if (globalHandled) {
142+ return true;
143+ }
144+
145+ // Try all components (for non-focused shortcuts)
146+ for (const [componentId] of this.components) {
147+ if (componentId === this.activeComponent) continue; // Already tried above
148+
149+ const handled = this.handleComponentShortcut(componentId, normalizedShortcut, false);
150+ if (handled) {
151+ return true;
152+ }
153+ }
154+
155+ this.log('info', 'Shortcut not handled', { shortcut: normalizedShortcut });
156+ return false;
157+ }
158+
159+ /**
160+ * Handle JavaScript keyboard events (for web page events)
161+ */
162+ handleKeyboardEvent(event: globalThis.KeyboardEvent): boolean {
163+ const shortcut = this.eventToShortcut(event);
164+ if (!shortcut) return false;
165+
166+ this.log('info', 'Keyboard event received', { shortcut });
167+ return this.handleNativeShortcut(shortcut);
168+ }
169+
170+ /**
171+ * Handle JavaScript mouse events (for navigation buttons)
172+ */
173+ handleMouseEvent(event: globalThis.MouseEvent): boolean {
174+ try {
175+ // Mouse button 3 (back button)
176+ if (event.button === 3) {
177+ event.preventDefault();
178+ event.stopPropagation();
179+
180+ if (window.history.length > 1) {
181+ console.log('⬅️ Mouse back button pressed');
182+ window.history.back();
183+ }
184+ return true;
185+ }
186+
187+ // Mouse button 4 (forward button)
188+ if (event.button === 4) {
189+ event.preventDefault();
190+ event.stopPropagation();
191+
192+ console.log('➡️ Mouse forward button pressed');
193+ window.history.forward();
194+ return true;
195+ }
196+
197+ } catch (error) {
198+ this.log('error', 'Mouse handler error', { error });
199+ }
200+
201+ return false;
202+ }
203+
204+ /**
205+ * Add event listener for keyboard service events
206+ */
207+ addEventListener(type: KeyboardEventType, listener: (event: KeyboardEvent) => void): void {
208+ if (!this.eventListeners.has(type)) {
209+ this.eventListeners.set(type, new Set());
210+ }
211+ this.eventListeners.get(type)!.add(listener);
212+ }
213+
214+ /**
215+ * Remove event listener
216+ */
217+ removeEventListener(type: KeyboardEventType, listener: (event: KeyboardEvent) => void): void {
218+ const listeners = this.eventListeners.get(type);
219+ if (listeners) {
220+ listeners.delete(listener);
221+ }
222+ }
223+
224+ /**
225+ * Get all registered shortcuts for debugging
226+ */
227+ getDebugInfo(): object {
228+ return {
229+ components: Array.from(this.components.entries()).map(([id, comp]) => ({
230+ id,
231+ shortcuts: comp.shortcuts.length,
232+ focused: comp.focused
233+ })),
234+ activeComponent: this.activeComponent,
235+ globalShortcuts: this.globalShortcuts.length
236+ };
237+ }
238+
239+ // Private methods
240+
241+ private handleComponentShortcut(componentId: string, shortcut: string, respectFocus: boolean): boolean {
242+ const component = this.components.get(componentId);
243+ if (!component) return false;
244+
245+ for (const shortcutConfig of component.shortcuts) {
246+ if (this.normalizeShortcut(shortcutConfig.key) === shortcut) {
247+ // Check focus requirement
248+ if (respectFocus && shortcutConfig.whenFocused && !component.focused) {
249+ continue;
250+ }
251+
252+ try {
253+ shortcutConfig.handler();
254+ this.log('info', 'Shortcut handled', {
255+ shortcut,
256+ componentId,
257+ focused: component.focused,
258+ description: shortcutConfig.description
259+ });
260+
261+ this.emit('shortcut-triggered', {
262+ shortcut,
263+ timestamp: Date.now(),
264+ targetComponent: componentId
265+ });
266+
267+ return true;
268+ } catch (error) {
269+ this.log('error', 'Shortcut handler error', { shortcut, componentId, error });
270+ }
271+ }
272+ }
273+
274+ return false;
275+ }
276+
277+ private handleGlobalShortcuts(shortcut: string): boolean {
278+ for (const shortcutConfig of this.globalShortcuts) {
279+ if (this.normalizeShortcut(shortcutConfig.key) === shortcut) {
280+ try {
281+ shortcutConfig.handler();
282+ this.log('info', 'Global shortcut handled', { shortcut, description: shortcutConfig.description });
283+
284+ this.emit('shortcut-triggered', {
285+ shortcut,
286+ timestamp: Date.now()
287+ });
288+
289+ return true;
290+ } catch (error) {
291+ this.log('error', 'Global shortcut handler error', { shortcut, error });
292+ }
293+ }
294+ }
295+ return false;
296+ }
297+
298+ private normalizeShortcut(shortcut: string): string {
299+ return shortcut.toLowerCase().replace(/\s+/g, '');
300+ }
301+
302+ private eventToShortcut(event: globalThis.KeyboardEvent): string | null {
303+ const parts: string[] = [];
304+
305+ // Add modifiers using MODIFIER_KEYS mapping
306+ if (event.ctrlKey && event.metaKey) {
307+ // Both ctrl and meta pressed - use cmdorctrl
308+ parts.push('cmdorctrl');
309+ } else if (event.ctrlKey) {
310+ parts.push('ctrl');
311+ } else if (event.metaKey) {
312+ parts.push('cmd');
313+ }
314+
315+ if (event.altKey) parts.push('alt');
316+ if (event.shiftKey) parts.push('shift');
317+
318+ // Add key
319+ if (event.key) {
320+ parts.push(event.key.toLowerCase());
321+ }
322+
323+ return parts.length > 0 ? parts.join('+') : null;
324+ }
325+
326+ private emit(type: KeyboardEventType, detail: KeyboardEventDetail): void {
327+ const listeners = this.eventListeners.get(type);
328+ if (listeners) {
329+ const keyboardEvent: KeyboardEvent = { type, detail };
330+ listeners.forEach(listener => {
331+ try {
332+ listener(keyboardEvent);
333+ } catch (error) {
334+ this.log('error', 'Event listener error', { type, error });
335+ }
336+ });
337+ }
338+ }
339+
340+ private log(level: 'info' | 'warn' | 'error' | string, message?: string, data?: unknown): void {
341+ if (!this.debug && level !== 'error') return;
342+
343+ const logMessage = message ? `[KeyboardService] ${message}` : `[KeyboardService] ${level}`;
344+
345+ switch (level) {
346+ case 'error':
347+ console.error(logMessage, data);
348+ break;
349+ case 'warn':
350+ console.warn(logMessage, data);
351+ break;
352+ default:
353+ console.log(logMessage, data);
354+ }
355+ }
356+}
357+
358+// Create and export singleton instance with global shortcuts
359+export const keyboardService = new KeyboardService({
360+ debug: false, // Disable debug logging in production
361+ globalShortcuts: [
362+ // Navigation shortcuts (migrated from controls module)
363+ {
364+ key: 'alt+arrowleft',
365+ handler: () => {
366+ if (window.history.length > 1) {
367+ window.history.back();
368+ }
369+ },
370+ description: 'Navigate back (Alt+Left Arrow)'
371+ },
372+ {
373+ key: 'alt+arrowright',
374+ handler: () => {
375+ window.history.forward();
376+ },
377+ description: 'Navigate forward (Alt+Right Arrow)'
378+ },
379+ // Global UI shortcuts (migrated from UCM JavaScript)
380+ {
381+ key: COMMON_SHORTCUTS.OMNIBOX_OPEN,
382+ handler: () => {
383+ try {
384+ if (window.__dumber_omnibox?.toggle) {
385+ console.log('🎯 Using omnibox API directly');
386+ window.__dumber_omnibox.toggle();
387+ } else if (typeof window.__dumber_toggle === 'function') {
388+ console.log('🎯 Using legacy toggle function');
389+ window.__dumber_toggle();
390+ } else {
391+ console.warn('⚠️ No omnibox toggle function available');
392+ }
393+ } catch (error) {
394+ console.error('❌ Error in omnibox open handler:', error);
395+ }
396+ },
397+ description: 'Open omnibox (Ctrl/Cmd+L)'
398+ },
399+ {
400+ key: COMMON_SHORTCUTS.FIND_OPEN,
401+ handler: () => {
402+ try {
403+ if (window.__dumber_omnibox?.open) {
404+ console.log('🔍 Using omnibox find API directly');
405+ window.__dumber_omnibox.open('find');
406+ } else if (typeof window.__dumber_find_open === 'function') {
407+ console.log('🔍 Using legacy find function');
408+ window.__dumber_find_open();
409+ } else {
410+ console.warn('⚠️ No find function available');
411+ }
412+ } catch (error) {
413+ console.error('❌ Error in find open handler:', error);
414+ }
415+ },
416+ description: 'Open find bar (Ctrl/Cmd+F)'
417+ },
418+ // Copy URL shortcut (Ctrl/Cmd+Shift+C)
419+ {
420+ key: 'cmdorctrl+shift+c',
421+ handler: async () => {
422+ try {
423+ const url = window.location.href;
424+ await navigator.clipboard.writeText(url);
425+
426+ // Show toast notification
427+ if (typeof window.__dumber_showToast === 'function') {
428+ window.__dumber_showToast('URL copied to clipboard!', 2000, 'success');
429+ }
430+ } catch (error) {
431+ console.error('Failed to copy URL:', error);
432+
433+ // Show error toast
434+ if (typeof window.__dumber_showToast === 'function') {
435+ window.__dumber_showToast('Failed to copy URL', 2000, 'error');
436+ }
437+ }
438+ },
439+ description: 'Copy current URL to clipboard (Ctrl/Cmd+Shift+C)'
440+ }
441+ ]
442+});
443+
444+// Export types for use in components
445+export type { ShortcutConfig, ComponentShortcuts, KeyboardServiceConfig } from './types';
446+export { COMMON_SHORTCUTS } from './types';⏎̸
A gui/src/lib/keyboard/index.ts +17 -0
@@ -0,0 +1,17 @@
1+/**
2+ * Keyboard Service Module
3+ *
4+ * Centralized keyboard shortcut management for Dumber Browser.
5+ * Provides type-safe keyboard event handling and component integration.
6+ */
7+
8+export { KeyboardService, keyboardService } from './KeyboardService';
9+export type {
10+ ShortcutConfig,
11+ ComponentShortcuts,
12+ KeyboardServiceConfig,
13+ KeyboardEventDetail,
14+ KeyboardEvent,
15+ KeyboardEventType
16+} from './types';
17+export { MODIFIER_KEYS, COMMON_SHORTCUTS } from './types';⏎̸
A gui/src/lib/keyboard/types.ts +101 -0
@@ -0,0 +1,101 @@
1+/**
2+ * Keyboard Service Types
3+ *
4+ * Type definitions for the unified keyboard shortcut system.
5+ * Provides type-safe interfaces for registering and handling keyboard shortcuts.
6+ */
7+
8+export interface ShortcutConfig {
9+ /** The keyboard shortcut key combination (e.g., 'cmdorctrl+l', 'escape', 'enter') */
10+ key: string;
11+
12+ /** Function to execute when the shortcut is triggered */
13+ handler: () => void;
14+
15+ /** Whether to prevent the default browser behavior (default: true) */
16+ preventDefault?: boolean;
17+
18+ /** Whether to stop event propagation (default: true) */
19+ stopPropagation?: boolean;
20+
21+ /** Only trigger when this component is focused (default: false) */
22+ whenFocused?: boolean;
23+
24+ /** Optional description for debugging */
25+ description?: string;
26+}
27+
28+export interface KeyboardEventDetail {
29+ /** The original keyboard shortcut string */
30+ shortcut: string;
31+
32+ /** Timestamp when the event occurred */
33+ timestamp: number;
34+
35+ /** Component that should handle this event (if specified) */
36+ targetComponent?: string;
37+}
38+
39+export interface ComponentShortcuts {
40+ /** Unique component identifier */
41+ componentId: string;
42+
43+ /** Array of shortcuts registered by this component */
44+ shortcuts: ShortcutConfig[];
45+
46+ /** Whether this component is currently focused */
47+ focused: boolean;
48+
49+ /** Optional cleanup function called when component is destroyed */
50+ cleanup?: () => void;
51+}
52+
53+export interface KeyboardServiceConfig {
54+ /** Enable debug logging for keyboard events */
55+ debug?: boolean;
56+
57+ /** Global shortcuts that work regardless of component focus */
58+ globalShortcuts?: ShortcutConfig[];
59+}
60+
61+/**
62+ * Event types for keyboard service
63+ */
64+export type KeyboardEventType =
65+ | 'shortcut-triggered'
66+ | 'component-focused'
67+ | 'component-blurred'
68+ | 'shortcut-registered'
69+ | 'shortcut-unregistered';
70+
71+export interface KeyboardEvent {
72+ type: KeyboardEventType;
73+ detail: KeyboardEventDetail | ComponentShortcuts | ShortcutConfig;
74+}
75+
76+/**
77+ * Keyboard modifiers mapping
78+ */
79+export const MODIFIER_KEYS = {
80+ ctrl: 'ctrlKey',
81+ cmd: 'metaKey',
82+ cmdorctrl: navigator.platform.includes('Mac') ? 'metaKey' : 'ctrlKey',
83+ alt: 'altKey',
84+ shift: 'shiftKey'
85+} as const;
86+
87+/**
88+ * Common shortcut patterns used throughout the application
89+ */
90+export const COMMON_SHORTCUTS = {
91+ OMNIBOX_OPEN: 'cmdorctrl+l',
92+ FIND_OPEN: 'cmdorctrl+f',
93+ ESCAPE: 'escape',
94+ ENTER: 'enter',
95+ ARROW_UP: 'arrowup',
96+ ARROW_DOWN: 'arrowdown',
97+ ARROW_LEFT: 'arrowleft',
98+ ARROW_RIGHT: 'arrowright',
99+ TAB: 'tab',
100+ SHIFT_TAB: 'shift+tab'
101+} as const;⏎̸
A gui/src/lib/styles.css +1 -0
@@ -0,0 +1,1 @@
1+@import '../styles/tailwind.css';⏎̸
A gui/src/lib/types.ts +44 -0
@@ -0,0 +1,44 @@
1+// Global window API types for browser integration
2+declare global {
3+ interface Window {
4+ // Toast system API
5+ __dumber_toast_loaded?: boolean;
6+ __dumber_showToast?: (message: string, duration?: number, type?: 'info' | 'success' | 'error') => number | void;
7+ __dumber_dismissToast?: (id: number) => void;
8+ __dumber_clearToasts?: () => void;
9+ __dumber_showZoomToast?: (zoomLevel: number) => void;
10+
11+ // Theme integration
12+ __dumber_initial_theme?: string;
13+ __dumber_setTheme?: (theme: 'light' | 'dark') => void;
14+
15+ // WebKit message handler
16+ webkit?: {
17+ messageHandlers?: {
18+ dumber?: {
19+ postMessage: (message: string) => void;
20+ };
21+ };
22+ };
23+ }
24+}
25+
26+export interface ToastAPI {
27+ show: (message: string, duration?: number, type?: 'info' | 'success' | 'error') => number;
28+ dismiss: (id: number) => void;
29+ clear: () => void;
30+ showZoom: (zoomLevel: number) => void;
31+}
32+
33+export interface ThemeAPI {
34+ setTheme: (theme: 'light' | 'dark') => void;
35+ getCurrentTheme: () => 'light' | 'dark';
36+}
37+
38+// Message types for WebKit communication
39+export interface BrowserMessage {
40+ action: string;
41+ data?: unknown;
42+}
43+
44+export {}; // Make this file a module⏎̸
A gui/src/main.ts +73 -0
@@ -0,0 +1,73 @@
1+import { AppService } from './services/app.js';
2+import { BrowserControlsService } from './services/browser-controls.js';
3+import { DOMRenderer } from './utils/dom.js';
4+
5+class DumberBrowserApp {
6+ private appService: AppService;
7+ private browserControls: BrowserControlsService;
8+ private domRenderer: DOMRenderer;
9+
10+ constructor() {
11+ this.appService = new AppService();
12+ this.browserControls = new BrowserControlsService();
13+ this.domRenderer = new DOMRenderer();
14+ }
15+
16+ async initialize(): Promise<void> {
17+ try {
18+ // Show loading states
19+ this.domRenderer.showLoading('history');
20+ this.domRenderer.showLoading('shortcuts');
21+
22+ // Initialize services
23+ await this.appService.initialize();
24+
25+ // Initialize browser controls
26+ await this.browserControls.initialize();
27+
28+ // Render the UI
29+ this.render();
30+
31+ console.log('✓ Dumber Browser initialized successfully');
32+ } catch (error) {
33+ console.error('Failed to initialize Dumber Browser:', error);
34+ this.handleInitializationError();
35+ }
36+ }
37+
38+ private render(): void {
39+ try {
40+ // Render history
41+ const history = this.appService.getHistory();
42+ this.domRenderer.displayHistory(history);
43+
44+ // Render shortcuts
45+ const shortcuts = this.appService.getShortcuts();
46+ this.domRenderer.displayShortcuts(shortcuts);
47+ } catch (error) {
48+ console.error('Failed to render UI:', error);
49+ this.domRenderer.showError('history', 'Failed to load history');
50+ this.domRenderer.showError('shortcuts', 'Failed to load shortcuts');
51+ }
52+ }
53+
54+ private handleInitializationError(): void {
55+ this.domRenderer.showError('history', 'Failed to initialize application');
56+ this.domRenderer.showError('shortcuts', 'Failed to initialize application');
57+ }
58+}
59+
60+// Initialize the application when the DOM is ready
61+function initializeApp(): void {
62+ const app = new DumberBrowserApp();
63+ app.initialize().catch(error => {
64+ console.error('Critical error during app initialization:', error);
65+ });
66+}
67+
68+// Wait for DOM to be ready
69+if (document.readyState === 'loading') {
70+ document.addEventListener('DOMContentLoaded', initializeApp);
71+} else {
72+ initializeApp();
73+}⏎̸
A gui/src/pages/Homepage.svelte +407 -0
@@ -0,0 +1,407 @@
1+<script lang="ts">
2+ import { onMount } from 'svelte';
3+
4+ interface HistoryItem {
5+ id: number;
6+ url: string;
7+ title: string;
8+ visit_count: number;
9+ last_visited: string;
10+ created_at: string;
11+ }
12+
13+ interface Shortcut {
14+ key: string;
15+ description: string;
16+ action: () => void;
17+ }
18+
19+ let historyItems = $state<HistoryItem[]>([]);
20+ let shortcuts = $state<Shortcut[]>([]);
21+ let historyLoading = $state(true);
22+ let shortcutsLoading = $state(true);
23+
24+ // Initialize shortcuts (these are browser shortcuts, not dynamic from API)
25+ const initializeShortcuts = () => {
26+ shortcuts = [
27+ {
28+ key: 'Ctrl+L',
29+ description: 'Focus address bar',
30+ action: () => {
31+ // This will be handled by the browser's keyboard shortcuts
32+ console.log('Ctrl+L triggered');
33+ }
34+ },
35+ {
36+ key: 'Ctrl+F',
37+ description: 'Find in page',
38+ action: () => {
39+ // This will be handled by the browser's keyboard shortcuts
40+ console.log('Ctrl+F triggered');
41+ }
42+ },
43+ {
44+ key: 'Ctrl+R',
45+ description: 'Reload page',
46+ action: () => {
47+ window.location.reload();
48+ }
49+ },
50+ {
51+ key: 'Ctrl+0',
52+ description: 'Reset zoom',
53+ action: () => {
54+ // This will be handled by the browser's zoom controls
55+ console.log('Ctrl+0 triggered');
56+ }
57+ },
58+ {
59+ key: 'Ctrl++',
60+ description: 'Zoom in',
61+ action: () => {
62+ // This will be handled by the browser's zoom controls
63+ console.log('Ctrl++ triggered');
64+ }
65+ },
66+ {
67+ key: 'Ctrl+-',
68+ description: 'Zoom out',
69+ action: () => {
70+ // This will be handled by the browser's zoom controls
71+ console.log('Ctrl+- triggered');
72+ }
73+ }
74+ ];
75+ shortcutsLoading = false;
76+ };
77+
78+ // Fetch recent history from the API
79+ const fetchHistory = async () => {
80+ try {
81+ const response = await fetch('dumb://homepage/api/history/recent?limit=20');
82+ if (response.ok) {
83+ const data = await response.json();
84+ historyItems = Array.isArray(data) ? data : [];
85+ } else {
86+ console.warn('Failed to fetch history:', response.status);
87+ historyItems = [];
88+ }
89+ } catch (error) {
90+ console.error('Error fetching history:', error);
91+ historyItems = [];
92+ } finally {
93+ historyLoading = false;
94+ }
95+ };
96+
97+ // Navigate to a URL when history item is clicked
98+ const navigateTo = (url: string) => {
99+ window.location.href = url;
100+ };
101+
102+ // Get domain from URL
103+ const getDomain = (url: string): string => {
104+ try {
105+ return new URL(url).hostname;
106+ } catch {
107+ return url;
108+ }
109+ };
110+
111+ // Format relative time
112+ const formatTime = (timestamp: string): string => {
113+ try {
114+ const date = new Date(timestamp);
115+ const now = new Date();
116+ const diff = now.getTime() - date.getTime();
117+
118+ const minutes = Math.floor(diff / (1000 * 60));
119+ const hours = Math.floor(diff / (1000 * 60 * 60));
120+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
121+
122+ if (minutes < 1) return 'Just now';
123+ if (minutes < 60) return `${minutes}m ago`;
124+ if (hours < 24) return `${hours}h ago`;
125+ if (days < 7) return `${days}d ago`;
126+
127+ return date.toLocaleDateString();
128+ } catch {
129+ return '';
130+ }
131+ };
132+
133+ onMount(() => {
134+ initializeShortcuts();
135+ fetchHistory();
136+ });
137+
138+ // Combined loading state
139+ const loading = $derived(historyLoading || shortcutsLoading);
140+</script>
141+
142+<div class="container">
143+ <div class="history-section">
144+ <h2 class="section-title">Recent History</h2>
145+ <div class="history-list">
146+ {#if historyLoading}
147+ <div class="loading">Loading history...</div>
148+ {:else if historyItems.length === 0}
149+ <div class="empty-state">
150+ <h3>No history yet</h3>
151+ <p>Start browsing to see your recent history here.</p>
152+ </div>
153+ {:else}
154+ {#each historyItems as item (item.url)}
155+ <div
156+ class="history-item"
157+ role="button"
158+ tabindex="0"
159+ onclick={() => navigateTo(item.url)}
160+ onkeydown={(e) => e.key === 'Enter' && navigateTo(item.url)}
161+ >
162+ <div class="history-line">
163+ <div class="history-favicon-chip">
164+ <div class="history-favicon-fallback">🌐</div>
165+ </div>
166+ <div class="history-title">{item.title || 'Untitled'}</div>
167+ <div class="history-sep">•</div>
168+ <div class="history-domain">{getDomain(item.url)}</div>
169+ <div class="history-sep">•</div>
170+ <div class="history-url">{item.url}</div>
171+ <div class="history-time">{formatTime(item.last_visited)}</div>
172+ </div>
173+ </div>
174+ {/each}
175+ {/if}
176+ </div>
177+ </div>
178+
179+ <div class="shortcuts-section">
180+ <h2 class="section-title">Keyboard Shortcuts</h2>
181+ <div class="shortcuts-grid">
182+ {#if shortcutsLoading}
183+ <div class="loading">Loading shortcuts...</div>
184+ {:else}
185+ {#each shortcuts as shortcut (shortcut.key)}
186+ <div
187+ class="shortcut"
188+ role="button"
189+ tabindex="0"
190+ onclick={shortcut.action}
191+ onkeydown={(e) => e.key === 'Enter' && shortcut.action()}
192+ >
193+ <div class="shortcut-key">{shortcut.key}</div>
194+ <div class="shortcut-desc">{shortcut.description}</div>
195+ </div>
196+ {/each}
197+ {/if}
198+ </div>
199+ </div>
200+</div>
201+
202+<style>
203+ /* Global styles - using CSS to match the original design */
204+ :global(*) {
205+ margin: 0;
206+ padding: 0;
207+ box-sizing: border-box;
208+ }
209+
210+ :global(body) {
211+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
212+ background: #1a1a1a;
213+ color: #ffffff;
214+ height: 100vh;
215+ display: flex;
216+ flex-direction: column;
217+ overflow-x: hidden;
218+ }
219+
220+ .container {
221+ flex: 1;
222+ display: flex;
223+ max-width: 1200px;
224+ margin: 0 auto;
225+ width: 100%;
226+ padding: 2rem;
227+ gap: 2rem;
228+ overflow-x: hidden;
229+ }
230+
231+ .history-section {
232+ flex: 1;
233+ min-height: 0;
234+ min-width: 0;
235+ }
236+
237+ .shortcuts-section {
238+ flex: 1;
239+ min-height: 0;
240+ min-width: 0;
241+ }
242+
243+ .section-title {
244+ font-size: 1.5rem;
245+ margin-bottom: 1rem;
246+ color: #ffffff;
247+ border-bottom: 2px solid #404040;
248+ padding-bottom: 0.5rem;
249+ }
250+
251+ .history-list {
252+ overflow-y: auto;
253+ overflow-x: hidden;
254+ max-height: calc(100vh - 8rem);
255+ max-width: 100%;
256+ }
257+
258+ .history-item {
259+ padding: 0.75rem;
260+ margin-bottom: 0.5rem;
261+ background: #2d2d2d;
262+ border-radius: 6px;
263+ cursor: pointer;
264+ transition: background 0.2s;
265+ border-left: 3px solid #404040;
266+ overflow: hidden;
267+ max-width: 100%;
268+ }
269+
270+ .history-item:hover {
271+ background: #3d3d3d;
272+ border-left-color: #0066cc;
273+ }
274+
275+ .history-line {
276+ display: flex;
277+ gap: 0.5rem;
278+ white-space: nowrap;
279+ align-items: center;
280+ overflow: hidden;
281+ width: 100%;
282+ min-width: 0;
283+ }
284+
285+ .history-favicon-chip {
286+ flex: 0 0 20px;
287+ width: 20px;
288+ height: 20px;
289+ border-radius: 50%;
290+ background: #ccc;
291+ border: 1px solid rgba(0,0,0,.12);
292+ box-shadow: 0 1px 2px rgba(0,0,0,.12);
293+ display: flex;
294+ align-items: center;
295+ justify-content: center;
296+ }
297+
298+ .history-favicon-img {
299+ width: 16px;
300+ height: 16px;
301+ filter: brightness(1.06) contrast(1.03);
302+ image-rendering: -webkit-optimize-contrast;
303+ }
304+
305+ .history-favicon-fallback {
306+ font-size: 12px;
307+ }
308+
309+ .history-title {
310+ font-size: 0.95rem;
311+ color: #e6e6e6;
312+ flex: 0 0 auto;
313+ }
314+
315+ .history-domain {
316+ font-size: 0.9rem;
317+ color: #a5a5a5;
318+ flex: 0 0 auto;
319+ }
320+
321+ .history-sep {
322+ color: #666;
323+ flex: 0 0 auto;
324+ }
325+
326+ .history-url {
327+ font-size: 0.9rem;
328+ color: #7a7a7a;
329+ flex: 1 1 auto;
330+ min-width: 0;
331+ overflow: hidden;
332+ text-overflow: ellipsis;
333+ -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
334+ mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
335+ }
336+
337+ .history-time {
338+ font-size: 0.85rem;
339+ color: #666;
340+ flex: 0 0 auto;
341+ margin-left: auto;
342+ }
343+
344+ .shortcuts-grid {
345+ display: grid;
346+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
347+ gap: 1rem;
348+ max-height: calc(100vh - 8rem);
349+ overflow-y: auto;
350+ }
351+
352+ .shortcut {
353+ padding: 1rem;
354+ background: #2d2d2d;
355+ border: 2px solid #404040;
356+ border-radius: 8px;
357+ cursor: pointer;
358+ transition: all 0.2s;
359+ text-align: center;
360+ }
361+
362+ .shortcut:hover {
363+ background: #3d3d3d;
364+ border-color: #0066cc;
365+ transform: translateY(-2px);
366+ }
367+
368+ .shortcut-key {
369+ font-weight: bold;
370+ color: #0066cc;
371+ margin-bottom: 0.5rem;
372+ font-size: 1.1rem;
373+ }
374+
375+ .shortcut-desc {
376+ color: #888;
377+ font-size: 0.9rem;
378+ }
379+
380+ .loading {
381+ padding: 2rem;
382+ text-align: center;
383+ color: #888;
384+ }
385+
386+ .empty-state {
387+ padding: 3rem 2rem;
388+ text-align: center;
389+ color: #666;
390+ }
391+
392+ .empty-state h3 {
393+ margin-bottom: 1rem;
394+ color: #888;
395+ }
396+
397+ @media (max-width: 768px) {
398+ .container {
399+ flex-direction: column;
400+ padding: 1rem;
401+ }
402+
403+ .shortcuts-grid {
404+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
405+ }
406+ }
407+</style>⏎̸
A gui/src/pages/homepage.ts +9 -0
@@ -0,0 +1,9 @@
1+import { mount } from 'svelte';
2+import Homepage from './Homepage.svelte';
3+
4+// Mount the Homepage component to the DOM
5+const app = mount(Homepage, {
6+ target: document.body,
7+});
8+
9+export default app;⏎̸
A gui/src/services/app.ts +105 -0
@@ -0,0 +1,105 @@
1+type HistoryEntry = {
2+ id: number;
3+ url: string;
4+ title: string;
5+ visit_count: number;
6+ last_visited: string | null;
7+ created_at: string | null;
8+}
9+
10+export class AppService {
11+ private history: HistoryEntry[] = [];
12+ private shortcuts: Record<string, any> = {};
13+
14+ constructor() {}
15+
16+ async initialize(): Promise<void> {
17+ try {
18+ await Promise.all([
19+ this.loadHistory(),
20+ this.loadShortcuts()
21+ ]);
22+ } catch (error) {
23+ console.error('Failed to initialize app:', error);
24+ }
25+ }
26+
27+ async loadHistory(): Promise<void> {
28+ try {
29+ const recentHistory: HistoryEntry[] = await fetch('/api/history/recent?limit=50').then(r => r.json());
30+ this.history = recentHistory;
31+ } catch (error) {
32+ console.error('Failed to load history:', error);
33+ // Mock history for development/fallback
34+ this.history = [
35+ {
36+ id: 1,
37+ url: 'https://github.com/wailsapp/wails',
38+ title: 'Wails Framework',
39+ visit_count: 3,
40+ last_visited: new Date().toISOString(),
41+ created_at: new Date().toISOString()
42+ },
43+ {
44+ id: 2,
45+ url: 'https://go.dev',
46+ title: 'The Go Programming Language',
47+ visit_count: 2,
48+ last_visited: new Date().toISOString(),
49+ created_at: new Date().toISOString()
50+ },
51+ {
52+ id: 3,
53+ url: 'https://developer.mozilla.org',
54+ title: 'MDN Web Docs',
55+ visit_count: 1,
56+ last_visited: new Date().toISOString(),
57+ created_at: new Date().toISOString()
58+ }
59+ ];
60+ }
61+ }
62+
63+ async loadShortcuts(): Promise<void> {
64+ try {
65+ const cfg = await fetch('/api/config').then(r => r.json());
66+ const raw = cfg?.search_shortcuts || {};
67+ // Normalize field casing from backend (supports URL/Description and url/description)
68+ const normalized: Record<string, { description: string; url: string }> = {};
69+ for (const [key, value] of Object.entries(raw)) {
70+ const v: any = value as any;
71+ normalized[key] = {
72+ description: v.description ?? v.Description ?? '',
73+ url: v.url ?? v.URL ?? ''
74+ };
75+ }
76+ this.shortcuts = normalized;
77+ } catch (error) {
78+ console.error('Failed to load shortcuts:', error);
79+ // Mock shortcuts for development/fallback
80+ this.shortcuts = {
81+ 'g': { description: 'Google Search', url: 'https://google.com/search?q={query}' },
82+ 'gh': { description: 'GitHub Search', url: 'https://github.com/search?q={query}' },
83+ 'so': { description: 'Stack Overflow', url: 'https://stackoverflow.com/search?q={query}' },
84+ 'w': { description: 'Wikipedia', url: 'https://en.wikipedia.org/wiki/Special:Search?search={query}' }
85+ };
86+ }
87+ }
88+
89+ getHistory(): HistoryEntry[] {
90+ return this.history;
91+ }
92+
93+ getShortcuts(): Record<string, any> {
94+ return this.shortcuts;
95+ }
96+
97+ async copyToClipboard(text: string): Promise<void> {
98+ try {
99+ await navigator.clipboard.writeText(text);
100+ console.log('Copied to clipboard:', text);
101+ } catch (err) {
102+ console.error('Failed to copy:', err);
103+ }
104+ }
105+}
A gui/src/services/browser-controls.ts +179 -0
@@ -0,0 +1,179 @@
1+import { NotificationService } from './notifications.js';
2+import { DebugConsoleService } from './debug-console.js';
3+
4+export class BrowserControlsService {
5+ private currentURL = '';
6+ private notifications = new NotificationService();
7+ private debugConsole = new DebugConsoleService();
8+
9+ constructor() {
10+ // Debug logging with enhanced details
11+ this.debugConsole.addLog('info', 'frontend', 'init', 'BrowserControlsService initializing...');
12+ this.debugConsole.addLog('info', 'frontend', 'init', 'Running without Wails bindings');
13+
14+ this.initializeEventListeners();
15+ this.debugConsole.addLog('info', 'frontend', 'init', 'BrowserControlsService initialized ✓');
16+ }
17+
18+
19+ private initializeEventListeners(): void {
20+ // Keyboard event handler for browser controls
21+ document.addEventListener('keydown', this.handleKeyboardEvent.bind(this));
22+
23+ // Mouse button handler for navigation
24+ document.addEventListener('mousedown', this.handleMouseEvent.bind(this));
25+
26+ // URL change listener (for SPA navigation)
27+ window.addEventListener('popstate', this.handleURLChange.bind(this));
28+ }
29+
30+ private async handleKeyboardEvent(event: KeyboardEvent): Promise<void> {
31+ // Skip F12 (debug console toggle)
32+ if (event.key === 'F12') {
33+ return;
34+ }
35+
36+ // Log all keyboard events to debug console
37+ this.debugConsole.logKeyboardEvent(event);
38+
39+ const { ctrlKey, metaKey, shiftKey, altKey, key } = event;
40+ const isCmd = ctrlKey || metaKey;
41+
42+ // Zoom controls handled natively by GTK; don't intercept
43+ if (isCmd && (key === '+' || key === '=' || key === '-' || key === '0')) {
44+ return; // allow native handler to process
45+ }
46+
47+ // Copy URL
48+ if (isCmd && shiftKey && key === 'C') {
49+ this.debugConsole.addLog('info', 'frontend', 'action', 'Copy URL triggered');
50+ event.preventDefault();
51+ await this.copyCurrentURL();
52+ return;
53+ }
54+
55+ // Navigation
56+ if (altKey && key === 'ArrowLeft') {
57+ this.debugConsole.addLog('info', 'frontend', 'action', 'Go Back triggered');
58+ event.preventDefault();
59+ history.back();
60+ return;
61+ }
62+
63+ if (altKey && key === 'ArrowRight') {
64+ this.debugConsole.addLog('info', 'frontend', 'action', 'Go Forward triggered');
65+ event.preventDefault();
66+ history.forward();
67+ return;
68+ }
69+ }
70+
71+ private async handleMouseEvent(event: MouseEvent): Promise<void> {
72+ // Log all mouse events to debug console
73+ this.debugConsole.logMouseEvent(event);
74+
75+ // Mouse button 3 (back button)
76+ if (event.button === 3) {
77+ this.debugConsole.addLog('info', 'frontend', 'action', 'Mouse Back button triggered');
78+ event.preventDefault();
79+ await this.goBack();
80+ return;
81+ }
82+
83+ // Mouse button 4 (forward button)
84+ if (event.button === 4) {
85+ this.debugConsole.addLog('info', 'frontend', 'action', 'Mouse Forward button triggered');
86+ event.preventDefault();
87+ await this.goForward();
88+ return;
89+ }
90+ }
91+
92+ private handleURLChange(): void {
93+ this.setCurrentURL(window.location.href);
94+ this.loadSavedZoom();
95+ }
96+
97+ // Zoom is handled natively; no JS-based zoom adjustments needed.
98+
99+ async copyCurrentURL(): Promise<void> {
100+ try {
101+ const url = this.getCurrentURL();
102+ await navigator.clipboard.writeText(url);
103+
104+ // URL copied successfully - no backend notification needed
105+
106+ this.notifications.show('URL copied to clipboard!', 'success');
107+ } catch (error) {
108+ console.error('Failed to copy URL:', error);
109+ this.notifications.show('Failed to copy URL', 'error');
110+ }
111+ }
112+
113+ async goBack(): Promise<void> {
114+ this.debugConsole.addLog('info', 'frontend', 'service', 'Calling GoBack');
115+
116+ try {
117+ // await BrowserService.GoBack(); // TODO: Implement BrowserService
118+ this.debugConsole.logServiceCall('BrowserService', 'GoBack', []);
119+ history.back();
120+ this.debugConsole.addLog('info', 'frontend', 'navigation', 'Browser navigated back');
121+ } catch (error) {
122+ this.debugConsole.logServiceCall('BrowserService', 'GoBack', [], null, error as Error);
123+ console.error('Failed to go back:', error);
124+ }
125+ }
126+
127+ async goForward(): Promise<void> {
128+ this.debugConsole.addLog('info', 'frontend', 'service', 'Calling GoForward');
129+
130+ try {
131+ // await BrowserService.GoForward(); // TODO: Implement BrowserService
132+ this.debugConsole.logServiceCall('BrowserService', 'GoForward', []);
133+ history.forward();
134+ this.debugConsole.addLog('info', 'frontend', 'navigation', 'Browser navigated forward');
135+ } catch (error) {
136+ this.debugConsole.logServiceCall('BrowserService', 'GoForward', [], null, error as Error);
137+ console.error('Failed to go forward:', error);
138+ }
139+ }
140+
141+ getCurrentURL(): string {
142+ return this.currentURL || window.location.href;
143+ }
144+
145+ setCurrentURL(url: string): void {
146+ this.currentURL = url;
147+ this.updateWindowTitle(url);
148+ }
149+
150+ private async updateWindowTitle(url: string): Promise<void> {
151+ try {
152+ // Extract domain or path for title
153+ let title = 'Dumber Browser';
154+ if (url && url !== '/') {
155+ const urlObj = new URL(url);
156+ title = `Dumber - ${urlObj.hostname}`;
157+ }
158+
159+ document.title = title;
160+
161+ // Backend title updates handled natively; no frontend call
162+ } catch (error) {
163+ console.error('Failed to update title:', error);
164+ }
165+ }
166+
167+ async loadSavedZoom(): Promise<void> {
168+ // Zoom persistence applied natively on page load
169+ }
170+
171+ async initialize(): Promise<void> {
172+ // Check if there's an initial URL from backend (direct navigation)
173+ // Set initial URL from browser location (homepage)
174+ this.setCurrentURL(window.location.href);
175+
176+ // Load saved zoom level (only if we're staying on homepage)
177+ // Zoom handled natively
178+ }
179+}
A gui/src/services/debug-console.ts +258 -0
@@ -0,0 +1,258 @@
1+export interface DebugLogEntry {
2+ timestamp: string;
3+ level: 'info' | 'warn' | 'error' | 'debug';
4+ source: 'frontend' | 'backend';
5+ category: string;
6+ message: string;
7+ data?: any;
8+}
9+
10+export class DebugConsoleService {
11+ private logs: DebugLogEntry[] = [];
12+ private maxLogs = 1000;
13+ private isVisible = false;
14+ private debugElement: HTMLElement | null = null;
15+ private logsContainer: HTMLElement | null = null;
16+
17+ constructor() {
18+ this.createDebugUI();
19+ this.setupKeyboardShortcut();
20+ this.interceptConsole();
21+ }
22+
23+ private createDebugUI(): void {
24+ // Create debug panel
25+ this.debugElement = document.createElement('div');
26+ this.debugElement.id = 'debug-console';
27+ this.debugElement.style.cssText = `
28+ position: fixed;
29+ top: 0;
30+ right: 0;
31+ width: 50%;
32+ height: 100%;
33+ background: rgba(0, 0, 0, 0.95);
34+ color: #00ff00;
35+ font-family: 'Courier New', monospace;
36+ font-size: 12px;
37+ z-index: 10000;
38+ display: none;
39+ border-left: 2px solid #00ff00;
40+ `;
41+
42+ // Create header
43+ const header = document.createElement('div');
44+ header.style.cssText = `
45+ padding: 10px;
46+ background: rgba(0, 255, 0, 0.1);
47+ border-bottom: 1px solid #00ff00;
48+ font-weight: bold;
49+ display: flex;
50+ justify-content: space-between;
51+ align-items: center;
52+ `;
53+ header.innerHTML = `
54+ <span>🐛 DUMBER DEBUG CONSOLE</span>
55+ <div style="font-size: 10px;">
56+ <span style="color: #888; margin-right: 10px;">F11: Inspect Services | F12: Toggle</span>
57+ <button id="clear-logs" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; margin-right: 5px; cursor: pointer;">Clear</button>
58+ <button id="close-debug" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; cursor: pointer;">Close</button>
59+ </div>
60+ `;
61+
62+ // Create logs container
63+ this.logsContainer = document.createElement('div');
64+ this.logsContainer.style.cssText = `
65+ height: calc(100% - 50px);
66+ overflow-y: auto;
67+ padding: 10px;
68+ `;
69+
70+ this.debugElement.appendChild(header);
71+ this.debugElement.appendChild(this.logsContainer);
72+ document.body.appendChild(this.debugElement);
73+
74+ // Setup button handlers
75+ header.querySelector('#clear-logs')?.addEventListener('click', () => this.clearLogs());
76+ header.querySelector('#close-debug')?.addEventListener('click', () => this.hide());
77+ }
78+
79+ private setupKeyboardShortcut(): void {
80+ document.addEventListener('keydown', (event) => {
81+ // F12 to toggle debug console
82+ if (event.key === 'F12') {
83+ event.preventDefault();
84+ this.toggle();
85+ }
86+ });
87+ }
88+
89+ private interceptConsole(): void {
90+ const originalConsole = {
91+ log: console.log,
92+ warn: console.warn,
93+ error: console.error,
94+ info: console.info,
95+ debug: console.debug
96+ };
97+
98+ console.log = (...args) => {
99+ this.addLog('info', 'frontend', 'console', args.join(' '));
100+ originalConsole.log(...args);
101+ };
102+
103+ console.warn = (...args) => {
104+ this.addLog('warn', 'frontend', 'console', args.join(' '));
105+ originalConsole.warn(...args);
106+ };
107+
108+ console.error = (...args) => {
109+ this.addLog('error', 'frontend', 'console', args.join(' '));
110+ originalConsole.error(...args);
111+ };
112+
113+ console.info = (...args) => {
114+ this.addLog('info', 'frontend', 'console', args.join(' '));
115+ originalConsole.info(...args);
116+ };
117+
118+ console.debug = (...args) => {
119+ this.addLog('debug', 'frontend', 'console', args.join(' '));
120+ originalConsole.debug(...args);
121+ };
122+ }
123+
124+ public addLog(level: DebugLogEntry['level'], source: DebugLogEntry['source'], category: string, message: string, data?: any): void {
125+ const entry: DebugLogEntry = {
126+ timestamp: new Date().toISOString().split('T')[1]?.split('.')[0] || '', // HH:MM:SS format
127+ level,
128+ source,
129+ category,
130+ message,
131+ data
132+ };
133+
134+ this.logs.push(entry);
135+
136+ // Keep only the last maxLogs entries
137+ if (this.logs.length > this.maxLogs) {
138+ this.logs.shift();
139+ }
140+
141+ this.updateDisplay();
142+ }
143+
144+ private updateDisplay(): void {
145+ if (!this.logsContainer) return;
146+
147+ const logHTML = this.logs.map(log => {
148+ const levelColor = {
149+ info: '#00ff00',
150+ warn: '#ffff00',
151+ error: '#ff0000',
152+ debug: '#00ffff'
153+ }[log.level];
154+
155+ const sourceIcon = log.source === 'backend' ? '🖥️' : '🌐';
156+
157+ return `
158+ <div style="margin-bottom: 2px; font-size: 11px;">
159+ <span style="color: #888;">[${log.timestamp}]</span>
160+ <span style="color: ${levelColor};">${sourceIcon} ${log.level.toUpperCase()}</span>
161+ <span style="color: #aaa;">[${log.category}]</span>
162+ <span style="color: #fff;">${log.message}</span>
163+ ${log.data ? `<pre style="color: #ccc; font-size: 10px; margin: 2px 0;">${JSON.stringify(log.data, null, 2)}</pre>` : ''}
164+ </div>
165+ `;
166+ }).join('');
167+
168+ this.logsContainer.innerHTML = logHTML;
169+
170+ // Auto-scroll to bottom
171+ this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
172+ }
173+
174+ public logKeyboardEvent(event: KeyboardEvent): void {
175+ const modifiers = [];
176+ if (event.ctrlKey) modifiers.push('Ctrl');
177+ if (event.shiftKey) modifiers.push('Shift');
178+ if (event.altKey) modifiers.push('Alt');
179+ if (event.metaKey) modifiers.push('Meta');
180+
181+ const shortcut = modifiers.length > 0 ? `${modifiers.join('+')}+${event.key}` : event.key;
182+
183+ this.addLog('info', 'frontend', 'keyboard', `Key pressed: ${shortcut}`, {
184+ key: event.key,
185+ code: event.code,
186+ modifiers,
187+ target: event.target?.constructor.name
188+ });
189+ }
190+
191+ public logMouseEvent(event: MouseEvent): void {
192+ const buttonNames: Record<number, string> = {
193+ 0: 'Left',
194+ 1: 'Middle',
195+ 2: 'Right',
196+ 3: 'Back',
197+ 4: 'Forward'
198+ };
199+
200+ this.addLog('info', 'frontend', 'mouse', `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`, {
201+ button: event.button,
202+ clientX: event.clientX,
203+ clientY: event.clientY,
204+ target: event.target?.constructor.name
205+ });
206+ }
207+
208+ public logServiceCall(serviceName: string, methodName: string, params: any[], result?: any, error?: Error): void {
209+ if (error) {
210+ this.addLog('error', 'frontend', 'service', `${serviceName}.${methodName}() failed`, {
211+ params,
212+ error: error.message
213+ });
214+ } else {
215+ this.addLog('info', 'frontend', 'service', `${serviceName}.${methodName}() called`, {
216+ params,
217+ result
218+ });
219+ }
220+ }
221+
222+ public logBackendMessage(message: string, data?: any): void {
223+ this.addLog('info', 'backend', 'service', message, data);
224+ }
225+
226+ public show(): void {
227+ if (this.debugElement) {
228+ this.debugElement.style.display = 'block';
229+ this.isVisible = true;
230+ this.addLog('info', 'frontend', 'debug', 'Debug console opened');
231+ }
232+ }
233+
234+ public hide(): void {
235+ if (this.debugElement) {
236+ this.debugElement.style.display = 'none';
237+ this.isVisible = false;
238+ }
239+ }
240+
241+ public toggle(): void {
242+ if (this.isVisible) {
243+ this.hide();
244+ } else {
245+ this.show();
246+ }
247+ }
248+
249+ public clearLogs(): void {
250+ this.logs = [];
251+ this.updateDisplay();
252+ this.addLog('info', 'frontend', 'debug', 'Logs cleared');
253+ }
254+
255+ public isDebugVisible(): boolean {
256+ return this.isVisible;
257+ }
258+}
A gui/src/services/notifications.ts +91 -0
@@ -0,0 +1,91 @@
1+import type { NotificationOptions } from '../types/app.js';
2+
3+export class NotificationService {
4+ private zoomNotification: HTMLElement | null = null;
5+ private generalNotification: HTMLElement | null = null;
6+
7+ showZoom(message: string, duration = 2000): void {
8+ // Create or update zoom notification
9+ if (!this.zoomNotification) {
10+ this.zoomNotification = this.createNotificationElement('zoom-notification', {
11+ top: '20px',
12+ right: '20px',
13+ background: 'rgba(0, 0, 0, 0.8)',
14+ color: 'white'
15+ });
16+ }
17+
18+ this.updateNotification(this.zoomNotification, message, duration);
19+ }
20+
21+ show(message: string, type: NotificationOptions['type'] = 'info', duration = 3000): void {
22+ // Create or update general notification
23+ if (!this.generalNotification) {
24+ this.generalNotification = this.createNotificationElement('general-notification', {
25+ top: '60px',
26+ right: '20px'
27+ });
28+ }
29+
30+ // Set colors based on type
31+ const colors = this.getNotificationColors(type);
32+ this.generalNotification.style.background = colors.background;
33+ this.generalNotification.style.color = colors.color;
34+
35+ this.updateNotification(this.generalNotification, message, duration);
36+ }
37+
38+ private createNotificationElement(id: string, baseStyles: Record<string, string>): HTMLElement {
39+ const notification = document.createElement('div');
40+ notification.id = id;
41+
42+ const styles = {
43+ position: 'fixed',
44+ padding: '10px 15px',
45+ borderRadius: '5px',
46+ fontSize: '14px',
47+ zIndex: '10000',
48+ transition: 'opacity 0.3s',
49+ opacity: '0',
50+ ...baseStyles
51+ };
52+
53+ notification.style.cssText = Object.entries(styles)
54+ .map(([key, value]) => `${this.camelToKebab(key)}: ${value}`)
55+ .join('; ');
56+
57+ document.body.appendChild(notification);
58+ return notification;
59+ }
60+
61+ private updateNotification(element: HTMLElement, message: string, duration: number): void {
62+ element.textContent = message;
63+ element.style.opacity = '1';
64+
65+ // Clear any existing timeout
66+ const existingTimeout = (element as any).timeout;
67+ if (existingTimeout) {
68+ clearTimeout(existingTimeout);
69+ }
70+
71+ // Hide after duration
72+ (element as any).timeout = setTimeout(() => {
73+ element.style.opacity = '0';
74+ }, duration);
75+ }
76+
77+ private getNotificationColors(type: NotificationOptions['type']): { background: string; color: string } {
78+ switch (type) {
79+ case 'error':
80+ return { background: 'rgba(220, 53, 69, 0.9)', color: 'white' };
81+ case 'success':
82+ return { background: 'rgba(25, 135, 84, 0.9)', color: 'white' };
83+ default:
84+ return { background: 'rgba(13, 110, 253, 0.9)', color: 'white' };
85+ }
86+ }
87+
88+ private camelToKebab(str: string): string {
89+ return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
90+ }
91+}⏎̸
A gui/src/styles/tailwind.css +252 -0
@@ -0,0 +1,252 @@
1+@import "tailwindcss";
2+
3+/* Define custom theme colors for v4 */
4+@theme {
5+ /* Light theme colors (default) */
6+ --color-browser-bg: rgb(255 255 255); /* white */
7+ --color-browser-surface: rgb(249 250 251); /* gray-50 */
8+ --color-browser-text: rgb(17 24 39); /* gray-900 */
9+ --color-browser-muted: rgb(107 114 128); /* gray-500 */
10+ --color-browser-accent: rgb(59 130 246); /* blue-500 */
11+ --color-browser-border: rgb(229 231 235); /* gray-200 */
12+}
13+
14+/* Dynamic theme switching via CSS variables */
15+:root {
16+ --dynamic-bg: var(--color-browser-bg);
17+ --dynamic-surface: var(--color-browser-surface);
18+ --dynamic-text: var(--color-browser-text);
19+ --dynamic-muted: var(--color-browser-muted);
20+ --dynamic-accent: var(--color-browser-accent);
21+ --dynamic-border: var(--color-browser-border);
22+}
23+
24+/* Dark theme colors (applied via .dark class from GTK) */
25+.dark {
26+ --dynamic-bg: rgb(17 24 39); /* gray-900 */
27+ --dynamic-surface: rgb(31 41 55); /* gray-800 */
28+ --dynamic-text: rgb(243 244 246); /* gray-100 */
29+ --dynamic-muted: rgb(156 163 175); /* gray-400 */
30+ --dynamic-accent: rgb(96 165 250); /* blue-400 */
31+ --dynamic-border: rgb(55 65 81); /* gray-700 */
32+}
33+
34+/* Base styles for browser UI components */
35+.browser-ui {
36+ background-color: var(--dynamic-bg);
37+ color: var(--dynamic-text);
38+ border-color: var(--dynamic-border);
39+ transition: colors 250ms;
40+}
41+
42+/* Toast notification styles */
43+.toast-base {
44+ border-radius: 0.5rem;
45+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
46+ padding: 0.75rem 1rem;
47+ max-width: 24rem;
48+ pointer-events: auto;
49+}
50+
51+.toast-success {
52+ background-color: rgb(240 253 244);
53+ color: rgb(22 163 74);
54+ border: 1px solid rgb(187 247 208);
55+}
56+
57+.dark .toast-success {
58+ background-color: rgb(22 163 74 / 0.2);
59+ color: rgb(187 247 208);
60+ border-color: rgb(22 163 74);
61+}
62+
63+.toast-error {
64+ background-color: rgb(254 242 242);
65+ color: rgb(220 38 38);
66+ border: 1px solid rgb(254 202 202);
67+}
68+
69+.dark .toast-error {
70+ background-color: rgb(220 38 38 / 0.2);
71+ color: rgb(254 202 202);
72+ border-color: rgb(220 38 38);
73+}
74+
75+.toast-info {
76+ background-color: rgb(239 246 255);
77+ color: rgb(59 130 246);
78+ border: 1px solid rgb(191 219 254);
79+}
80+
81+.dark .toast-info {
82+ background-color: rgb(59 130 246 / 0.2);
83+ color: rgb(191 219 254);
84+ border-color: rgb(59 130 246);
85+}
86+
87+/* Omnibox/Find bar styles */
88+.omnibox-base {
89+ background-color: var(--dynamic-bg);
90+ color: var(--dynamic-text);
91+ border: 2px solid var(--dynamic-border);
92+ border-radius: 0.375rem;
93+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
94+ transition: all 150ms;
95+}
96+
97+.omnibox-base:focus-within {
98+ border-color: var(--dynamic-accent);
99+}
100+
101+.omnibox-input {
102+ width: 100%;
103+ padding: 0.5rem 0.75rem;
104+ background-color: transparent;
105+ color: var(--dynamic-text);
106+ outline: none;
107+ font-size: 0.875rem;
108+}
109+
110+.omnibox-input::placeholder {
111+ color: var(--dynamic-muted);
112+}
113+
114+.omnibox-results {
115+ background-color: var(--dynamic-bg);
116+ border: 1px solid var(--dynamic-border);
117+ border-radius: 0.375rem;
118+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
119+ max-height: 24rem;
120+ overflow-y: auto;
121+}
122+
123+.omnibox-item {
124+ padding: 0.75rem 1rem;
125+ cursor: pointer;
126+ transition: colors 150ms;
127+ border-bottom: 1px solid var(--dynamic-border);
128+}
129+
130+.omnibox-item:hover,
131+.omnibox-item:focus {
132+ background-color: var(--dynamic-surface);
133+ outline: none;
134+}
135+
136+.omnibox-item.selected {
137+ background-color: color-mix(in srgb, var(--dynamic-accent) 10%, transparent);
138+ color: var(--dynamic-accent);
139+}
140+
141+/* Find bar specific styles */
142+.findbar {
143+ display: flex;
144+ align-items: center;
145+ gap: 0.5rem;
146+ padding: 0.5rem 0.75rem;
147+}
148+
149+.findbar-input {
150+ flex: 1;
151+ background-color: transparent;
152+ color: var(--dynamic-text);
153+ outline: none;
154+ font-size: 0.875rem;
155+}
156+
157+.findbar-input::placeholder {
158+ color: var(--dynamic-muted);
159+}
160+
161+.findbar-button {
162+ padding: 0.25rem 0.5rem;
163+ color: var(--dynamic-muted);
164+ transition: colors 150ms;
165+ font-size: 0.875rem;
166+}
167+
168+.findbar-button:hover {
169+ color: var(--dynamic-text);
170+}
171+
172+.findbar-count {
173+ font-size: 0.75rem;
174+ color: var(--dynamic-muted);
175+ font-variant-numeric: tabular-nums;
176+}
177+
178+/* Animation utilities */
179+.slide-in-top {
180+ animation: slide-in-top 0.25s ease-out;
181+}
182+
183+.slide-out-top {
184+ animation: slide-out-top 0.25s ease-in;
185+}
186+
187+.fade-in {
188+ animation: fade-in 0.15s ease-out;
189+}
190+
191+.fade-out {
192+ animation: fade-out 0.15s ease-in;
193+}
194+
195+@keyframes slide-in-top {
196+ from {
197+ transform: translateY(-100%);
198+ opacity: 0;
199+ }
200+ to {
201+ transform: translateY(0);
202+ opacity: 1;
203+ }
204+}
205+
206+@keyframes slide-out-top {
207+ from {
208+ transform: translateY(0);
209+ opacity: 1;
210+ }
211+ to {
212+ transform: translateY(-100%);
213+ opacity: 0;
214+ }
215+}
216+
217+@keyframes fade-in {
218+ from { opacity: 0; }
219+ to { opacity: 1; }
220+}
221+
222+@keyframes fade-out {
223+ from { opacity: 1; }
224+ to { opacity: 0; }
225+}
226+
227+/* Find highlighting styles for in-page search */
228+.find-highlight {
229+ background-color: rgb(255 235 59); /* yellow-400 */
230+ color: rgb(0 0 0); /* black */
231+ padding: 0 0.125rem; /* px-0.5 */
232+ border-radius: 0.125rem; /* rounded-sm */
233+ box-shadow: 0 0 0 1px rgb(200 185 0) inset;
234+}
235+
236+.find-active {
237+ background-color: rgb(255 152 0) !important; /* orange-500 */
238+ color: rgb(0 0 0); /* black */
239+ box-shadow: 0 0 0 1px rgb(179 107 0) inset !important;
240+}
241+
242+/* Utility for isolating component styles */
243+.browser-component-root {
244+ /* Reset any inherited styles from the website */
245+ all: initial;
246+ /* Apply our design system */
247+ font-family: Fira Sans, system-ui, -apple-system, sans-serif;
248+ box-sizing: border-box;
249+ * {
250+ box-sizing: border-box;
251+ }
252+}⏎̸
A gui/src/types/app.ts +26 -0
@@ -0,0 +1,26 @@
1+import type { HistoryEntry, SearchShortcut } from './generated.js';
2+
3+export interface AppState {
4+ currentURL: string;
5+ currentZoom: number;
6+ history: HistoryEntry[];
7+ shortcuts: Record<string, SearchShortcut>;
8+ isLoading: boolean;
9+}
10+
11+export interface NotificationOptions {
12+ message: string;
13+ type?: 'info' | 'success' | 'error';
14+ duration?: number;
15+}
16+
17+export interface KeyboardShortcut {
18+ key: string;
19+ ctrlKey?: boolean;
20+ metaKey?: boolean;
21+ shiftKey?: boolean;
22+ altKey?: boolean;
23+ handler: () => void | Promise<void>;
24+}
25+
26+export { HistoryEntry, SearchShortcut } from './generated.js';⏎̸
A gui/src/types/generated.ts +80 -0
@@ -0,0 +1,80 @@
1+// GENERATED FROM GO FILES: SEE THE GENERATOR SCRIPT
2+// Auto-generated TypeScript types from Go structs
3+// Generated on: 2025-09-09T09:57:41.059Z
4+// Generator script: scripts/generate-types.js
5+// Source files: internal/db/models.go, internal/parser/types.go
6+// Do not edit manually - run 'node scripts/generate-types.js' to regenerate
7+
8+export interface History {
9+ id: number;
10+ url: string;
11+ title: string | null;
12+ visit_count: number | null;
13+ last_visited: string | null;
14+ created_at: string | null;
15+}
16+
17+export interface Shortcut {
18+ id: number;
19+ shortcut: string;
20+ url_template: string;
21+ description: string | null;
22+ created_at: string | null;
23+}
24+
25+export interface ZoomSetting {
26+ id: number;
27+ url: string;
28+ zoom_level: number;
29+ last_updated: string | null;
30+}
31+
32+export interface ParseResult {
33+ type: InputType;
34+ url: string;
35+ query: string;
36+ confidence: number;
37+ fuzzy_matches?: FuzzyMatch[];
38+ shortcut?: DetectedShortcut;
39+ processing_time: number;
40+}
41+
42+export interface DetectedShortcut {
43+ key: string;
44+ query: string;
45+ url: string;
46+ description: string;
47+}
48+
49+export interface FuzzyMatch {
50+ history_entry?: History;
51+ score: number;
52+ url_score: number;
53+ title_score: number;
54+ recency_score: number;
55+ visit_score: number;
56+ matched_field: string;
57+}
58+
59+export interface FuzzyConfig {
60+ min_similarity_threshold: number;
61+ max_results: number;
62+ url_weight: number;
63+ title_weight: number;
64+ recency_weight: number;
65+ visit_weight: number;
66+ recency_decay_days: number;
67+}
68+
69+// Manual types and enums
70+export enum InputType {
71+ InputTypeDirectURL = 0,
72+ InputTypeSearchShortcut = 1,
73+ InputTypeHistorySearch = 2,
74+ InputTypeFallbackSearch = 3
75+}
76+
77+// Type aliases for frontend usage
78+export type HistoryEntry = History;
79+export type SearchShortcut = Shortcut;
80+export type ZoomEntry = ZoomSetting;
A gui/src/utils/dom.ts +196 -0
@@ -0,0 +1,196 @@
1+type HistoryEntry = { id: number; url: string; title: string };
2+type SearchShortcut = { description: string; url: string };
3+
4+export class DOMRenderer {
5+ private historyListElement: HTMLElement | null = null;
6+ private shortcutsElement: HTMLElement | null = null;
7+
8+ constructor() {
9+ this.historyListElement = document.getElementById('historyList');
10+ this.shortcutsElement = document.getElementById('shortcuts');
11+ }
12+
13+ displayHistory(historyEntries: HistoryEntry[]): void {
14+ if (!this.historyListElement) return;
15+
16+ if (!historyEntries || historyEntries.length === 0) {
17+ this.historyListElement.innerHTML = `
18+ <div class="empty-state">
19+ <h3>No history yet</h3>
20+ <p>Use CLI commands to browse websites</p>
21+ </div>
22+ `;
23+ return;
24+ }
25+
26+ this.historyListElement.innerHTML = '';
27+ historyEntries.forEach(item => {
28+ const historyItem = this.createHistoryItem(item);
29+ this.historyListElement!.appendChild(historyItem);
30+ });
31+ }
32+
33+ displayShortcuts(shortcutsData: Record<string, SearchShortcut>): void {
34+ if (!this.shortcutsElement) return;
35+
36+ if (!shortcutsData || Object.keys(shortcutsData).length === 0) {
37+ this.shortcutsElement.innerHTML = `
38+ <div class="empty-state">
39+ <h3>No shortcuts configured</h3>
40+ <p>Add shortcuts to your config file</p>
41+ </div>
42+ `;
43+ return;
44+ }
45+
46+ this.shortcutsElement.innerHTML = '';
47+ Object.entries(shortcutsData).forEach(([key, shortcut]) => {
48+ const shortcutEl = this.createShortcutElement(key, shortcut);
49+ this.shortcutsElement!.appendChild(shortcutEl);
50+ });
51+ }
52+
53+ showLoading(container: 'history' | 'shortcuts'): void {
54+ const element = container === 'history' ? this.historyListElement : this.shortcutsElement;
55+ if (element) {
56+ element.innerHTML = '<div class="loading">Loading...</div>';
57+ }
58+ }
59+
60+ showError(container: 'history' | 'shortcuts', message: string): void {
61+ const element = container === 'history' ? this.historyListElement : this.shortcutsElement;
62+ if (element) {
63+ element.innerHTML = `
64+ <div class="empty-state">
65+ <h3>Error</h3>
66+ <p>${message}</p>
67+ </div>
68+ `;
69+ }
70+ }
71+
72+ private createHistoryItem(item: HistoryEntry): HTMLElement {
73+ const historyItem = document.createElement('div');
74+ historyItem.className = 'history-item';
75+ const parsed = this.safeParseURL(item.url);
76+ const title = item.title && item.title.trim() !== '' ? item.title : (parsed.host || 'Untitled');
77+
78+ const line = document.createElement('div');
79+ line.className = 'history-line';
80+
81+ // Favicon chip (wrapper ensures perfect circle sizing)
82+ const iconWrap = document.createElement('div');
83+ iconWrap.className = 'history-favicon-chip';
84+ const icon = document.createElement('img');
85+ icon.className = 'history-favicon-img';
86+ icon.width = 16; icon.height = 16;
87+ icon.loading = 'lazy';
88+ icon.referrerPolicy = 'no-referrer';
89+ icon.src = this.buildFaviconURL(item.url);
90+ icon.addEventListener('error', () => { iconWrap.style.display = 'none'; });
91+ iconWrap.appendChild(icon);
92+
93+ const titleEl = document.createElement('span');
94+ titleEl.className = 'history-title';
95+ titleEl.textContent = title;
96+
97+ const sep1 = document.createElement('span');
98+ sep1.className = 'history-sep';
99+ sep1.textContent = ' - ';
100+
101+ const domainEl = document.createElement('span');
102+ domainEl.className = 'history-domain';
103+ domainEl.textContent = parsed.host || '';
104+
105+ const sep2 = document.createElement('span');
106+ sep2.className = 'history-sep';
107+ sep2.textContent = ' - ';
108+
109+ const urlEl = document.createElement('span');
110+ urlEl.className = 'history-url';
111+ urlEl.textContent = item.url;
112+
113+ line.appendChild(iconWrap);
114+ line.appendChild(titleEl);
115+ line.appendChild(sep1);
116+ line.appendChild(domainEl);
117+ line.appendChild(sep2);
118+ line.appendChild(urlEl);
119+
120+ historyItem.appendChild(line);
121+
122+ historyItem.addEventListener('click', () => {
123+ this.navigateToUrl(item.url);
124+ });
125+
126+ return historyItem;
127+ }
128+
129+ private createShortcutElement(key: string, shortcut: SearchShortcut): HTMLElement {
130+ const shortcutEl = document.createElement('div');
131+ shortcutEl.className = 'shortcut';
132+ shortcutEl.innerHTML = `
133+ <div class="shortcut-key">${this.escapeHtml(key)}:</div>
134+ <div class="shortcut-desc">${this.escapeHtml(shortcut.description)}</div>
135+ `;
136+
137+ shortcutEl.addEventListener('click', () => {
138+ // Navigate to the base URL of the shortcut (remove %s template parameter)
139+ const baseUrl = this.extractBaseUrl(shortcut.url);
140+ this.navigateToUrl(baseUrl);
141+ });
142+
143+ return shortcutEl;
144+ }
145+
146+ private async navigateToUrl(url: string): Promise<void> {
147+ console.log('Navigating to:', url);
148+ window.location.href = url;
149+ }
150+
151+ private extractBaseUrl(templateUrl: string): string {
152+ try {
153+ // Support both %s and {query} placeholders
154+ const candidate = templateUrl.replace(/%s|\{query\}/g, "");
155+ const url = new URL(candidate);
156+ // Remove empty query params left after placeholder removal
157+ const params = new URLSearchParams(url.search);
158+ for (const key of Array.from(params.keys())) {
159+ const v = params.get(key);
160+ if (v === null || v === "") {
161+ params.delete(key);
162+ }
163+ }
164+ url.search = params.toString();
165+ return url.toString();
166+ } catch (error) {
167+ console.error('Failed to parse shortcut URL:', templateUrl, error);
168+ throw error;
169+ }
170+ }
171+
172+ private escapeHtml(text: string): string {
173+ const div = document.createElement('div');
174+ div.textContent = text;
175+ return div.innerHTML;
176+ }
177+
178+ private safeParseURL(raw: string): { host: string } {
179+ try {
180+ const u = new URL(raw);
181+ return { host: u.host };
182+ } catch {
183+ return { host: '' };
184+ }
185+ }
186+
187+ private buildFaviconURL(raw: string): string {
188+ try {
189+ const u = new URL(raw);
190+ const scheme = u.protocol && u.protocol !== ':' ? u.protocol : 'https:';
191+ return `${scheme}//${u.host}/favicon.ico`;
192+ } catch {
193+ return '';
194+ }
195+ }
196+}
A gui/src/utils/html-builder.ts +251 -0
@@ -0,0 +1,251 @@
1+export class HTMLBuilder {
2+ static createBaseHTML(): string {
3+ const faviconData = this.getFaviconDataURL();
4+ return `
5+<!DOCTYPE html>
6+<html lang="en">
7+<head>
8+ <meta charset="UTF-8">
9+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10+ <title>Dumber Browser</title>
11+ <link rel="icon" type="image/svg+xml" href="${faviconData}">
12+ <style>
13+ ${this.getStyles()}
14+ </style>
15+</head>
16+<body>
17+ <div class="container">
18+ <div class="history-section">
19+ <h2 class="section-title">Recent History</h2>
20+ <div id="historyList" class="history-list">
21+ <div class="loading">Loading history...</div>
22+ </div>
23+ </div>
24+
25+ <div class="shortcuts-section">
26+ <h2 class="section-title">Search Shortcuts</h2>
27+ <div id="shortcuts" class="shortcuts-grid">
28+ <div class="loading">Loading shortcuts...</div>
29+ </div>
30+ </div>
31+ </div>
32+ <script type="module" src="./main.js"></script>
33+</body>
34+</html>`;
35+ }
36+
37+ private static getFaviconDataURL(): string {
38+ const svg = HTMLBuilder.getFaviconSVG();
39+ // Minimal encoding for safe inline usage
40+ const encoded = encodeURIComponent(svg)
41+ .replace(/'/g, '%27')
42+ .replace(/\(/g, '%28')
43+ .replace(/\)/g, '%29');
44+ return `data:image/svg+xml;utf8,${encoded}`;
45+ }
46+
47+ static getFaviconSVG(): string {
48+ // Stylized D with speed bolt on a subtle dark rounded card
49+ return `<?xml version="1.0" encoding="UTF-8"?>
50+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
51+ <defs>
52+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
53+ <stop offset="0%" stop-color="#121212"/>
54+ <stop offset="100%" stop-color="#1e1e1e"/>
55+ </linearGradient>
56+ <filter id="inset" x="-20%" y="-20%" width="140%" height="140%">
57+ <feOffset dx="0" dy="1"/>
58+ <feGaussianBlur stdDeviation="0.6" result="blur"/>
59+ <feComposite in2="SourceAlpha" operator="out"/>
60+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
61+ <feBlend in="SourceGraphic" mode="normal"/>
62+ </filter>
63+ </defs>
64+ <rect x="4" y="4" width="56" height="56" rx="14" ry="14" fill="url(#bg)" filter="url(#inset)"/>
65+ <rect x="4.5" y="4.5" width="55" height="55" rx="13.5" ry="13.5" fill="none" stroke="#252a2f" stroke-width="1"/>
66+ <!-- D glyph (slightly larger and wider) -->
67+ <path d="M20 16 L20 48 M20 16 H34 q12 0 12 16 t-12 16 H20" fill="none" stroke="#e9eef3" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(33,32) scale(1.12,1.10) translate(-33,-32)"/>
68+ <!-- Speed bolt accent (significantly larger, centered) -->
69+ <path d="M36 22 L32 34 h6 l-8 16 4-12 h-6 z" fill="#ff9800" opacity="0.95" transform="translate(33,32) scale(1.8) translate(-33,-32) translate(0,-3)"/>
70+</svg>`;
71+ }
72+
73+ private static getStyles(): string {
74+ return `
75+ * {
76+ margin: 0;
77+ padding: 0;
78+ box-sizing: border-box;
79+ }
80+
81+ body {
82+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
83+ background: #1a1a1a;
84+ color: #ffffff;
85+ height: 100vh;
86+ display: flex;
87+ flex-direction: column;
88+ overflow-x: hidden; /* prevent horizontal scroll from long content */
89+ }
90+
91+ .container {
92+ flex: 1;
93+ display: flex;
94+ max-width: 1200px;
95+ margin: 0 auto;
96+ width: 100%;
97+ padding: 2rem;
98+ gap: 2rem;
99+ overflow-x: hidden;
100+ }
101+
102+ .history-section {
103+ flex: 1;
104+ min-height: 0;
105+ min-width: 0; /* allow flex child to shrink */
106+ }
107+
108+ .shortcuts-section {
109+ flex: 1;
110+ min-height: 0;
111+ min-width: 0; /* allow flex child to shrink */
112+ }
113+
114+ .section-title {
115+ font-size: 1.5rem;
116+ margin-bottom: 1rem;
117+ color: #ffffff;
118+ border-bottom: 2px solid #404040;
119+ padding-bottom: 0.5rem;
120+ }
121+
122+ .history-list {
123+ overflow-y: auto;
124+ overflow-x: hidden;
125+ max-height: calc(100vh - 8rem);
126+ max-width: 100%;
127+ }
128+
129+ .history-item {
130+ padding: 0.75rem;
131+ margin-bottom: 0.5rem;
132+ background: #2d2d2d;
133+ border-radius: 6px;
134+ cursor: pointer;
135+ transition: background 0.2s;
136+ border-left: 3px solid #404040;
137+ overflow: hidden;
138+ max-width: 100%;
139+ }
140+
141+ .history-item:hover {
142+ background: #3d3d3d;
143+ border-left-color: #0066cc;
144+ }
145+
146+ .history-line {
147+ display: flex;
148+ gap: 0.5rem;
149+ white-space: nowrap;
150+ align-items: center;
151+ overflow: hidden;
152+ width: 100%;
153+ min-width: 0; /* allow flex children to shrink */
154+ }
155+ .history-favicon-chip {
156+ flex: 0 0 20px;
157+ width: 20px; height: 20px;
158+ border-radius: 50%;
159+ background: #ccc;
160+ border: 1px solid rgba(0,0,0,.12);
161+ box-shadow: 0 1px 2px rgba(0,0,0,.12);
162+ display: flex;
163+ align-items: center;
164+ justify-content: center;
165+ }
166+ .history-favicon-img {
167+ width: 16px; height: 16px;
168+ filter: brightness(1.06) contrast(1.03);
169+ image-rendering: -webkit-optimize-contrast;
170+ }
171+
172+ .history-title { font-size: 0.95rem; color: #e6e6e6; flex: 0 0 auto; }
173+ .history-domain { font-size: 0.9rem; color: #a5a5a5; flex: 0 0 auto; }
174+ .history-sep { color: #666; flex: 0 0 auto; }
175+ .history-url {
176+ font-size: 0.9rem;
177+ color: #7a7a7a; /* darker than domain */
178+ flex: 1 1 auto;
179+ min-width: 0; /* critical for flex truncation */
180+ overflow: hidden;
181+ text-overflow: ellipsis; /* fallback */
182+ -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
183+ mask-image: linear-gradient(to right, rgba(0,0,0,1) 85%, rgba(0,0,0,0) 100%);
184+ }
185+
186+ .shortcuts-grid {
187+ display: grid;
188+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
189+ gap: 1rem;
190+ max-height: calc(100vh - 8rem);
191+ overflow-y: auto;
192+ }
193+
194+ .shortcut {
195+ padding: 1rem;
196+ background: #2d2d2d;
197+ border: 2px solid #404040;
198+ border-radius: 8px;
199+ cursor: pointer;
200+ transition: all 0.2s;
201+ text-align: center;
202+ }
203+
204+ .shortcut:hover {
205+ background: #3d3d3d;
206+ border-color: #0066cc;
207+ transform: translateY(-2px);
208+ }
209+
210+ .shortcut-key {
211+ font-weight: bold;
212+ color: #0066cc;
213+ margin-bottom: 0.5rem;
214+ font-size: 1.1rem;
215+ }
216+
217+ .shortcut-desc {
218+ color: #888;
219+ font-size: 0.9rem;
220+ }
221+
222+ .loading {
223+ padding: 2rem;
224+ text-align: center;
225+ color: #888;
226+ }
227+
228+ .empty-state {
229+ padding: 3rem 2rem;
230+ text-align: center;
231+ color: #666;
232+ }
233+
234+ .empty-state h3 {
235+ margin-bottom: 1rem;
236+ color: #888;
237+ }
238+
239+ @media (max-width: 768px) {
240+ .container {
241+ flex-direction: column;
242+ padding: 1rem;
243+ }
244+
245+ .shortcuts-grid {
246+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
247+ }
248+ }
249+ `;
250+ }
251+}
A gui/svelte.config.js +10 -0
@@ -0,0 +1,10 @@
1+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
2+
3+export default {
4+ // Enable rune mode for Svelte 5
5+ compilerOptions: {
6+ runes: true,
7+ },
8+ // Use Vite's preprocessor for TypeScript and PostCSS
9+ preprocess: vitePreprocess(),
10+};⏎̸
A gui/tailwind.config.js +44 -0
@@ -0,0 +1,44 @@
1+/** @type {import('tailwindcss').Config} */
2+export default {
3+ content: [
4+ './src/**/*.{html,js,ts,svelte}',
5+ ],
6+ theme: {
7+ extend: {
8+ // Custom design system colors
9+ colors: {
10+ // Browser-specific colors that work well in both themes
11+ browser: {
12+ bg: 'rgb(var(--browser-bg) / <alpha-value>)',
13+ surface: 'rgb(var(--browser-surface) / <alpha-value>)',
14+ text: 'rgb(var(--browser-text) / <alpha-value>)',
15+ muted: 'rgb(var(--browser-muted) / <alpha-value>)',
16+ accent: 'rgb(var(--browser-accent) / <alpha-value>)',
17+ border: 'rgb(var(--browser-border) / <alpha-value>)',
18+ }
19+ },
20+ // Animation speeds optimized for browser UI
21+ transitionDuration: {
22+ '150': '150ms', // Fast interactions
23+ '250': '250ms', // UI feedback
24+ '400': '400ms', // Smooth transitions
25+ },
26+ // Typography scale for browser UI
27+ fontSize: {
28+ 'xs': ['0.75rem', '1rem'],
29+ 'sm': ['0.875rem', '1.25rem'],
30+ 'base': ['1rem', '1.5rem'],
31+ 'lg': ['1.125rem', '1.75rem'],
32+ },
33+ // Spacing for compact UI elements
34+ spacing: {
35+ '18': '4.5rem',
36+ '22': '5.5rem',
37+ }
38+ },
39+ },
40+ plugins: [],
41+ // Manual dark mode control (not system preference)
42+ // This allows GTK to control theme via CSS class
43+ darkMode: 'class',
44+};⏎̸
A gui/tsconfig.json +33 -0
@@ -0,0 +1,33 @@
1+{
2+ "compilerOptions": {
3+ "target": "ES2022",
4+ "module": "ESNext",
5+ "moduleResolution": "bundler",
6+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7+ "allowImportingTsExtensions": true,
8+ "moduleDetection": "force",
9+ "noEmit": true,
10+ "strict": true,
11+ "noUnusedLocals": true,
12+ "noUnusedParameters": true,
13+ "noFallthroughCasesInSwitch": true,
14+ "noUncheckedIndexedAccess": true,
15+ "skipLibCheck": true,
16+ "esModuleInterop": true,
17+ "allowSyntheticDefaultImports": true,
18+ "forceConsistentCasingInFileNames": true,
19+ "baseUrl": ".",
20+ "paths": {
21+ "$lib/*": ["src/lib/*"],
22+ "$components/*": ["src/components/*"]
23+ }
24+ },
25+ "include": [
26+ "src/**/*",
27+ "vite.config.ts"
28+ ],
29+ "exclude": [
30+ "node_modules",
31+ "dist"
32+ ]
33+}⏎̸
A gui/vite-plugin-pages.ts +98 -0
@@ -0,0 +1,98 @@
1+import type { Plugin } from 'vite';
2+import { writeFileSync, readFileSync } from 'fs';
3+import { resolve } from 'path';
4+
5+interface PageConfig {
6+ /** Page name (used for filename and script name) */
7+ name: string;
8+ /** Page title */
9+ title: string;
10+ /** Script filename (e.g., 'homepage.min.js') */
11+ script: string;
12+ /** Optional CSS filename */
13+ css?: string;
14+ /** Target filename (defaults to name + '.html') */
15+ filename?: string;
16+}
17+
18+// Load the favicon SVG from assets
19+function getFaviconSVG(): string {
20+ const logoPath = resolve(__dirname, '..', 'assets', 'logo.svg');
21+ return readFileSync(logoPath, 'utf-8');
22+}
23+
24+// Create favicon data URL for inline usage
25+function getFaviconDataURL(): string {
26+ const svg = getFaviconSVG();
27+ const encoded = encodeURIComponent(svg)
28+ .replace(/'/g, '%27')
29+ .replace(/\(/g, '%28')
30+ .replace(/\)/g, '%29');
31+ return `data:image/svg+xml;utf8,${encoded}`;
32+}
33+
34+// Generate HTML content for a page
35+function generatePageHTML(config: PageConfig): string {
36+ const cssLink = config.css ? `<link rel="stylesheet" href="./${config.css}">` : '';
37+
38+ return `<!DOCTYPE html>
39+<html lang="en">
40+<head>
41+ <meta charset="UTF-8">
42+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
43+ <title>${config.title}</title>
44+ <link rel="icon" type="image/svg+xml" href="${getFaviconDataURL()}">
45+ ${cssLink}
46+</head>
47+<body>
48+ <script src="./${config.script}"></script>
49+</body>
50+</html>`;
51+}
52+
53+/**
54+ * Vite plugin to generate dumb:// protocol pages
55+ * Supports multiple pages like homepage, config, about, etc.
56+ */
57+export function pageGenerator(pages: PageConfig[]): Plugin {
58+ return {
59+ name: 'dumb-page-generator',
60+ writeBundle() {
61+ const assetsDir = resolve(__dirname, '..', 'assets', 'gui');
62+
63+ try {
64+ // Generate HTML files for each page
65+ for (const page of pages) {
66+ const htmlContent = generatePageHTML(page);
67+ const filename = page.filename || `${page.name}.html`;
68+ writeFileSync(resolve(assetsDir, filename), htmlContent);
69+ console.log(`✓ Generated ${filename} for dumb://${page.name}`);
70+ }
71+
72+ // Generate favicon.svg for scheme handler lookups
73+ const faviconContent = getFaviconSVG();
74+ writeFileSync(resolve(assetsDir, 'favicon.svg'), faviconContent);
75+ console.log('✓ Generated favicon.svg');
76+
77+ } catch (error) {
78+ console.error('✗ Failed to generate page files:', error);
79+ throw error;
80+ }
81+ }
82+ };
83+}
84+
85+/**
86+ * Convenience function for homepage generation
87+ * @deprecated Use pageGenerator with homepage config instead
88+ */
89+export function homepageGenerator(): Plugin {
90+ return pageGenerator([
91+ {
92+ name: 'homepage',
93+ title: 'Dumber Browser',
94+ script: 'homepage.min.js',
95+ filename: 'index.html', // Special case: homepage uses index.html
96+ }
97+ ]);
98+}⏎̸
A gui/vite.config.gui.ts +35 -0
@@ -0,0 +1,35 @@
1+import { defineConfig } from 'vite';
2+import { svelte } from '@sveltejs/vite-plugin-svelte';
3+import { resolve } from 'path';
4+
5+export default defineConfig({
6+ plugins: [
7+ svelte(),
8+ ],
9+ build: {
10+ rollupOptions: {
11+ input: resolve(__dirname, 'src/injected/gui.ts'),
12+ output: {
13+ dir: '../assets/gui',
14+ format: 'iife',
15+ entryFileNames: 'gui.min.js',
16+ name: '__dumberGUI',
17+ inlineDynamicImports: true,
18+ assetFileNames: '[name].[ext]', // Don't hash asset names
19+ },
20+ },
21+ emptyOutDir: false,
22+ target: ['es2020', 'chrome91', 'firefox90'],
23+ minify: true,
24+ sourcemap: false,
25+ cssCodeSplit: false, // Keep CSS in single file
26+ assetsInlineLimit: 0, // Don't inline assets by default
27+ },
28+ resolve: {
29+ alias: {
30+ // Alias for easier imports
31+ '$lib': resolve(__dirname, 'src/lib'),
32+ '$components': resolve(__dirname, 'src/components'),
33+ },
34+ },
35+});⏎̸
A gui/vite.config.homepage.ts +51 -0
@@ -0,0 +1,51 @@
1+import { defineConfig } from 'vite';
2+import { svelte } from '@sveltejs/vite-plugin-svelte';
3+import { resolve } from 'path';
4+import { pageGenerator } from './vite-plugin-pages';
5+
6+export default defineConfig({
7+ plugins: [
8+ svelte(),
9+ pageGenerator([
10+ {
11+ name: 'homepage',
12+ title: 'Dumber Browser',
13+ script: 'homepage.min.js',
14+ css: 'homepage.css',
15+ filename: 'index.html', // Special case: homepage uses index.html
16+ }
17+ ]),
18+ ],
19+ build: {
20+ rollupOptions: {
21+ input: resolve(__dirname, 'src/pages/homepage.ts'),
22+ output: {
23+ dir: '../assets/gui',
24+ format: 'iife',
25+ entryFileNames: 'homepage.min.js',
26+ name: '__dumberHomepage',
27+ inlineDynamicImports: true,
28+ assetFileNames: (assetInfo) => {
29+ // Keep CSS as homepage.css to avoid conflicts with the main GUI
30+ if (assetInfo.name?.endsWith('.css')) {
31+ return 'homepage.[ext]';
32+ }
33+ return '[name].[ext]';
34+ },
35+ },
36+ },
37+ emptyOutDir: false, // Don't clear the directory (shared with GUI build)
38+ target: ['es2020', 'chrome91', 'firefox90'],
39+ minify: true,
40+ sourcemap: false,
41+ cssCodeSplit: false, // Keep CSS in single file
42+ assetsInlineLimit: 0, // Don't inline assets by default
43+ },
44+ resolve: {
45+ alias: {
46+ // Alias for easier imports
47+ '$lib': resolve(__dirname, 'src/lib'),
48+ '$components': resolve(__dirname, 'src/components'),
49+ },
50+ },
51+});⏎̸

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close