diff --git a/README.md b/README.md index 90802cd..31342c3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,33 @@ # sky_phone +## Payphones + +Payphone dialing is validated against server-owned booth positions. Vanilla GTA V positions live +in `sky_phone/config/payphones.lua`; the server compares its own player coordinates with this list +and does not trust coordinates or models sent by the NUI or client. + +For a custom map or MLO, add each booth to `Config.Payphones.Locations` in that server-only file: + +```lua +{ model = "prop_phonebox_01a", coords = { x = 123.45, y = 678.90, z = 21.0 } }, +``` + +The model must also be present in `Config.Payphones.Props`. Coordinates must be finite Lua numbers +inside the supported world bounds. Restart `sky_phone` after changing the list. If payphones are +enabled but the list is empty or contains invalid entries, the server logs a visible warning and +rejects calls that cannot be matched to a valid configured location. + +## Custom Apps + +`sky_phone` erkennt Registrierungen gestarteter Fremd-App-Ressourcen über integrierte +Hersteller-Aliase und übernimmt unterstützte Apps automatisch in Springboard und App Store. In +App-Ressourcen müssen dafür keine Sky-Vorlagen abgelegt werden. Unterstützt werden die +dokumentierten Basisverträge von LB Phone, 17Movement, High Phone, Quasar Smartphone V3 und +YSeries; die Resource- und Cfx-Export-Aliase `lb-phone`, `17mov_Phone`, `high-phone`, +`qs-smartphone` und `yseries` werden direkt von `sky_phone` bereitgestellt. Einrichtung und +ehrliche Kompatibilitätsgrenzen stehen in der +[deutschen Custom-App-Anleitung](docs/custom-apps.md). + ## FlipTok verification FlipTok verification is server-authoritative and limited to the framework groups configured in @@ -57,13 +85,13 @@ existing playlist entries can no longer resolve the track. Players can add public YouTube video links to their own library. Metadata is requested through YouTube's oEmbed endpoint on the server, while playback uses the embedded YouTube player only on -that player's NUI. Personal songs and playlists are stored per linked iFruit account, or per phone +that player's NUI. Personal songs and playlists are stored per linked Sky Cloud account, or per phone IMEI while signed out, in the `sky_phone_music_*` tables. Limits and rate controls are configured in `config/music.lua`. ## FlipTok accounts -FlipTok profiles use their own username and password login. Registration requires a linked iFruit +FlipTok profiles use their own username and password login. Registration requires a linked Sky Cloud account once so an existing creator profile, videos, followers, and verification can be claimed without data loss. Login sessions are stored per phone IMEI and survive resource or server restarts; signing out removes only that device session. @@ -78,47 +106,140 @@ set sky_phone_fliptok_password_pepper "replace-with-a-long-random-secret" Passwords are stored as salted hashes. The pepper is read server-side from the convar and is never included in the NUI bundle. -Standalone FiveM phone built with Vue 3, TypeScript, Pinia, Vue Router, Konsta UI 5, and Tailwind CSS 4. Each non-stackable `phone` item receives a unique 15-digit IMEI and owns its server-persisted device state. The phone opens through the usable item; `/phone` is disabled unless `Config.Phone.DevelopmentCommand` is enabled explicitly. +Standalone FiveM phone built with Vue 3, TypeScript, Pinia, Vue Router, the Sky-owned FiveM UI system, and Tailwind CSS 4. Konsta UI remains only for screens that have not yet completed their migration. The phone opens through the usable item; `/phone` is disabled unless `Config.Phone.DevelopmentCommand` is enabled explicitly. Phone identity and SIM-card behavior are selected independently through `Config.Phone.Unique` and `Config.Sim.Enabled`. -An iFruit account is optional. Unlinked devices retain local settings, alarms, media, apps, notes, contacts, and recent calls. Linking from Mail or Settings moves local data into an empty cloud account; an existing cloud dataset wins over local contacts and recents. Signing out keeps an editable local snapshot without deleting cloud data. +### Ingame test data + +On development servers, enable `Config.TestData.Enabled` and run `/phonetestdata` as the player who +owns the phone. The command creates or refreshes idempotent, player-scoped fixtures for contacts, +calls, messages, mail, notes, gallery, banking history, billing, calendar, map markers, music, radio, +EasyShare, CityMarkt, Local Pages, Picstagram, FlipTok, Feather, Flare, DarkChat, CrewLink, SkyRide, +and company requests. It also creates a linked Sky Cloud account and a registered SIM when the selected +phone does not have them yet. Reopen the phone after the command completes. + +Garage and Housing intentionally continue to use the real configured provider data. Apps without +persistent content, such as Calculator, Camera, Clock, Weather, Settings, and Payphone, do not need +database fixtures. Set `Config.TestData.AdminOnly = true` to restrict the command to the configured +framework admin groups, and disable the feature outside development environments. + +A Sky Cloud account is optional. Unlinked devices retain local settings, alarms, media, apps, notes, contacts, and recent calls. Linking from Mail or Settings moves local data into an empty Sky Cloud account; an existing Sky Cloud dataset wins over local contacts and recents. Signing out keeps an editable local snapshot without deleting Sky Cloud data. + +## Phone identity and SIM modes + +Both compatibility switches default to the physical, unique-item behavior: + +```lua +Config.Phone.Unique = true +Config.Sim.Enabled = true +``` + +For backwards compatibility, an omitted switch is also treated as `true`. + +With `Config.Phone.Unique = true`, every phone item is one transferable physical Device. Its unique +15-digit IMEI is stored in item metadata, so its settings, PIN, local app data, linked Sky Cloud +account, and installed SIM travel with that item. The item must be non-stackable. + +With `Config.Phone.Unique = false`, the server assigns each framework character one persistent +virtual Device. Possessing any configured phone item grants access to that Device, but the item does +not own its IMEI or data. Losing a handset therefore removes access only until the character obtains +another one; the replacement opens the same data, PIN, account session, and number. Phone items may +be stackable in this mode, although at least one phone item is still required to use or receive the +phone. The character mapping is server-authoritative and uses the framework character identifier, +never a client-supplied owner value. + +With `Config.Sim.Enabled = true`, a physical registered or anonymous SIM item must be inserted before +the Device has cellular service. SIM items remain unique and non-stackable. With +`Config.Sim.Enabled = false`, SIM inventory items are not required. The first time a Device without +an attached SIM is resolved, the server creates an anonymous virtual SIM with a random unique phone +number using `Config.Sim.NumberPrefix` and `NumberLength`; `NumberGroups` controls its display +format. Physical SIM insertion and ejection are disabled in this mode. An already attached physical +SIM and its number are preserved when SIM requirements are disabled. + +The switches are independent. An automatic number follows a physical handset when unique phones are +enabled and follows the character's persistent virtual Device when unique phones are disabled. +Likewise, a physical SIM inserted while unique phones are disabled belongs to the character's +virtual Device and remains available after replacing the handset. + +### Existing installations and first use + +After changing an existing installation to `Config.Phone.Unique = false`, the first phone item a +character uses establishes the persistent mapping. If that item has a valid legacy IMEI which is not +already mapped to another character, the complete existing Device is adopted. This preserves its +local content, PIN, linked account, and attached SIM. If the legacy IMEI cannot be adopted, the +server creates a fresh virtual Device instead. Historic unique Devices did not record an owner, so +the character carrying a handset on its first use after the change is the character that claims it. + +When `Config.Sim.Enabled = false`, an existing attached physical SIM is kept; only Devices without a +SIM receive a random virtual one. If SIM cards are enabled again, automatically created virtual SIMs +are detached during resource startup so those Devices once again require a physical SIM. Stored +character-to-Device mappings remain available, but changing back to unique phones does not copy a +virtual Device's data onto an arbitrary inventory item. Treat production mode changes as migrations +and restart the resource after updating the configuration. ## Requirements - ESX Legacy (`es_extended`), Qbox (`qbx_core`), or QBCore (`qb-core`). The bridge selects a running supported framework when `Config.Bridge.Framework` is set to `"auto"`. - A supported inventory: `ox_inventory`, `qb-inventory`, `lj-inventory`, `qs-inventory`, `codem-inventory`, `core_inventory`, `mf-inventory`, or `smx-inventory`. The bridge auto-detects a running provider and normalizes metadata, slots, counts, item mutations, and usable-item callbacks. `mf-inventory` and `smx-inventory` require ESX. Because SMX stores standard ESX items as stacks, its adapter persists one active Phone/SIM metadata record per player and item type in ESX player metadata. -- A non-stackable inventory item named `phone`. -- Two unique, non-stackable inventory items named `sky_phone_sim_registered` and `sky_phone_sim_anonymous`. Their metadata is initialized automatically on first use, so shops and crafting recipes add plain items without supplying a number. +- An inventory item named `phone`. It must be non-stackable when `Config.Phone.Unique = true` and may be stackable when it is `false`. +- When `Config.Sim.Enabled = true`, two unique, non-stackable inventory items named `sky_phone_sim_registered` and `sky_phone_sim_anonymous`. Their metadata is initialized automatically on first use, so shops and crafting recipes add plain items without supplying a number. These item definitions are not required when SIM cards are disabled. - `oxmysql` with MySQL/MariaDB. - `pma-voice` when `Config.Calls.VoiceProvider` is set to `"pma"`. -- A FiveManage V3 Media API token for Camera photo/video uploads and Gallery deletion. Set the - server-only `Config.Media.FiveManage.ApiKey` in `sky_phone/config/media.lua`; the token is never - sent to NUI because clients receive temporary presigned upload URLs instead. +- A FiveManage V3 Media API token for Camera photo/video uploads and Gallery deletion. Set it as a + server-only convar; the token is never sent to NUI because clients receive temporary presigned + upload URLs instead: + +```cfg +set sky_phone_fivemanage_api_key "replace-with-your-media-token" +``` - `yaca-voice`, `pma-voice`, or `saltychat` when the Radio app is enabled. `Config.Radio.VoiceProvider = "auto"` selects the first running provider in that order. ## Messages GIF provider -Configure GIF search in `sky_phone/config/config.lua`: +Configure GIF search as a server-only convar: -```lua -Config.Media.GiphyApiKey = "YOUR_GIPHY_API_KEY" +```cfg +set sky_phone_giphy_api_key "replace-with-your-giphy-api-key" ``` -GIPHY provides trending and searched GIFs through a paginated server-side proxy. The shared -`config.lua` is loaded by both FiveM runtimes, so its values are available to clients even though -only the server uses the GIPHY key. Photo and video actions in Messages are intentionally inactive -until their dedicated implementation is available. +GIPHY provides trending and searched GIFs through a paginated server-side proxy. Only the server +reads the key. Photo and video actions in Messages use media captured by the Camera app. -Database migrations run automatically. Existing `sky_phone_mail_accounts` installations are renamed to `sky_phone_accounts` while preserving account IDs and mail foreign keys. iFruit passwords are intentional in-character credentials and remain plaintext `VARCHAR(64)` values; registration screens warn players never to reuse a real password. +Database migrations run automatically. Existing `sky_phone_mail_accounts` installations are renamed to `sky_phone_accounts` while preserving account IDs and mail foreign keys. The migration also creates `sky_phone_character_devices` for persistent non-unique phone mappings and marks automatic SIMs through `sky_phone_sims.is_virtual`. Sky Cloud passwords are intentional in-character credentials and remain plaintext `VARCHAR(64)` values; registration screens warn players never to reuse a real password. Camera and Gallery media is stored in `sky_phone_media`. Signed-out captures belong to the current -IMEI; linking an iFruit account moves those rows into the account gallery so every linked phone sees -them. Signing out hides cloud media without deleting it. Factory reset removes device-local media -and attempts to delete its remote FiveManage files, while account-owned media remains in the cloud. +IMEI; linking a Sky Cloud account moves those rows into the account gallery so every linked phone sees +them. Signing out hides Sky Cloud media without deleting it. Factory reset removes device-local media +and attempts to delete only remote FiveManage files created by `sky_phone`; account-owned media +remains in Sky Cloud. Media imported from a website is removed locally but never deleted at its +source. Register import websites under `Config.Media.Import.Websites` in +`sky_phone/config/media.lua`; built-in adapters support FiveManage files and version-1 JSON +manifests. The Gallery import form accepts direct HTTPS image/video links only when their hostname +matches the selected website's `AllowedMediaHosts`. For a fresh manual database installation, import `sky_phone/sql/install.sql`. It contains the complete current table, key, index, collation, and foreign-key schema. Runtime migrations remain authoritative for upgrading an existing installation and must stay enabled. Framework, inventory, callback, notification, and database integrations live under `sky_phone/source/bridge`. The resource has no dependency on any other Sky resource. +## Weazel News app + +The built-in Weazel News app publishes articles for every phone user and exposes its editorial +desk only to server-authorized jobs. Configure the job-to-minimum-grade map in +`sky_phone/config/weazel_news.lua`; unlisted jobs remain read-only: + +```lua +AllowedJobs = { + weazel = 0, + reporter = 2, +} +``` + +Every create, update, and delete request rechecks the player's current framework job and grade on +the server. Authorized editorial jobs share the newsroom and can manage drafts and published +articles. Cover images must come from the current phone's Gallery, changes use revision checks, and +deletion is retained as an audit-safe soft delete. Runtime migration creates +`sky_phone_weazel_articles` automatically; fresh installations receive the same schema through +`sky_phone/sql/install.sql`. + ## Radio app The built-in Radio app supports a primary frequency, volume, recent channels, participant lists, automatic rejoin, join/leave notifications, and an optional service number. YACA and SaltyChat support the configured secondary frequency; PMA Voice exposes one radio channel, so the secondary input is hidden automatically. @@ -127,13 +248,13 @@ Configure frequency bounds and precision, restricted channel ranges and allowed Radio profiles are stored in `sky_phone_radio_profiles`. Runtime migration creates the table automatically; fresh installations receive it through `sky_phone/sql/install.sql`. -Inventory metadata has no framework-wide standard: providers differ in export names, callback payloads, slot handling, and whether metadata is called `metadata` or `info`. For that reason, `sky_phone` uses explicit provider adapters instead of guessing exports at runtime. Every supported adapter implements slot lookup, item lookup, metadata replacement, capacity handling, add/remove operations, and usable-item registration. Providers without a separate capacity export use their authoritative add operation as the final capacity gate. +Inventory metadata has no framework-wide standard: providers differ in export names, callback payloads, slot handling, and whether metadata is called `metadata` or `info`. For that reason, `sky_phone` uses explicit provider adapters instead of guessing exports at runtime. Every supported adapter implements slot lookup, item lookup, metadata replacement, capacity handling, add/remove operations, and usable-item registration. Providers without a separate capacity export use their authoritative add operation as the final capacity gate. Phone item metadata is authoritative only when `Config.Phone.Unique = true`; in non-unique mode the persistent character mapping is authoritative instead. -When a SIM is ejected or replaced, the returned inventory item is rebuilt from the authoritative `sky_phone_sims` row. Its metadata contains `sim_metadata_version`, `sim_id`, `phone_number`, `formatted_number`, and `sim_type`. Registered SIMs additionally contain `firstname`, `lastname`, `birthdate`, and `registered_at`. The internal framework owner identifier remains database-only. Inserting the item again resolves the SIM by `sim_id`; contacts and device/cloud data remain attached to their existing phone-owned persistence instead of being copied into inventory metadata. +When physical SIMs are enabled and a SIM is ejected or replaced, the returned inventory item is rebuilt from the authoritative `sky_phone_sims` row. Its metadata contains `sim_metadata_version`, `sim_id`, `phone_number`, `formatted_number`, and `sim_type`. Registered SIMs additionally contain `firstname`, `lastname`, `birthdate`, and `registered_at`. The internal framework owner identifier remains database-only. Inserting the item again resolves the SIM by `sim_id`; contacts and device/Sky Cloud data remain attached to their existing Device or Sky Cloud persistence instead of being copied into inventory metadata. Automatically created virtual SIMs remain database-only and never become inventory items. -For `ox_inventory`, configure all three items with `stack = false` and `consume = 0`. Do not configure a client event or export. Ox then completes its normal server-authoritative use flow and emits `ox_inventory:usedItem`; the bridge resolves the authoritative slot again and only opens the matching device or SIM. A client export would return before Ox calls `useItem` and therefore prevent `ox_inventory:usedItem` from being emitted. +For `ox_inventory`, configure the phone with `stack = false` when `Config.Phone.Unique = true`; it may use `stack = true` in non-unique mode. Physical SIM items always use `stack = false` and are only needed when `Config.Sim.Enabled = true`. Every usable item should use `consume = 0`. Do not configure a client event or export. Ox then completes its normal server-authoritative use flow and emits `ox_inventory:usedItem`; the bridge resolves the authoritative slot again and only opens the matching Device or SIM. A client export would return before Ox calls `useItem` and therefore prevent `ox_inventory:usedItem` from being emitted. -Example `ox_inventory/data/items.lua` entries: +Example `ox_inventory/data/items.lua` entries for the default unique-phone, physical-SIM modes: ```lua ["phone"] = { @@ -159,19 +280,53 @@ Example `ox_inventory/data/items.lua` entries: }, ``` -For QBCore-style item tables, define the same names with `unique = true`, `useable = true`, and `shouldClose = true`. The provider adapter registers the server-side usable callbacks; no `lb-phone` event or export is used. +For QBCore-style item tables, set the phone's `unique` value to match `Config.Phone.Unique`, and use `useable = true` and `shouldClose = true`. When physical SIMs are enabled, define both SIM items with `unique = true`, `useable = true`, and `shouldClose = true`; omit them when SIMs are disabled. The provider adapter registers the server-side usable callbacks; no `lb-phone` event or export is used. The homescreen is an original implementation inspired by the interaction and layout concepts in [lukejacksonn/homescreen](https://github.com/lukejacksonn/homescreen), inspected at commit [`98a812f`](https://github.com/lukejacksonn/homescreen/tree/98a812f4f7c33594e791d65092f73b8f54b3c598). No source code or image assets from that project are included. ## Development -From `frontend/`, run `pnpm dev` for browser development. The phone opens automatically and NUI callbacks are mocked. Feather can be opened directly with the following browser scenarios: +From `frontend/`, run `pnpm dev` for browser development. The phone opens automatically and NUI callbacks are mocked with stateful data. Every built-in app can be opened directly by appending its id to `http://localhost:5174/?apiPort=3002#/apps/`: + +| Area | App ids | +| ------------------- | ------------------------------------------------------------------------------------------------------------- | +| Communication | `phone`, `messages`, `mail`, `darkchat`, `radio` | +| Social | `feather`, `fliptok`, `picstagram`, `flare`, `crewlink` | +| Services | `companies`, `citymarkt`, `local-pages`, `banking`, `billing`, `garage`, `house`, `map`, `skyride`, `weather` | +| Media and utilities | `camera`, `photos`, `music`, `calendar`, `notes`, `calculator`, `clock`, `app-store`, `settings` | +| Games | `snake`, `memory`, `number-merge`, `minesweeper`, `tower-stack`, `sky-flappy`, `neon-drop` | + +The browser bootstrap includes contacts, calls, messages, mail, invoices, transactions, vehicles, properties, companies, marketplace profiles and listings, social feeds, media, calendar entries, notes, alarms, game high scores, app settings, and persisted notifications. Mutating callbacks update the in-memory mock state until the mock server restarts. Unknown callbacks fail with `mock_endpoint_missing` instead of silently succeeding. + +System overlays are available through dedicated preview parameters: + +- SIM picker: `http://localhost:5174/?apiPort=3002&simPickerPreview=1` +- Payphone: `http://localhost:5174/?apiPort=3002&payphonePreview=1` (dial `5551110001` for a connected call or `5550000000` for a busy line) + +Feather can be opened directly with the following browser scenarios: - Full data: `http://localhost:5174/?apiPort=3002#/apps/feather` - Login and registration: `http://localhost:5174/?apiPort=3002&testScenario=feather-login#/apps/feather` - Profile onboarding: `http://localhost:5174/?apiPort=3002&testScenario=feather-onboarding#/apps/feather` - Empty states: `http://localhost:5174/?apiPort=3002&testScenario=feather-empty#/apps/feather` +EasyShare browser data is available from every app that exposes a share action. Open the seeded +Gallery directly, share an item, and then use the EasyShare and History actions in the sheet: + +- Full data, including incoming, transferring, accepted, completed, declined, cancelled, expired, + and failed transfers: `http://localhost:5174/?apiPort=3002&testScenario=easyshare-full#/apps/photos` +- Incoming request only: `http://localhost:5174/?apiPort=3002&testScenario=easyshare-incoming#/apps/photos` +- Transfer history without active requests: `http://localhost:5174/?apiPort=3002&testScenario=easyshare-history#/apps/photos` +- Complete content catalog with contact, document, link, location, note, photo, playlist, post, + profile, text, track, and video: `http://localhost:5174/?apiPort=3002&testScenario=easyshare-catalog#/apps/photos` +- Empty nearby and history states: `http://localhost:5174/?apiPort=3002&testScenario=easyshare-empty#/apps/photos` + +In the full scenario, sending to Mia or Jamie creates a pending transfer. Sending to Noah creates a +transfer at 58 percent so the progress and cancel states can be tested. Visibility changes and +accepting or declining the seeded incoming request are kept in memory until the mock server restarts. +The catalog also contains source examples from Companies, Mail, Garage, and House; completed history +rows and rich chat cards can be clicked to verify app/deep-link navigation. + The full-data scenario includes posts, replies, quotes, media grids, profiles, ranked hashtags, network search results, and every notification type. Run `pnpm test`, `pnpm typecheck`, `pnpm lint`, and `pnpm build` before packaging. `pnpm build` uses `build.cjs` to replace `sky_phone/source/html` deterministically with the Vite output. Production assets use relative paths so they work through the FiveM NUI protocol. diff --git a/frontend/package.json b/frontend/package.json index 7c86e8f..5bccab7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,10 +11,10 @@ "typecheck": "vue-tsc --build", "lint": "eslint .", "format": "prettier --write src/ testserver/ build.cjs", - "test": "vitest run" + "test": "vitest run && pnpm test:browser-mocks", + "test:browser-mocks": "node testserver/smoke.cjs" }, "dependencies": { - "emoji-picker-element-data": "^1.8.0", "@tiptap/core": "^3.29.2", "@tiptap/extension-placeholder": "^3.29.2", "@tiptap/markdown": "^3.29.2", @@ -22,6 +22,7 @@ "@tiptap/starter-kit": "^3.29.2", "@tiptap/vue-3": "^3.29.2", "dompurify": "^3.4.13", + "emoji-picker-element-data": "^1.8.0", "fix-webm-duration": "^1.0.6", "konsta": "~5.2.0", "lucide-vue-next": "^0.525.0", @@ -46,6 +47,7 @@ "tailwindcss": "^4.0.0", "typescript": "~5.8.0", "vite": "^7.0.0", + "vite-plugin-vue-devtools": "^8.2.1", "vitest": "^3.2.4", "vue-tsc": "^2.2.10" } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 1f4dd36..5588678 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: vite: specifier: ^7.0.0 version: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + vite-plugin-vue-devtools: + specifier: ^8.2.1 + version: 8.2.1(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.40(typescript@5.8.3)) vitest: specifier: ^3.2.4 version: 3.2.7(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) @@ -108,6 +111,72 @@ importers: packages: + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -116,11 +185,68 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.8': resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-proposal-decorators@7.29.7': + resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.29.7': + resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} @@ -369,6 +495,7 @@ packages: engines: {node: ^22.20 || ^24.12 || >=25} cpu: [x64] os: [linux] + libc: [glibc] '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -386,6 +513,9 @@ packages: resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} @@ -423,66 +553,79 @@ packages: resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.62.4': resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.62.4': resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.62.4': resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.62.4': resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.62.4': resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.62.4': resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.62.4': resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.62.4': resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.62.4': resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.62.4': resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.4': resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.62.4': resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.62.4': resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} @@ -552,24 +695,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.3': resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.3': resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.3': resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.3': resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} @@ -891,6 +1038,22 @@ packages: '@volar/typescript@2.4.15': resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@vue/compiler-core@3.5.40': resolution: {integrity: sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==} @@ -912,12 +1075,23 @@ packages: '@vue/devtools-api@7.7.10': resolution: {integrity: sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==} + '@vue/devtools-core@8.2.1': + resolution: {integrity: sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA==} + peerDependencies: + vue: ^3.0.0 + '@vue/devtools-kit@7.7.10': resolution: {integrity: sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==} + '@vue/devtools-kit@8.2.1': + resolution: {integrity: sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==} + '@vue/devtools-shared@7.7.10': resolution: {integrity: sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==} + '@vue/devtools-shared@8.2.1': + resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} + '@vue/eslint-config-prettier@10.2.0': resolution: {integrity: sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==} peerDependencies: @@ -998,6 +1172,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1012,9 +1190,17 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} + engines: {node: '>=6.0.0'} + hasBin: true + birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + birpc@4.1.0: + resolution: {integrity: sha512-O8L9vALWGqdEe0cG4HJckauw3WeJETlJnDRPUYpgwB7wrU43b/5NGMdVjdVcRo+4ROgd3ih2wha1glDe4HRVgw==} + body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} @@ -1036,6 +1222,15 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1056,6 +1251,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -1099,6 +1297,9 @@ packages: resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} engines: {node: '>=18'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -1146,6 +1347,18 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1164,6 +1377,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.405: + resolution: {integrity: sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==} + emoji-picker-element-data@1.8.0: resolution: {integrity: sha512-VfRuRJNEDLS1JKlNS4olaqhjX5S1nnZ+ZHG73b/dV8QeZyi0yPruTPEE72EmF6XO3k/9hj3lybMIYMOYXb/57A==} @@ -1182,6 +1398,9 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1382,6 +1601,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1463,6 +1686,11 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1475,6 +1703,15 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1486,6 +1723,10 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1493,6 +1734,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -1500,6 +1744,11 @@ packages: resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1509,9 +1758,17 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + konsta@5.2.0: resolution: {integrity: sha512-afWzSinsEfDU+OI7+ljShPAlZqecJlqDUSQVyvqzFy3PgoON+UQXD+f0k5ZXsQRfKmU5fyznKaHW0k8OK8nBNg==} engines: {node: '>= 4.7.0'} @@ -1555,24 +1812,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1603,6 +1864,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-vue-next@0.525.0: resolution: {integrity: sha512-Xf8+x8B2DrnGDV/rxylS+KBp2FIe6ljwDn2JsGTZZvXIfhmm/q+nv8RuGO1OyoMjOVkkz7CqtUqJfwtFPRbB2w==} deprecated: Package deprecated. Please use @lucide/vue instead. @@ -1659,6 +1923,10 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1677,6 +1945,10 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -1688,6 +1960,13 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1695,6 +1974,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1742,6 +2025,9 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1770,6 +2056,10 @@ packages: resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1872,6 +2162,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1881,6 +2175,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1928,6 +2226,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2017,6 +2319,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2057,6 +2363,16 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-utils@0.3.2: + resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} + engines: {node: '>=20.19.0'} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2067,11 +2383,42 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-dev-rpc@2.0.0: + resolution: {integrity: sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0 + + vite-hot-client@2.2.0: + resolution: {integrity: sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0 + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite-plugin-inspect@11.4.1: + resolution: {integrity: sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 || ^8.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@8.2.1: + resolution: {integrity: sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA==} + engines: {node: '>=v14.21.3'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + + vite-plugin-vue-inspector@6.0.0: + resolution: {integrity: sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vite@7.3.6: resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2192,6 +2539,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -2200,6 +2551,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -2214,14 +2568,192 @@ packages: snapshots: + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.29.7': {} '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@babel/parser@7.29.8': dependencies: '@babel/types': 7.29.8 + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -2414,6 +2946,8 @@ snapshots: '@pkgr/core@0.3.6': {} + '@polka/url@1.0.0-next.29': {} + '@rolldown/pluginutils@1.0.1': {} '@rollup/rollup-android-arm-eabi@4.62.4': @@ -2908,6 +3442,35 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.7) + '@vue/shared': 3.5.40 + optionalDependencies: + '@babel/core': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/parser': 7.29.8 + '@vue/compiler-sfc': 3.5.40 + transitivePeerDependencies: + - supports-color + '@vue/compiler-core@3.5.40': dependencies: '@babel/parser': 7.29.8 @@ -2949,6 +3512,12 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.10 + '@vue/devtools-core@8.2.1(vue@3.5.40(typescript@5.8.3))': + dependencies: + '@vue/devtools-kit': 8.2.1 + '@vue/devtools-shared': 8.2.1 + vue: 3.5.40(typescript@5.8.3) + '@vue/devtools-kit@7.7.10': dependencies: '@vue/devtools-shared': 7.7.10 @@ -2959,10 +3528,19 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.6 + '@vue/devtools-kit@8.2.1': + dependencies: + '@vue/devtools-shared': 8.2.1 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + '@vue/devtools-shared@7.7.10': dependencies: rfdc: 1.4.1 + '@vue/devtools-shared@8.2.1': {} + '@vue/eslint-config-prettier@10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.5.3)': dependencies: eslint: 9.39.5(jiti@2.7.0) @@ -3053,6 +3631,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansis@4.3.1: {} + argparse@2.0.1: {} assertion-error@2.0.1: {} @@ -3061,8 +3641,12 @@ snapshots: balanced-match@4.0.4: {} + baseline-browser-mapping@2.11.13: {} + birpc@2.9.0: {} + birpc@4.1.0: {} + body-parser@2.3.0: dependencies: bytes: 3.1.2 @@ -3096,6 +3680,18 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.405 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bytes@3.1.2: {} cac@6.7.14: {} @@ -3112,6 +3708,8 @@ snapshots: callsites@3.1.0: {} + caniuse-lite@1.0.30001809: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -3156,6 +3754,8 @@ snapshots: content-type@2.0.0: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -3189,6 +3789,15 @@ snapshots: deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + depd@2.0.0: {} detect-libc@2.1.2: {} @@ -3205,6 +3814,8 @@ snapshots: ee-first@1.1.1: {} + electron-to-chromium@1.5.405: {} + emoji-picker-element-data@1.8.0: {} emoji-regex@8.0.0: {} @@ -3218,6 +3829,8 @@ snapshots: entities@7.0.1: {} + error-stack-parser-es@1.0.5: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3479,6 +4092,8 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -3552,6 +4167,8 @@ snapshots: ipaddr.js@1.9.1: {} + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -3560,32 +4177,50 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-number@7.0.0: {} is-promise@4.0.0: {} is-what@5.5.0: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} jiti@2.7.0: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} js-yaml@4.3.1: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + kolorist@1.8.0: {} + konsta@5.2.0: dependencies: tailwind-merge: 3.6.0 @@ -3654,6 +4289,10 @@ snapshots: loupe@3.2.1: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-vue-next@0.525.0(vue@3.5.40(typescript@5.8.3)): dependencies: vue: 3.5.40(typescript@5.8.3) @@ -3697,6 +4336,8 @@ snapshots: mitt@3.0.1: {} + mrmime@2.0.1: {} + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -3707,6 +4348,8 @@ snapshots: negotiator@1.0.0: {} + node-releases@2.0.53: {} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -3715,6 +4358,10 @@ snapshots: object-inspect@1.13.4: {} + obug@2.1.4: {} + + ohash@2.0.11: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -3723,6 +4370,15 @@ snapshots: dependencies: wrappy: 1.0.2 + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3762,6 +4418,8 @@ snapshots: perfect-debounce@1.0.0: {} + perfect-debounce@2.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -3786,6 +4444,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + powershell-utils@0.1.0: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: @@ -3943,6 +4603,8 @@ snapshots: transitivePeerDependencies: - supports-color + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3953,6 +4615,8 @@ snapshots: safer-buffer@2.1.2: {} + semver@6.3.1: {} + semver@7.8.5: {} send@1.2.1: @@ -4020,6 +4684,12 @@ snapshots: siginfo@2.0.0: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} speakingurl@14.0.1: {} @@ -4089,6 +4759,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + tree-kill@1.2.2: {} ts-api-utils@2.5.0(typescript@5.8.3): @@ -4124,6 +4796,17 @@ snapshots: unpipe@1.0.0: {} + unplugin-utils@0.3.2: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.5 + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -4132,6 +4815,16 @@ snapshots: vary@1.1.2: {} + vite-dev-rpc@2.0.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)): + dependencies: + birpc: 4.1.0 + vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + vite-hot-client: 2.2.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)) + + vite-hot-client@2.2.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)): + dependencies: + vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + vite-node@3.2.4(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: cac: 6.7.14 @@ -4153,6 +4846,48 @@ snapshots: - tsx - yaml + vite-plugin-inspect@11.4.1(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)): + dependencies: + ansis: 4.3.1 + error-stack-parser-es: 1.0.5 + obug: 2.1.4 + ohash: 2.0.11 + open: 11.0.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.2 + vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + vite-dev-rpc: 2.0.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)) + + vite-plugin-vue-devtools@8.2.1(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.40(typescript@5.8.3)): + dependencies: + '@vue/devtools-core': 8.2.1(vue@3.5.40(typescript@5.8.3)) + '@vue/devtools-kit': 8.2.1 + '@vue/devtools-shared': 8.2.1 + sirv: 3.0.2 + vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + vite-plugin-inspect: 11.4.1(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)) + vite-plugin-vue-inspector: 6.0.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + + vite-plugin-vue-inspector@6.0.0(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.7) + '@vue/compiler-dom': 3.5.40 + kolorist: 1.8.0 + magic-string: 0.30.21 + vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: esbuild: 0.28.1 @@ -4264,10 +4999,17 @@ snapshots: wrappy@1.0.2: {} + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xml-name-validator@4.0.0: {} y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/frontend/public/img/custom-app.svg b/frontend/public/img/custom-app.svg new file mode 100644 index 0000000..8b9ec86 --- /dev/null +++ b/frontend/public/img/custom-app.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 8dab943..b4b4be7 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -18,6 +18,7 @@ import PhonePasscode from '@/components/PhonePasscode.vue' import PhoneNotifications from '@/components/PhoneNotifications.vue' import NotificationPhonePreview from '@/components/NotificationPhonePreview.vue' import PhoneStatusBar from '@/components/PhoneStatusBar.vue' +import EasyShareSheet from '@/components/EasyShareSheet.vue' import PayphoneOverlay from '@/components/PayphoneOverlay.vue' import RadioHud from '@/components/RadioHud.vue' import SimPhonePicker, { @@ -29,7 +30,9 @@ import { useGamesStore } from '@/features/games/store' import { useCallsStore } from '@/stores/calls' import { useBankingStore } from '@/stores/banking' import { useBillingStore } from '@/stores/billing' +import { useCompaniesStore } from '@/stores/companies' import { useAccountStore } from '@/stores/account' +import { useAppAuthStore } from '@/stores/app-auth' import { useMailStore } from '@/stores/mail' import { useMessagesStore } from '@/stores/messages' import { useDarkChatStore } from '@/stores/darkchat' @@ -39,23 +42,32 @@ import { usePicstagramStore } from '@/stores/picstagram' import { useFeatherStore } from '@/stores/feather' import { useMediaStore } from '@/stores/media' import { useMarketplaceStore } from '@/stores/marketplace' +import { useAppCatalogStore } from '@/stores/app-catalog' import { useAppStoreStore } from '@/stores/app-store' import { useWidgetsStore } from '@/stores/widgets' import { isPhoneAppId } from '@/config/apps' import { useNotesStore } from '@/stores/notes' import { useWeatherStore } from '@/stores/weather' +import { useEasyShareStore } from '@/stores/easyshare' import { useNotificationsStore, + type PhoneNotification, type PhoneNotificationInput, } from '@/stores/notifications' import { usePhoneStore, type PhoneOpenPayload } from '@/stores/phone' import type { PhoneNotificationDevicePayload } from '@/types/device' import type { MailCounts } from '@/types/mail' import type { MarketplaceCounts } from '@/types/marketplace' +import type { + CompanyChangedPayload, + CompanyUnreadCounts, +} from '@/types/companies' import type { PhoneCall } from '@/types/phone' +import type { EasyShareEvent } from '@/types/easyshare' import { nuiCall } from '@/utils/nui' import { formatTimer } from '@/utils/clock' import { parsePhonePreferences } from '@/utils/preferences' +import { isTrustedRootMessageSource } from '@/utils/windowMessages' import SpringboardView from '@/views/SpringboardView.vue' type AppMessage = { @@ -64,6 +76,7 @@ type AppMessage = { | CalendarReminderData | MailEventData | MarketplaceEventData + | CompaniesEventData | MessagesEventData | DarkChatEventData | FlareEventData @@ -73,9 +86,22 @@ type AppMessage = { | PicstagramNotificationData | FeatherNotificationData | BillingNotificationData + | EasyShareEvent | PhoneCall | PhoneNotificationInput | PhoneOpenPayload + | CustomAppCatalogEventData + | CustomAppEventData +} + +type CustomAppCatalogEventData = { + apps?: unknown +} + +type CustomAppEventData = { + appId?: unknown + data?: unknown + payload?: unknown } type SimPickerPayload = { @@ -104,6 +130,18 @@ type MessagesEventData = { title?: string } +type CompaniesEventData = { + area?: CompanyChangedPayload['area'] + companyId?: string + counts?: CompanyUnreadCounts + device?: PhoneNotificationDevicePayload + kind?: 'assigned' | 'newMessage' | 'newRequest' | 'requestUpdated' + requestId?: string + subtitle?: string + text?: string + title?: string +} + type DarkChatEventData = { conversationId?: string device?: PhoneNotificationDevicePayload @@ -200,15 +238,20 @@ const REFERENCE_VIEWPORT_WIDTH = 1920 const REFERENCE_VIEWPORT_HEIGHT = 1080 const PHONE_BASE_SCALE = 0.69 const DEVELOPMENT_PHONE_SCALE = 1.25 +const PHONE_PORTRAIT_WIDTH = 390 +const PHONE_PORTRAIT_HEIGHT = 844 +const MIN_PRODUCTION_PHONE_ZOOM = 260 / PHONE_PORTRAIT_WIDTH const isDevelopment = import.meta.env.DEV const phone = usePhoneStore() const account = useAccountStore() +const appAuth = useAppAuthStore() const clock = useClockStore() const games = useGamesStore() const calls = useCallsStore() const banking = useBankingStore() const billing = useBillingStore() +const companies = useCompaniesStore() const mail = useMailStore() const messages = useMessagesStore() const darkchat = useDarkChatStore() @@ -218,14 +261,19 @@ const picstagram = usePicstagramStore() const feather = useFeatherStore() const media = useMediaStore() const marketplace = useMarketplaceStore() +const appCatalog = useAppCatalogStore() const appStore = useAppStoreStore() const widgets = useWidgetsStore() const notes = useNotesStore() const weather = useWeatherStore() +const easyShare = useEasyShareStore() const notifications = useNotificationsStore() const route = useRoute() const router = useRouter() const isAppRoute = computed(() => route.name === 'app') +const showActiveCallReturn = computed( + () => Boolean(calls.activeCall) && route.params.appId !== 'phone', +) const appTransitionName = computed(() => route.query.transition === 'app-switch' ? 'app-switch' : 'app-window', ) @@ -248,11 +296,34 @@ const phoneBaseZoom = computed( viewportScale.value * (isDevelopment ? DEVELOPMENT_PHONE_SCALE : PHONE_BASE_SCALE), ) +const phoneZoom = computed(() => { + const preferred = + phoneBaseZoom.value * (phone.preferences.settings.phoneScale / 100) + if (isDevelopment) return preferred + + const edgeGap = 24 * viewportScale.value + const shellWidth = phone.cameraLandscape + ? PHONE_PORTRAIT_HEIGHT + : PHONE_PORTRAIT_WIDTH + const shellHeight = phone.cameraLandscape + ? PHONE_PORTRAIT_WIDTH + : PHONE_PORTRAIT_HEIGHT + const viewportMaximum = Math.max( + 0, + Math.min( + (window.innerWidth - edgeGap) / shellWidth, + (window.innerHeight - edgeGap) / shellHeight, + ), + ) + return Math.min( + viewportMaximum, + Math.max(MIN_PRODUCTION_PHONE_ZOOM, preferred), + ) +}) const phoneResolutionStyle = computed(() => ({ '--phone-edge-gap': `${24 * viewportScale.value}px`, '--phone-stack-gap': `${16 * viewportScale.value}px`, - '--phone-zoom': - phoneBaseZoom.value * (phone.preferences.settings.phoneScale / 100), + '--phone-zoom': phoneZoom.value, })) const phoneStageStyle = computed(() => ({ ...phoneResolutionStyle.value, @@ -267,9 +338,13 @@ const phoneFrameImage = computed( () => PHONE_FRAME_IMAGES[phone.preferences.settings.frame], ) let clockTicker: ReturnType | undefined +let companiesChangeTimer: number | undefined +let pendingCompaniesChange: CompanyChangedPayload | null = null let unlockTimer: number | undefined let passcodeLockTimer: number | undefined let unlockedServicesIdle: number | undefined +let phoneClosePending = false +let simPickerClosePending = false function getViewportScale(): number { const heightScale = window.innerHeight / REFERENCE_VIEWPORT_HEIGHT @@ -279,7 +354,16 @@ function getViewportScale(): number { } function hydratePhone(payload: PhoneOpenPayload): void { + if (payload.device?.imei) { + companies.bindDeviceScope( + payload.device.imei, + payload.device.sim?.id ?? null, + ) + } phone.open(payload) + phone.ensureAppNotificationPreferences( + appCatalog.externalApps.map((app) => app.id), + ) if (payload.device?.imei) { notifications.hydrate( payload.device.data.notifications?.payload, @@ -288,6 +372,10 @@ function hydratePhone(payload: PhoneOpenPayload): void { notifications.hideDevicePreview(payload.device.imei) } account.hydrate(payload.account ?? null) + appAuth.hydrate( + payload.device?.data.appAuth?.payload, + payload.account?.email ?? '', + ) notes.hydrate(payload.notes ?? []) clock.hydrate(payload.device?.data.alarms?.payload) games.hydrate(payload.device?.data.games?.payload) @@ -306,6 +394,31 @@ function cancelUnlockedPhoneDataLoad(): void { unlockedServicesIdle = undefined } +function queueCompaniesChange(change: CompanyChangedPayload): void { + if (!pendingCompaniesChange) { + pendingCompaniesChange = { ...change } + } else { + pendingCompaniesChange = { + area: pendingCompaniesChange.area === change.area ? change.area : 'all', + companyId: + pendingCompaniesChange.companyId === change.companyId + ? change.companyId + : undefined, + requestId: + pendingCompaniesChange.requestId === change.requestId + ? change.requestId + : undefined, + } + } + if (companiesChangeTimer !== undefined) return + companiesChangeTimer = window.setTimeout(() => { + companiesChangeTimer = undefined + const queuedChange = pendingCompaniesChange + pendingCompaniesChange = null + if (queuedChange) void companies.applyChanged(queuedChange) + }, 2000) +} + async function bootstrapUnlockedPhoneData(): Promise { const tasks: Array<() => Promise | void> = [ () => calls.bootstrap(), @@ -316,6 +429,7 @@ async function bootstrapUnlockedPhoneData(): Promise { if (account.email) return marketplace.loadCounts() marketplace.setCounts({ active: 0, unread: 0 }) }, + () => companies.refreshUnreadCounts(), () => (account.email ? darkchat.bootstrap() : undefined), ] @@ -370,6 +484,7 @@ async function hydrateDevelopmentPhone(): Promise { sim: { id: 'development-sim', number: '5551234567', + removable: true, registered: true, type: 'registered', }, @@ -379,15 +494,87 @@ async function hydrateDevelopmentPhone(): Promise { }) } +function openDevelopmentPayphonePreview(): void { + window.dispatchEvent( + new MessageEvent('message', { + data: { + data: { + currency: '$', + locales: { + busy: 'LINE BUSY', + call: 'CALL', + callEnded: 'CALL ENDED', + clear: 'Clear number', + close: 'Close payphone', + connected: 'CONNECTED', + cost: 'COST', + declined: 'CALL DECLINED', + delete: 'Delete digit', + dialing: 'DIALING', + disconnected: 'DISCONNECTED', + elapsed: 'TIME', + hangup: 'HANG UP', + insufficientFunds: 'OUT OF MONEY', + invalidNumber: 'ENTER A VALID NUMBER', + keypad: 'Dial pad', + noAnswer: 'NO ANSWER', + numberLabel: 'NUMBER TO CALL', + numberPlaceholder: 'Enter a phone number', + rate: '{currency}{price} / SEC', + ready: 'READY', + requestFailed: 'CALL COULD NOT BE STARTED', + ringing: 'RINGING', + subtitle: 'PUBLIC TELEPHONE', + title: 'PAYPHONE', + unavailable: 'NUMBER UNAVAILABLE', + voiceUnavailable: 'VOICE SERVICE UNAVAILABLE', + }, + maxNumberLength: 10, + pricePerSecond: 2, + }, + type: 'payphone:open', + }, + }), + ) +} + function onMessage(event: MessageEvent): void { - if (event.data?.type === 'app:open') { + if (!isTrustedRootMessageSource(event.source, window)) return + + if (event.data?.type === 'custom-apps:catalog') { + appCatalog.replaceCatalog(event.data.data) + const activeAppId = route.params.appId + if (typeof activeAppId === 'string' && !isPhoneAppId(activeAppId)) { + void router.push('/') + } + } else if (event.data?.type === 'custom-app:message') { + const data = event.data.data as CustomAppEventData | undefined + if (typeof data?.appId === 'string') { + appCatalog.queueHostMessage(data.appId, data.payload) + } else { + console.error('[Custom apps] Ignored a message without a valid app id.') + } + } else if (event.data?.type === 'custom-app:open') { + const data = event.data.data as CustomAppEventData | undefined + if ( + typeof data?.appId === 'string' && + appCatalog.requestOpen(data.appId, data.data) + ) { + void router.push(`/apps/${data.appId}`) + } + } else if (event.data?.type === 'custom-app:close') { + const data = event.data.data as CustomAppEventData | undefined + if (typeof data?.appId === 'string' && route.params.appId === data.appId) { + void router.push('/') + } + } else if (event.data?.type === 'app:open') { hydratePhone(event.data.data as PhoneOpenPayload) void nuiCall('ui:opened') } else if (event.data?.type === 'device:updated') { hydratePhone(event.data.data as PhoneOpenPayload) } else if (event.data?.type === 'app:close') { activitySuspended.value = false - phone.close() + phone.endDeviceSession() } else if (event.data?.type === 'app:suspend') { activitySuspended.value = true } else if (event.data?.type === 'app:resume') { @@ -396,10 +583,7 @@ function onMessage(event: MessageEvent): void { const data = event.data.data as NotificationEventData const { device, ...input } = data const notification: PhoneNotificationInput = input - if ( - device && - (!phone.isOpen || device.imei !== phone.device?.imei) - ) { + if (device && (!phone.isOpen || device.imei !== phone.device?.imei)) { notification.device = { imei: device.imei, name: device.name, @@ -441,6 +625,47 @@ function onMessage(event: MessageEvent): void { } else if (event.data?.type === 'marketplace:changed' && event.data.data) { const data = event.data.data as MarketplaceEventData if (data.counts) marketplace.setCounts(data.counts) + } else if (event.data?.type === 'companies:changed') { + const data = event.data.data as CompaniesEventData | undefined + if (data?.counts) companies.applyUnreadCounts(data.counts) + if (data?.area) { + queueCompaniesChange({ + area: data.area, + companyId: data.companyId, + requestId: data.requestId, + }) + } else if (!data?.counts) queueCompaniesChange({ area: 'all' }) + } else if (event.data?.type === 'companies:notification' && event.data.data) { + const data = event.data.data as CompaniesEventData + if (data.counts) companies.applyUnreadCounts(data.counts) + + const notification: PhoneNotificationInput = { + appId: 'companies', + ...(data.requestId && data.area + ? { + route: `/apps/companies?requestId=${encodeURIComponent(data.requestId)}&area=${encodeURIComponent(data.area)}`, + } + : {}), + persistent: !phone.isOpen && Boolean(data.requestId && data.area), + subtitle: data.subtitle, + text: + data.text ?? + phone.t( + `Apps.companies.notifications.${data.kind ?? 'requestUpdated'}`, + ), + title: data.title ?? phone.t('Apps.companies.name'), + } + if ( + data.device && + (!phone.isOpen || data.device.imei !== phone.device?.imei) + ) { + notification.device = { + imei: data.device.imei, + name: data.device.name, + preferences: parsePhonePreferences(data.device.settings ?? null), + } + } + notifications.show(notification) } else if ( event.data?.type === 'fliptok:verification-changed' && event.data.data @@ -568,6 +793,8 @@ function onMessage(event: MessageEvent): void { notifications.show(notification) } else if (event.data?.type === 'contacts:changed') { void calls.loadContacts() + } else if (event.data?.type === 'easyshare:changed' && event.data.data) { + easyShare.applyEvent(event.data.data as EasyShareEvent) } else if (event.data?.type === 'messages:changed') { void messages.loadConversations() if (messages.activeNumber) void messages.openThread(messages.activeNumber) @@ -702,10 +929,7 @@ function onMessage(event: MessageEvent): void { } } notifications.show(notification) - } else if ( - event.data?.type === 'crewlink:notification' && - event.data.data - ) { + } else if (event.data?.type === 'crewlink:notification' && event.data.data) { const data = event.data.data as CrewLinkNotificationData const notification: PhoneNotificationInput = { appId: 'crewlink', @@ -744,14 +968,69 @@ function onMessage(event: MessageEvent): void { } } +async function closeSimPicker(): Promise { + if (simPickerClosePending || !simPicker.value) return + simPickerClosePending = true + const closingPicker = simPicker.value + try { + const response = await nuiCall('sim:picker-close') + if (response.success && simPicker.value === closingPicker) { + simPicker.value = null + } + } finally { + simPickerClosePending = false + } +} + +async function closePhone(): Promise { + if (phoneClosePending || !phone.isOpen) return + phoneClosePending = true + const closingGeneration = phone.persistenceGeneration + const closingImei = phone.device?.imei ?? null + const closingToken = phone.deviceSessionToken + try { + await phone.flushDevicePersistence() + if ( + !phone.isOpen || + phone.persistenceGeneration !== closingGeneration || + (phone.device?.imei ?? null) !== closingImei || + phone.deviceSessionToken !== closingToken + ) { + return + } + const response = await nuiCall('close') + if ( + !response.success || + !phone.isOpen || + phone.persistenceGeneration !== closingGeneration || + (phone.device?.imei ?? null) !== closingImei || + phone.deviceSessionToken !== closingToken + ) { + return + } + phone.endDeviceSession() + } finally { + phoneClosePending = false + } +} + function onKeydown(event: KeyboardEvent): void { - if (event.key !== 'Escape' || !phone.isOpen || activitySuspended.value) return - if (controlCenterOpened.value) { - controlCenterOpened.value = false + if (event.key !== 'Escape') return + if (simPicker.value) { + event.preventDefault() + void closeSimPicker() return } - phone.close() - void nuiCall('close') + + queueMicrotask(() => { + if (event.defaultPrevented || !phone.isOpen || activitySuspended.value) + return + if (controlCenterOpened.value) { + controlCenterOpened.value = false + return + } + void closePhone() + }) } function onSystemColorSchemeChange(event: MediaQueryListEvent): void { @@ -801,6 +1080,28 @@ function unlockPhone(): void { finishUnlock() } +function openLockScreenNotification(notification: PhoneNotification): void { + if (!notification.route) return + pendingUnlockRoute.value = notification.route + notifications.dismissFromLockScreen(notification.id) + unlockPhone() +} + +async function openNotificationPreview( + notification: PhoneNotification, +): Promise { + if (!notification.route) return + const imei = notification.device?.imei ?? phone.device?.imei + if (!imei) return + pendingUnlockRoute.value = notification.route + const response = await nuiCall('device:notification-open', { imei }) + if (!response.success) { + pendingUnlockRoute.value = null + return + } + notifications.dismissFromLockScreen(notification.id, imei) +} + function cancelPasscode(): void { if (passcodeBusy.value) return passcodeVisible.value = false @@ -862,6 +1163,12 @@ function lockPhone(): void { isLocked.value = true } +function returnToActiveCall(): void { + if (!calls.activeCall) return + controlCenterOpened.value = false + void router.push('/apps/phone') +} + function unlockCamera(): void { if (phone.security.enabled) { pendingUnlockRoute.value = '/apps/camera' @@ -878,7 +1185,7 @@ onMounted(() => { window.addEventListener('resize', updateViewportScale) systemColorScheme.addEventListener('change', onSystemColorSchemeChange) phone.setSystemDarkMode(systemColorScheme.matches) - void nuiCall('ui:ready') + void nuiCall('ui:ready', { protocolVersion: 1 }) clockTicker = setInterval(() => { const now = Date.now() for (const alarm of clock.dueAlarms(now)) { @@ -907,8 +1214,9 @@ onMounted(() => { } }, 1000) if (isDevelopment) { + const developmentParameters = new URLSearchParams(window.location.search) void hydrateDevelopmentPhone() - if (new URLSearchParams(window.location.search).has('simPickerPreview')) { + if (developmentParameters.has('simPickerPreview')) { simPicker.value = { choices: [ { @@ -926,24 +1234,31 @@ onMounted(() => { number: '5551234567', } } + if (developmentParameters.has('payphonePreview')) { + openDevelopmentPayphonePreview() + } } }) watch( () => route.params.appId, (appId) => { - if (typeof appId === 'string' && isPhoneAppId(appId)) { + if ( + phone.isOpen && + phone.device?.imei && + appStore.hydrated && + typeof appId === 'string' && + isPhoneAppId(appId) + ) { appStore.recordLaunch(appId) } }, ) watch( - [() => notifications.requiresAttention, () => calls.activeCall], - ([requiresAttention, activeCall]) => { - void nuiCall('notification:focus', { - active: requiresAttention || activeCall !== null, - }) + () => notifications.requiresAttention, + (requiresAttention) => { + void nuiCall('notification:focus', { active: requiresAttention }) }, ) @@ -953,6 +1268,7 @@ watch( if (unlockTimer !== undefined) window.clearTimeout(unlockTimer) if (!isOpen) { cancelUnlockedPhoneDataLoad() + appStore.cancelPendingInstalls() activitySuspended.value = false weather.stop() controlCenterOpened.value = false @@ -1001,6 +1317,9 @@ onBeforeUnmount(() => { cancelUnlockedPhoneDataLoad() weather.stop() if (clockTicker) clearInterval(clockTicker) + if (companiesChangeTimer !== undefined) { + window.clearTimeout(companiesChangeTimer) + } if (unlockTimer !== undefined) window.clearTimeout(unlockTimer) if (passcodeLockTimer !== undefined) window.clearInterval(passcodeLockTimer) window.removeEventListener('message', onMessage) @@ -1018,7 +1337,7 @@ onBeforeUnmount(() => { v-if="simPicker" :choices="simPicker.choices" :number="simPicker.number" - @close="simPicker = null" + @close="closeSimPicker" />
{ 100) " @close="notifications.dismiss(notification.id)" + @open="openNotificationPreview" />
{ :style="phoneDisplayStyle" :class="{ dark: phone.isDarkMode, + 'phone-app--darkchat': route.params.appId === 'darkchat', 'phone-app--light': !phone.isDarkMode, + 'phone-app--messages': route.params.appId === 'messages', [`phone-app--${phone.preferences.settings.graphicsMode}`]: true, 'phone-app--unlocking': isUnlocking, }" > @@ -1110,6 +1434,7 @@ onBeforeUnmount(() => { @camera="unlockCamera" @clear-notifications="notifications.clearLockScreen" @dismiss-notification="notifications.dismissFromLockScreen" + @open-notification="openLockScreenNotification" @unlock="unlockPhone" /> @@ -1136,7 +1461,9 @@ onBeforeUnmount(() => { +
diff --git a/frontend/src/assets/img/app-icons/companies.svg b/frontend/src/assets/img/app-icons/companies.svg new file mode 100644 index 0000000..b52f7ce --- /dev/null +++ b/frontend/src/assets/img/app-icons/companies.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/img/app-icons/radio.svg b/frontend/src/assets/img/app-icons/radio.svg deleted file mode 100644 index 001fab2..0000000 --- a/frontend/src/assets/img/app-icons/radio.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/frontend/src/assets/img/app-icons/radio.webp b/frontend/src/assets/img/app-icons/radio.webp new file mode 100644 index 0000000..bbce3fa Binary files /dev/null and b/frontend/src/assets/img/app-icons/radio.webp differ diff --git a/frontend/src/assets/img/app-icons/weazel-news.svg b/frontend/src/assets/img/app-icons/weazel-news.svg new file mode 100644 index 0000000..c98d991 --- /dev/null +++ b/frontend/src/assets/img/app-icons/weazel-news.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 8239d4b..6e8f816 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -14,9 +14,13 @@ .sim-picker { position: relative; width: min(58vh, 90vw); - max-height: 46vh; - overflow: clip; + max-width: calc(100vw - 32px); + max-height: calc(100vh - 32px); + min-height: 0; + overflow: hidden; padding: 2.2vh; + display: flex; + flex-direction: column; border: 0.1vh solid rgb(255 255 255 / 12%); border-radius: 0.9vh; background: #050505; @@ -56,11 +60,16 @@ .sim-picker__header { display: flex; + flex: 0 0 auto; justify-content: space-between; gap: 2vh; margin-bottom: 1.6vh; } +.sim-picker__header > div { + min-width: 0; +} + .sim-picker__header h1, .sim-picker__confirmation h2 { margin: 0; @@ -73,6 +82,7 @@ margin: 0.45vh 0 0; color: #9ca3af; font-size: 1.15vh; + overflow-wrap: anywhere; } .sim-picker__close { @@ -80,6 +90,8 @@ place-items: center; width: 2.8vh; height: 2.8vh; + min-width: 32px; + min-height: 32px; border: 0; border-radius: 0.35vh; background: #1dd1ce; @@ -96,10 +108,12 @@ position: relative; z-index: 1; display: grid; + flex: 1 1 auto; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1vh; - max-height: 34vh; - overflow: auto; + min-height: 0; + max-height: 54vh; + overflow-y: auto; } .sim-picker__card { @@ -140,30 +154,34 @@ } .sim-picker__details strong { - overflow: hidden; font-size: 1.2vh; - text-overflow: ellipsis; - white-space: nowrap; + overflow-wrap: anywhere; } .sim-picker__details small { color: #9ca3af; font-size: 0.95vh; + overflow-wrap: anywhere; } .sim-picker__confirmation { + min-height: 0; + overflow-y: auto; padding: 2vh 0 0; text-align: center; } .sim-picker__confirmation > div { display: flex; + flex-wrap: wrap; justify-content: center; gap: 1vh; margin-top: 2vh; } .sim-picker__confirmation button { + min-height: 40px; + overflow-wrap: anywhere; padding: 0.8vh 1.6vh; border: 0.1vh solid rgb(255 255 255 / 18%); border-radius: 0.45vh; @@ -184,6 +202,13 @@ color: #ff453a; font-size: 1vh; text-align: center; + overflow-wrap: anywhere; +} + +@media (max-width: 640px) { + .sim-picker__cards { + grid-template-columns: 1fr; + } } :root { font-family: @@ -205,6 +230,7 @@ body, margin: 0; overflow: hidden; background: transparent !important; + pointer-events: none; user-select: none; } button, @@ -467,6 +493,47 @@ button { cursor: pointer; pointer-events: auto; } +.phone-status-bar__time-button--active-call { + isolation: isolate; + text-shadow: none; +} +.phone-status-bar__time-button--active-call::before { + position: absolute; + z-index: -1; + top: 19px; + left: calc(50% - 3px); + width: 90px; + height: 30px; + border-radius: 15px; + background: #34c759; + box-shadow: 0 2px 8px #0b6f2b66; + animation: phone-active-call-pill-in 220ms cubic-bezier(0.22, 1, 0.36, 1); + content: ''; + transform-origin: center; + transform: translateX(-50%); + transition: transform 140ms ease; +} +.phone-status-bar__time-button--active-call:active::before { + transform: translateX(-50%) scale(0.94); +} +.phone-status-bar__active-call-icon { + position: absolute; + top: 27px; + left: calc(50% - 39px); + width: 13px; + height: 13px; + stroke-width: 2.5; +} +@keyframes phone-active-call-pill-in { + from { + opacity: 0; + transform: translateX(-50%) scale(0.82); + } + to { + opacity: 1; + transform: translateX(-50%) scale(1); + } +} .phone-status-bar__indicators { position: absolute; top: 0; @@ -667,17 +734,75 @@ button { backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); } +.lock-screen__notification-row { + position: relative; + overflow: hidden; + min-height: 76px; + border-radius: 18px; + background: transparent; + isolation: isolate; +} +.lock-screen__notification-clear { + position: absolute; + z-index: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + width: 76px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + border: 0; + padding: 0; + color: #fff; + background: #ff3b30; + font-size: 10px; + font-weight: 650; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s ease-out; +} +.lock-screen__notification-row.is-action-visible { + background: #ff3b30; +} +.lock-screen__notification-row.is-action-visible + .lock-screen__notification-clear { + opacity: 1; +} .lock-screen__notification { position: relative; + z-index: 1; display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 10px; - min-height: 72px; + width: 100%; + min-height: 76px; border-radius: 18px; - padding: 11px 38px 11px 11px; + padding: 11px; color: #fff; - background-color: rgb(28 28 32 / 72%); + background-color: rgb(38 36 40 / 76%); text-shadow: 0 1px 3px #0009; + touch-action: pan-y; + transform: translate3d(var(--notification-swipe), 0, 0); + transition: transform 0.28s cubic-bezier(0.22, 0.78, 0.24, 1); + will-change: transform; + backdrop-filter: blur(22px) saturate(1.18); + -webkit-backdrop-filter: blur(22px) saturate(1.18); +} +.lock-screen__notification-row.is-swiping .lock-screen__notification { + transition: none; +} +.phone-notification.is-actionable { + cursor: pointer; +} +.lock-screen__notification.is-actionable { + cursor: pointer; +} +.lock-screen__notification.is-actionable:focus-visible { + outline: 2px solid #fff; + outline-offset: 2px; } .lock-screen__notification-icon { width: 42px; @@ -723,21 +848,6 @@ button { -webkit-box-orient: vertical; -webkit-line-clamp: 2; } -.lock-screen__notification-close { - position: absolute; - top: 8px; - right: 8px; - display: grid; - width: 24px; - height: 24px; - place-items: center; - border: 0; - border-radius: 50%; - padding: 0; - color: #fff; - background: #0007; - cursor: pointer; -} .lock-screen__footer { position: absolute; z-index: 3; @@ -834,13 +944,13 @@ button { } /* DarkChat is intentionally independent from the phone appearance setting. */ -.phone-app:has(.darkchat-page), -.phone-app:has(.darkchat-page) .phone-app-window, -.phone-app:has(.darkchat-page) .phone-app-window__content { +.phone-app--darkchat, +.phone-app--darkchat .phone-app-window, +.phone-app--darkchat .phone-app-window__content { background: #000 !important; color-scheme: dark; } -.phone-app:has(.darkchat-page) .phone-status-bar { +.phone-app--darkchat .phone-status-bar { color: #fff !important; --status-bar-color: #fff; } @@ -1541,7 +1651,7 @@ button { font-size: 9px; white-space: nowrap; } -.darkchat-message:has(.darkchat-reactions) { +.darkchat-message { margin-bottom: 12px; } .darkchat-replying { @@ -2359,7 +2469,7 @@ button { .app-dock { position: absolute; z-index: 8; - bottom: 43px; + bottom: 30px; left: 15px; right: 15px; height: 82px; @@ -2410,7 +2520,7 @@ button { } .page-indicator { position: absolute; - bottom: 132px; + bottom: 122px; left: 0; right: 0; height: 14px; @@ -3711,7 +3821,7 @@ button { .clock-world-time { margin-top: 8px; font-variant-numeric: tabular-nums; - font-size: min(76px, 20cqw); + font-size: 74px; font-weight: 200; letter-spacing: -4px; } @@ -3858,7 +3968,7 @@ button { .clock-digits { margin: 70px 0 54px; font-variant-numeric: tabular-nums; - font-size: min(52px, 14cqw); + font-size: 52px; font-weight: 200; } .clock-sound-menu { @@ -4239,11 +4349,11 @@ button { background: #fff !important; color: #111; } -.phone-app:has(.messages-page) .phone-status-bar { +.phone-app--messages .phone-status-bar { color: #080808; text-shadow: none; } -.phone-app:has(.messages-page)::before { +.phone-app--messages::before { content: ''; position: absolute; z-index: 60; @@ -4496,22 +4606,28 @@ button { inset 0 1px 0 rgb(255 255 255 / 30%), 0 3px 12px rgb(0 0 0 / 12%); overflow: hidden; + background: linear-gradient(145deg, #5b91c2, #22527d); color: #fff; + font-size: 19px; + font-weight: 700; + letter-spacing: -0.4px; } -.messages-avatar__glyph { - padding-top: 6px; - font-family: 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif; - font-size: 34px; +.messages-avatar__initials { line-height: 1; - filter: drop-shadow(0 2px 2px rgb(0 0 0 / 15%)); +} +.messages-avatar__image { + width: 100%; + height: 100%; + display: block; + border-radius: inherit; + object-fit: cover; } .messages-avatar--small { width: 38px; height: 38px; } -.messages-avatar--small .messages-avatar__glyph { - padding-top: 5px; - font-size: 27px; +.messages-avatar--small .messages-avatar__initials { + font-size: 15px; } .messages-empty-state { min-height: 72%; @@ -4579,6 +4695,9 @@ button { .messages-thread-page--emoji { padding-bottom: 288px !important; } +.messages-thread-page--share { + padding-bottom: 260px !important; +} .messages-chat-header { position: fixed; z-index: 35; @@ -4625,9 +4744,8 @@ button { 0 0 0 1px rgb(0 0 0 / 5%), 0 8px 22px rgb(0 0 0 / 12%); } -.messages-avatar--header .messages-avatar__glyph { - padding-top: 7px; - font-size: 42px; +.messages-avatar--header .messages-avatar__initials { + font-size: 23px; } .messages-chat-header__name { max-width: 205px; @@ -4658,6 +4776,11 @@ button { min-height: calc(100% - 12px); padding: 10px 12px 20px; } +.messages-thread-bottom { + display: block; + width: 100%; + height: 1px; +} .messages-bubbles [class*='message-bubble'] { max-width: 248px; border-radius: 21px; @@ -4897,6 +5020,53 @@ button { gap: 7px; overflow-y: auto; } +.shared-composer-preview { + position: fixed; + z-index: 43; + right: 12px; + bottom: 89px; + left: 12px; + padding: 7px; + border: 1px solid rgb(255 255 255 / 72%); + border-radius: 19px; + background: rgb(242 242 247 / 90%); + box-shadow: 0 10px 28px rgb(20 44 76 / 18%); + backdrop-filter: blur(24px) saturate(1.6); + -webkit-backdrop-filter: blur(24px) saturate(1.6); +} +.shared-composer-preview > button { + position: absolute; + z-index: 2; + top: 2px; + right: 2px; + width: 24px; + height: 24px; + border-radius: 50%; + display: grid; + place-items: center; + color: white; + background: rgb(60 60 67 / 78%); +} +.shared-composer-preview--dark { + position: relative; + inset: auto; + z-index: 2; + margin: 0 8px 7px; + flex: 0 0 auto; + border-color: rgb(255 255 255 / 10%); + background: rgb(20 18 27 / 94%); + box-shadow: 0 8px 22px rgb(0 0 0 / 28%); +} +.phone-app.dark .shared-composer-preview { + border-color: rgb(255 255 255 / 10%); + background: rgb(36 36 40 / 92%); +} +.messages-media-picker__contacts { + height: 270px; + margin: 0; + padding: 8px 0 18px; + overflow-y: auto; +} .messages-media-picker__gifs button { min-height: 83px; display: flex; @@ -5273,9 +5443,8 @@ button { width: 78px; height: 78px; } -.messages-avatar--contact .messages-avatar__glyph { - padding-top: 10px; - font-size: 57px; +.messages-avatar--contact .messages-avatar__initials { + font-size: 32px; } .messages-contact-details__hero h2 { margin: 10px 0 2px; @@ -5396,7 +5565,7 @@ button { } } /* iOS 26-style Liquid Glass refinements and complete media pickers. */ -.phone-app:has(.messages-page) { +.messages-page { --ios-glass: rgb(252 252 255 / 70%); --ios-glass-strong: rgb(255 255 255 / 86%); --ios-glass-border: rgb(255 255 255 / 72%); @@ -5717,21 +5886,21 @@ button { .messages-attachment--video small { z-index: 2; } -.messages-attachment--gif:has(img) { +.messages-attachment--gif { background: #e9e9ed; } /* Konsta's k-app dark state drives the complete iOS Messages palette. */ -.phone-app.dark:has(.messages-page) { +.phone-app.dark .messages-page { --ios-glass: rgb(28 28 30 / 76%); --ios-glass-strong: rgb(36 36 38 / 90%); --ios-glass-border: rgb(255 255 255 / 12%); color-scheme: dark; } -.phone-app.dark:has(.messages-page)::before { +.phone-app--messages.dark::before { background: #000; } -.phone-app.dark:has(.messages-page) .phone-status-bar { +.phone-app--messages.dark .phone-status-bar { color: #fff; text-shadow: none; } @@ -6109,6 +6278,18 @@ button { display: block; margin: 0; } +.messages-thread-page .messages-attachment-menu > button:nth-child(4) > span { + background: #e9f9ed; + color: #28a745; +} +.messages-thread-page .messages-attachment-menu > button:nth-child(5) > span { + background: #f6eefe; + color: #af52de; +} +.messages-thread-page .messages-attachment-menu > button:nth-child(6) > span { + background: #fff0ef; + color: #ff3b30; +} .messages-thread-page .messages-messagebar > .k-toolbar @@ -6268,9 +6449,8 @@ button { width: 50px; height: 50px; } -.messages-inbox-page .messages-avatar__glyph { - padding-top: 6px; - font-size: 34px; +.messages-inbox-page .messages-avatar__initials { + font-size: 19px; } .messages-inbox-page .messages-conversation__body { gap: 3px; diff --git a/frontend/src/components/AppIcon.vue b/frontend/src/components/AppIcon.vue index c56d222..8e2da12 100644 --- a/frontend/src/components/AppIcon.vue +++ b/frontend/src/components/AppIcon.vue @@ -1,16 +1,21 @@ @@ -216,12 +255,17 @@ onBeforeUnmount(() => { class="app-icon-button" :class="{ 'app-icon-button--compact': compact }" type="button" - :aria-label="phone.t(app.labelKey)" + :aria-label="getPhoneAppLabel(app, phone.t)" :aria-disabled="!app.route" + :aria-keyshortcuts=" + editMode ? 'ArrowLeft ArrowRight ArrowUp ArrowDown' : undefined + " @click="launch" @contextmenu.prevent + @keydown="onKeydown" @pointercancel="cancelPointerDrag" @pointerdown="onPointerDown" + @lostpointercapture="cancelPointerDrag" @pointerleave="isDragging || clearHold()" @pointermove="onPointerMove" @pointerup="onPointerUp" @@ -233,6 +277,7 @@ onBeforeUnmount(() => { app.iconClass, { 'app-icon--image': !iconFailed && app.id !== 'calendar' }, ]" + :style="iconStyle" > {{ calendarWeekday }} @@ -262,14 +307,18 @@ onBeforeUnmount(() => { {{ - phone.t(app.labelKey) + getPhoneAppLabel(app, phone.t) }}